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

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