mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
get roc glue working directly with .roc
plugin files
This commit is contained in:
parent
b81ede5e2c
commit
17ece67999
5 changed files with 200 additions and 111 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3389,10 +3389,12 @@ dependencies = [
|
|||
"libc",
|
||||
"libloading",
|
||||
"pretty_assertions",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_linker",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
|
|
@ -7,10 +7,11 @@ use bumpalo::Bump;
|
|||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||||
use roc_build::link::{LinkType, LinkingStrategy};
|
||||
use roc_build::program::{
|
||||
standard_load_config, BuildFileError, BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions,
|
||||
handle_error_module, handle_loading_problem, standard_load_config, BuildFileError,
|
||||
BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME,
|
||||
};
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
|
||||
use roc_load::{ExpectMetadata, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_packaging::cache::RocCacheDir;
|
||||
use roc_packaging::tarball::Compression;
|
||||
|
@ -33,8 +34,6 @@ use tempfile::TempDir;
|
|||
mod format;
|
||||
pub use format::format;
|
||||
|
||||
const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
||||
|
||||
pub const CMD_BUILD: &str = "build";
|
||||
pub const CMD_RUN: &str = "run";
|
||||
pub const CMD_DEV: &str = "dev";
|
||||
|
@ -765,48 +764,6 @@ pub fn build(
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_error_module(
|
||||
mut module: roc_load::LoadedModule,
|
||||
total_time: std::time::Duration,
|
||||
filename: &OsStr,
|
||||
print_run_anyway_hint: bool,
|
||||
) -> io::Result<i32> {
|
||||
debug_assert!(module.total_problems() > 0);
|
||||
|
||||
let problems = roc_build::program::report_problems_typechecked(&mut module);
|
||||
|
||||
problems.print_to_stdout(total_time);
|
||||
|
||||
if print_run_anyway_hint {
|
||||
// If you're running "main.roc" then you can just do `roc run`
|
||||
// to re-run the program.
|
||||
print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
|
||||
|
||||
if filename != DEFAULT_ROC_FILENAME {
|
||||
print!(" {}", &filename.to_string_lossy());
|
||||
}
|
||||
|
||||
println!("\x1B[39m");
|
||||
}
|
||||
|
||||
Ok(problems.exit_code())
|
||||
}
|
||||
|
||||
fn handle_loading_problem(problem: LoadingProblem) -> io::Result<i32> {
|
||||
match problem {
|
||||
LoadingProblem::FormattedReport(report) => {
|
||||
print!("{}", report);
|
||||
Ok(1)
|
||||
}
|
||||
_ => {
|
||||
// TODO: tighten up the types here, we should always end up with a
|
||||
// formatted report from load.
|
||||
print!("Failed with error: {:?}", problem);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||
arena: &Bump,
|
||||
opt_level: OptLevel,
|
||||
|
|
|
@ -18,6 +18,7 @@ use roc_reporting::{
|
|||
report::{RenderTarget, DEFAULT_PALETTE},
|
||||
};
|
||||
use roc_target::TargetInfo;
|
||||
use std::ffi::OsStr;
|
||||
use std::ops::Deref;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
|
@ -29,6 +30,8 @@ use target_lexicon::Triple;
|
|||
#[cfg(feature = "target-wasm32")]
|
||||
use roc_collections::all::MutSet;
|
||||
|
||||
pub const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CodeGenTiming {
|
||||
pub code_gen: Duration,
|
||||
|
@ -647,6 +650,48 @@ impl<'a> BuildFileError<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_error_module(
|
||||
mut module: roc_load::LoadedModule,
|
||||
total_time: std::time::Duration,
|
||||
filename: &OsStr,
|
||||
print_run_anyway_hint: bool,
|
||||
) -> std::io::Result<i32> {
|
||||
debug_assert!(module.total_problems() > 0);
|
||||
|
||||
let problems = report_problems_typechecked(&mut module);
|
||||
|
||||
problems.print_to_stdout(total_time);
|
||||
|
||||
if print_run_anyway_hint {
|
||||
// If you're running "main.roc" then you can just do `roc run`
|
||||
// to re-run the program.
|
||||
print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
|
||||
|
||||
if filename != DEFAULT_ROC_FILENAME {
|
||||
print!(" {}", &filename.to_string_lossy());
|
||||
}
|
||||
|
||||
println!("\x1B[39m");
|
||||
}
|
||||
|
||||
Ok(problems.exit_code())
|
||||
}
|
||||
|
||||
pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result<i32> {
|
||||
match problem {
|
||||
LoadingProblem::FormattedReport(report) => {
|
||||
print!("{}", report);
|
||||
Ok(1)
|
||||
}
|
||||
_ => {
|
||||
// TODO: tighten up the types here, we should always end up with a
|
||||
// formatted report from load.
|
||||
print!("Failed with error: {:?}", problem);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn standard_load_config(
|
||||
target: &Triple,
|
||||
order: BuildOrdering,
|
||||
|
|
|
@ -20,6 +20,8 @@ roc_collections = { path = "../compiler/collections" }
|
|||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_tracing = { path = "../tracing" }
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_linker = { path = "../linker"}
|
||||
bumpalo = { version = "3.11.1", features = ["collections"] }
|
||||
target-lexicon = "0.12.3"
|
||||
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] }
|
||||
|
|
|
@ -2,13 +2,22 @@ use crate::roc_type;
|
|||
use crate::types::{Env, Types};
|
||||
use bumpalo::Bump;
|
||||
use libloading::Library;
|
||||
use roc_build::{
|
||||
link::{LinkType, LinkingStrategy},
|
||||
program::{
|
||||
build_file, handle_error_module, handle_loading_problem, standard_load_config,
|
||||
BuildFileError, BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions,
|
||||
},
|
||||
};
|
||||
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_mono::layout::GlobalLayoutInterner;
|
||||
use roc_packaging::cache::{self, RocCacheDir};
|
||||
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
|
||||
use roc_target::{Architecture, OperatingSystem, TargetInfo};
|
||||
use std::fs::File;
|
||||
use std::io::{self, ErrorKind, Write};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process;
|
||||
use strum::IntoEnumIterator;
|
||||
|
@ -23,91 +32,165 @@ impl IgnoreErrors {
|
|||
}
|
||||
|
||||
pub fn generate(input_path: &Path, output_path: &Path, spec_path: &Path) -> io::Result<i32> {
|
||||
// TODO: Add verification around the paths. Make sure they heav the correct file extension and what not.
|
||||
match load_types(
|
||||
input_path.to_path_buf(),
|
||||
Threading::AllAvailable,
|
||||
IgnoreErrors::NONE,
|
||||
) {
|
||||
Ok(types) => {
|
||||
// TODO: correctly setup building the glue spec. Then use the dylib it generates here.
|
||||
// For now, I am just passing in the dylib as the spec.
|
||||
// Also, it would be best if we directly call the internal build function from here instead of launch roc from the cli.
|
||||
// Lastly, we may need to modify the app file first before it can be loaded.
|
||||
// TODO: we should to modify the app file first before loading it.
|
||||
// Somehow it has to point to the correct platform file which may not exist on the target machine.
|
||||
let lib = unsafe { Library::new(spec_path) }.unwrap();
|
||||
type MakeGlue = unsafe extern "C" fn(
|
||||
*mut roc_std::RocResult<roc_std::RocList<roc_type::File>, roc_std::RocStr>,
|
||||
&roc_std::RocList<roc_type::Types>,
|
||||
let triple = Triple::host();
|
||||
|
||||
let code_gen_options = CodeGenOptions {
|
||||
backend: CodeGenBackend::Llvm, // Maybe eventually use dev here or add flag for this.
|
||||
opt_level: OptLevel::Development,
|
||||
emit_debug_info: false,
|
||||
};
|
||||
|
||||
let load_config = standard_load_config(
|
||||
&triple,
|
||||
BuildOrdering::BuildIfChecks,
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let make_glue: libloading::Symbol<MakeGlue> = unsafe {
|
||||
lib.get("roc__makeGlueForHost_1_exposed_generic".as_bytes())
|
||||
.ok()
|
||||
.ok_or(format!("Unable to load glue function"))
|
||||
.expect("errored")
|
||||
let arena = ManuallyDrop::new(Bump::new());
|
||||
let link_type = LinkType::Dylib;
|
||||
let linking_strategy = if roc_linker::supported(link_type, &triple) {
|
||||
LinkingStrategy::Surgical
|
||||
} else {
|
||||
LinkingStrategy::Legacy
|
||||
};
|
||||
let roc_types: roc_std::RocList<roc_type::Types> =
|
||||
types.iter().map(|x| x.into()).collect();
|
||||
let mut files = roc_std::RocResult::err(roc_std::RocStr::empty());
|
||||
unsafe { make_glue(&mut files, &roc_types) };
|
||||
|
||||
// Roc will free data passed into it. So forget that data.
|
||||
std::mem::forget(roc_types);
|
||||
let res_binary_path = build_file(
|
||||
&arena,
|
||||
&triple,
|
||||
spec_path.to_path_buf(),
|
||||
code_gen_options,
|
||||
false,
|
||||
link_type,
|
||||
linking_strategy,
|
||||
true,
|
||||
None,
|
||||
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
||||
load_config,
|
||||
);
|
||||
|
||||
let files: Result<roc_std::RocList<roc_type::File>, roc_std::RocStr> = files.into();
|
||||
let files = files.unwrap_or_else(|err| {
|
||||
eprintln!("Glue generation failed: {}", err);
|
||||
match res_binary_path {
|
||||
Ok(BuiltFile {
|
||||
binary_path,
|
||||
problems,
|
||||
total_time,
|
||||
expect_metadata: _,
|
||||
}) => {
|
||||
// TODO: Should binary_path be update to deal with extensions?
|
||||
use target_lexicon::OperatingSystem;
|
||||
let lib_path = match triple.operating_system {
|
||||
OperatingSystem::Windows => binary_path.with_extension("dll"),
|
||||
OperatingSystem::Darwin | OperatingSystem::MacOSX { .. } => {
|
||||
binary_path.with_extension("dylib")
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
for roc_type::File { name, content } in &files {
|
||||
let valid_name = PathBuf::from(name.as_str())
|
||||
.components()
|
||||
.all(|comp| matches!(comp, Component::CurDir | Component::Normal(_)));
|
||||
if !valid_name {
|
||||
eprintln!("File name was invalid: {}", &name);
|
||||
_ => binary_path.with_extension("so.1.0"),
|
||||
};
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
let full_path = output_path.join(name.as_str());
|
||||
if let Some(dir_path) = full_path.parent() {
|
||||
std::fs::create_dir_all(&dir_path).unwrap_or_else(|err| {
|
||||
eprintln!(
|
||||
"Unable to create output directory {} - {:?}",
|
||||
dir_path.display(),
|
||||
err
|
||||
// TODO: Should glue try and run with errors, especially type errors.
|
||||
// My gut feeling is no or that we should add a flag for it.
|
||||
// Given glue will generally be run by random end users, I think it should default to full correctness.
|
||||
debug_assert_eq!(
|
||||
problems.errors, 0,
|
||||
"if there are errors, they should have been returned as an error variant"
|
||||
);
|
||||
if problems.warnings > 0 {
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(
|
||||
".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
);
|
||||
}
|
||||
|
||||
let lib = unsafe { Library::new(lib_path) }.unwrap();
|
||||
type MakeGlue = unsafe extern "C" fn(
|
||||
*mut roc_std::RocResult<roc_std::RocList<roc_type::File>, roc_std::RocStr>,
|
||||
&roc_std::RocList<roc_type::Types>,
|
||||
);
|
||||
|
||||
let make_glue: libloading::Symbol<MakeGlue> = unsafe {
|
||||
lib.get("roc__makeGlueForHost_1_exposed_generic".as_bytes())
|
||||
.ok()
|
||||
.ok_or(format!("Unable to load glue function"))
|
||||
.expect("errored")
|
||||
};
|
||||
let roc_types: roc_std::RocList<roc_type::Types> =
|
||||
types.iter().map(|x| x.into()).collect();
|
||||
let mut files = roc_std::RocResult::err(roc_std::RocStr::empty());
|
||||
unsafe { make_glue(&mut files, &roc_types) };
|
||||
|
||||
// Roc will free data passed into it. So forget that data.
|
||||
std::mem::forget(roc_types);
|
||||
|
||||
let files: Result<roc_std::RocList<roc_type::File>, roc_std::RocStr> =
|
||||
files.into();
|
||||
let files = files.unwrap_or_else(|err| {
|
||||
eprintln!("Glue generation failed: {}", err);
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
for roc_type::File { name, content } in &files {
|
||||
let valid_name = PathBuf::from(name.as_str())
|
||||
.components()
|
||||
.all(|comp| matches!(comp, Component::CurDir | Component::Normal(_)));
|
||||
if !valid_name {
|
||||
eprintln!("File name was invalid: {}", &name);
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
let full_path = output_path.join(name.as_str());
|
||||
if let Some(dir_path) = full_path.parent() {
|
||||
std::fs::create_dir_all(&dir_path).unwrap_or_else(|err| {
|
||||
eprintln!(
|
||||
"Unable to create output directory {} - {:?}",
|
||||
dir_path.display(),
|
||||
err
|
||||
);
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
}
|
||||
let mut file = File::create(&full_path).unwrap_or_else(|err| {
|
||||
eprintln!(
|
||||
"Unable to create output file {} - {:?}",
|
||||
full_path.display(),
|
||||
err
|
||||
);
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
file.write_all(content.as_bytes()).unwrap_or_else(|err| {
|
||||
eprintln!(
|
||||
"Unable to write bindings to output file {} - {:?}",
|
||||
full_path.display(),
|
||||
err
|
||||
);
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
println!(
|
||||
"🎉 Generated type declarations in:\n\n\t{}",
|
||||
output_path.display()
|
||||
);
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
let mut file = File::create(&full_path).unwrap_or_else(|err| {
|
||||
eprintln!(
|
||||
"Unable to create output file {} - {:?}",
|
||||
full_path.display(),
|
||||
err
|
||||
);
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
file.write_all(content.as_bytes()).unwrap_or_else(|err| {
|
||||
eprintln!(
|
||||
"Unable to write bindings to output file {} - {:?}",
|
||||
full_path.display(),
|
||||
err
|
||||
);
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
Err(BuildFileError::ErrorModule { module, total_time }) => {
|
||||
handle_error_module(module, total_time, spec_path.as_os_str(), true)
|
||||
}
|
||||
Err(BuildFileError::LoadingProblem(problem)) => handle_loading_problem(problem),
|
||||
}
|
||||
|
||||
println!(
|
||||
"🎉 Generated type declarations in:\n\n\t{}",
|
||||
output_path.display()
|
||||
);
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue