get roc glue working directly with .roc plugin files

This commit is contained in:
Brendan Hansknecht 2023-03-04 17:57:41 -08:00
parent b81ede5e2c
commit 17ece67999
No known key found for this signature in database
GPG key ID: 0EA784685083E75B
5 changed files with 200 additions and 111 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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,

View file

@ -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,

View file

@ -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"] }

View file

@ -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 => {