diff --git a/Cargo.lock b/Cargo.lock index b17b6f36b8..e48a57fdf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3757,7 +3757,9 @@ dependencies = [ "roc_build", "roc_collections", "roc_error_macros", + "roc_load", "roc_mono", + "roc_reporting", "serde", "target-lexicon", "tempfile", diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 7848aace1f..65180805a0 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -44,6 +44,7 @@ pub const CMD_VERSION: &str = "version"; pub const CMD_FORMAT: &str = "format"; pub const CMD_TEST: &str = "test"; pub const CMD_GLUE: &str = "glue"; +pub const CMD_GEN_DUMMY_LIB: &str = "gen-dummy-lib"; pub const FLAG_DEBUG: &str = "debug"; pub const FLAG_DEV: &str = "dev"; @@ -276,6 +277,23 @@ pub fn build_app<'a>() -> Command<'a> { .required(true) ) ) + .subcommand(Command::new(CMD_GEN_DUMMY_LIB) + .about("Generate a dummy shared library that can be used for linking a platform binary") + .arg( + Arg::new(ROC_FILE) + .help("The .roc file for an app using the platform") + .allow_invalid_utf8(true) + .required(true) + ) + .arg( + Arg::new(FLAG_TARGET) + .long(FLAG_TARGET) + .help("Choose a different target") + .default_value(Target::default().as_str()) + .possible_values(Target::OPTIONS) + .required(false), + ) + ) .trailing_var_arg(true) .arg(flag_optimize) .arg(flag_max_threads.clone()) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index be4eb2afdb..cfa7801204 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -2,9 +2,9 @@ use roc_build::link::LinkType; use roc_cli::build::check_file; use roc_cli::{ build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV, - CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION, - DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, GLUE_FILE, - ROC_FILE, + CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_DUMMY_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, + CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, + GLUE_FILE, ROC_FILE, }; use roc_docs::generate_docs_html; use roc_error_macros::user_error; @@ -93,6 +93,12 @@ fn main() -> io::Result<()> { Ok(1) } } + Some((CMD_GEN_DUMMY_LIB, matches)) => { + let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap()); + let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default(); + + roc_linker::generate_dummy_lib(input_path, &target.to_triple()) + } Some((CMD_BUILD, matches)) => { let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default(); diff --git a/crates/linker/Cargo.toml b/crates/linker/Cargo.toml index 6d5467fde2..11aea3b232 100644 --- a/crates/linker/Cargo.toml +++ b/crates/linker/Cargo.toml @@ -16,6 +16,8 @@ roc_mono = { path = "../compiler/mono" } roc_build = { path = "../compiler/build" } roc_collections = { path = "../compiler/collections" } roc_error_macros = { path = "../error_macros" } +roc_load = { path = "../compiler/load" } +roc_reporting = { path = "../reporting" } bumpalo = { version = "3.11.0", features = ["collections"] } clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] } iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } diff --git a/crates/linker/src/lib.rs b/crates/linker/src/lib.rs index 952682f262..cc80685cf4 100644 --- a/crates/linker/src/lib.rs +++ b/crates/linker/src/lib.rs @@ -2,7 +2,9 @@ use memmap2::{Mmap, MmapMut}; use object::Object; use roc_build::link::{rebuild_host, LinkType}; use roc_error_macros::internal_error; +use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading}; use roc_mono::ir::OptLevel; +use roc_reporting::report::RenderTarget; use std::cmp::Ordering; use std::mem; use std::path::{Path, PathBuf}; @@ -94,6 +96,63 @@ pub fn link_preprocessed_host( surgery(roc_app_bytes, &metadata, binary_path, false, false, target) } +// Exposed function to load a platform file and generate a dummy lib for it. +pub fn generate_dummy_lib(input_path: &Path, triple: &Triple) -> std::io::Result { + // Note: this should theoretically just be able to load the host, I think. + // Instead, I am loading an entire app because that was simpler and had example code. + // If this was expected to stay around for the the long term, we should change it. + // But hopefully it will be removable once we have surgical linking on all platforms. + let target_info = triple.into(); + let arena = &bumpalo::Bump::new(); + let subs_by_module = Default::default(); + let loaded = roc_load::load_and_monomorphize( + arena, + input_path.to_path_buf(), + subs_by_module, + LoadConfig { + target_info, + render: RenderTarget::Generic, + threading: Threading::AllAvailable, + exec_mode: ExecutionMode::Executable, + }, + ) + .unwrap_or_else(|problem| todo!("{:?}", problem)); + + let exposed_to_host = loaded + .exposed_to_host + .values + .keys() + .map(|x| x.as_str(&loaded.interns).to_string()) + .collect(); + + let exported_closure_types = loaded + .exposed_to_host + .closure_types + .iter() + .map(|x| { + format!( + "{}_{}", + x.module_string(&loaded.interns), + x.as_str(&loaded.interns) + ) + }) + .collect(); + + if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point { + let dummy_lib = if let target_lexicon::OperatingSystem::Windows = triple.operating_system { + platform_path.with_file_name("libapp.obj") + } else { + platform_path.with_file_name("libapp.so") + }; + + let dummy_dll_symbols = make_dummy_dll_symbols(exposed_to_host, exported_closure_types); + generate_dynamic_lib(triple, &dummy_dll_symbols, &dummy_lib); + } else { + unreachable!(); + }; + Ok(0) +} + fn make_dummy_dll_symbols( exposed_to_host: Vec, exported_closure_types: Vec,