Merge remote-tracking branch 'remote/main' into list-splitting

This commit is contained in:
Luke Boswell 2024-11-13 07:34:03 +11:00
commit 69c2fef943
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
244 changed files with 3601 additions and 5671 deletions

View file

@ -42,29 +42,6 @@ pub fn link(
}
}
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
pub fn legacy_host_file(target: Target, platform_main_roc: &Path) -> PathBuf {
let lib_ext = target.static_library_file_ext();
let file_name = roc_linker::preprocessed_host_filename(target)
.replace(roc_linker::PRECOMPILED_HOST_EXT, lib_ext);
let lib_path = platform_main_roc.with_file_name(file_name);
let default_host_path: PathBuf = platform_main_roc
.with_file_name("libhost")
.with_extension(lib_ext);
if lib_path.exists() {
lib_path
} else if default_host_path.exists() {
default_host_path
} else {
let obj_ext = target.object_file_ext();
lib_path.with_extension(obj_ext)
}
}
// Attempts to find a file that is stored relative to the roc executable.
// Since roc is built in target/debug/roc, we may need to drop that path to find the file.
// This is used to avoid depending on the current working directory.
@ -457,6 +434,7 @@ pub fn rebuild_host(
};
let host_dest = if matches!(target.architecture(), Architecture::Wasm32) {
// TODO verify this is corect, how do we do get here with OptLevel::Development
if matches!(opt_level, OptLevel::Development) {
platform_main_roc.with_extension("o")
} else {
@ -467,7 +445,7 @@ pub fn rebuild_host(
.with_file_name("dynhost")
.with_extension(executable_extension)
} else {
legacy_host_file(target, platform_main_roc)
platform_main_roc.with_file_name(target.prebuilt_static_object())
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -1352,9 +1330,9 @@ pub fn llvm_module_to_dylib(
unsafe { Library::new(path) }
}
pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &Path) {
pub fn preprocess_host_wasm32(host_input_path: &Path, host_output_path: &Path) {
let host_input = host_input_path.to_str().unwrap();
let output_file = preprocessed_host_path.to_str().unwrap();
let output_file = host_output_path.to_str().unwrap();
/*
Notes:
@ -1400,9 +1378,11 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_counter: usize) {
let command_string = stringify_command(&command, false);
let cmd_str = &command_string;
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
print_command_str(cmd_str);
});
let cmd_output = command.output().unwrap();
let max_flaky_fail_count = 10;

View file

@ -1,6 +1,4 @@
use crate::link::{
legacy_host_file, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
};
use crate::link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy};
use bumpalo::collections::CollectIn;
use bumpalo::Bump;
use inkwell::memory_buffer::MemoryBuffer;
@ -28,6 +26,7 @@ use std::{
#[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet;
use roc_target::SurgicalHostArtifacts;
pub const DEFAULT_ROC_FILENAME: &str = "main.roc";
@ -97,7 +96,7 @@ pub fn gen_from_mono_module<'a>(
roc_file_path: &Path,
target: Target,
code_gen_options: CodeGenOptions,
preprocessed_host_path: &Path,
built_host_opt: &BuiltHostOpt,
wasm_dev_stack_bytes: Option<u32>,
) -> GenFromMono<'a> {
let path = roc_file_path;
@ -107,19 +106,27 @@ pub fn gen_from_mono_module<'a>(
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
CodeGenBackend::Wasm => gen_from_mono_module_dev(
arena,
loaded,
target,
preprocessed_host_path,
wasm_dev_stack_bytes,
AssemblyBackendMode::Binary, // dummy value, unused in practice
),
CodeGenBackend::Wasm => {
assert_ne!(
*built_host_opt,
BuiltHostOpt::None,
"Wasm backend needs a built host."
);
gen_from_mono_module_dev(
arena,
loaded,
target,
built_host_opt,
wasm_dev_stack_bytes,
AssemblyBackendMode::Binary, // dummy value, unused in practice
)
}
CodeGenBackend::Assembly(backend_mode) => gen_from_mono_module_dev(
arena,
loaded,
target,
preprocessed_host_path,
built_host_opt,
wasm_dev_stack_bytes,
backend_mode,
),
@ -430,43 +437,59 @@ fn gen_from_mono_module_llvm<'a>(
)
}
#[cfg(feature = "target-wasm32")]
fn gen_from_mono_module_dev<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: Target,
preprocessed_host_path: &Path,
built_host_opt: &BuiltHostOpt,
wasm_dev_stack_bytes: Option<u32>,
backend_mode: AssemblyBackendMode,
#[allow(unused_variables)] backend_mode: AssemblyBackendMode,
) -> GenFromMono<'a> {
match target.architecture() {
Architecture::Wasm32 => gen_from_mono_module_dev_wasm32(
arena,
loaded,
preprocessed_host_path,
wasm_dev_stack_bytes,
),
Architecture::X86_64 | Architecture::Aarch64 => {
gen_from_mono_module_dev_assembly(arena, loaded, target, backend_mode)
}
_ => todo!(),
}
}
match (built_host_opt, target.architecture()) {
(BuiltHostOpt::Additive(host_path), Architecture::Wasm32) => {
#[cfg(feature = "target-wasm32")]
{
gen_from_mono_module_dev_wasm32(arena, loaded, host_path, wasm_dev_stack_bytes)
}
#[cfg(not(feature = "target-wasm32"))]
pub fn gen_from_mono_module_dev<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: Target,
_host_input_path: &Path,
_wasm_dev_stack_bytes: Option<u32>,
backend_mode: AssemblyBackendMode,
) -> GenFromMono<'a> {
match target.architecture() {
Architecture::X86_64 | Architecture::Aarch64 => {
gen_from_mono_module_dev_assembly(arena, loaded, target, backend_mode)
#[cfg(not(feature = "target-wasm32"))]
{
internal_error!("Compiler was not built with feature 'target-wasm32'.");
}
}
(BuiltHostOpt::None, Architecture::Wasm32) => {
internal_error!("Cannot compile wasm32 without a host on the dev compiler backend.")
}
(BuiltHostOpt::Legacy(host_path), Architecture::Wasm32) => internal_error!(
"Unsupported host files found for use with wasm32 dev compiler backend:\n {}",
host_path.display()
),
(
BuiltHostOpt::Surgical(SurgicalHostArtifacts {
preprocessed_host, ..
}),
Architecture::Wasm32,
) => internal_error!(
"Unsupported host files found for use with wasm32 dev compiler backend:\n {}",
preprocessed_host.display()
),
(_, Architecture::X86_64 | Architecture::Aarch64) => {
#[cfg(not(feature = "target-wasm32"))]
{
gen_from_mono_module_dev_assembly(arena, loaded, target, backend_mode)
}
#[cfg(feature = "target-wasm32")]
{
internal_error!("Compiler was not built with feature 'target-wasm32'.")
}
}
(_, Architecture::Aarch32) => {
internal_error!("Dev compiler backend does not support 32 bit ARM architectures")
}
(_, Architecture::X86_32) => {
internal_error!("Dev compiler backend does not support 32 bit x86 architectures")
}
_ => todo!(),
}
}
@ -474,7 +497,7 @@ pub fn gen_from_mono_module_dev<'a>(
fn gen_from_mono_module_dev_wasm32<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
preprocessed_host_path: &Path,
built_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> GenFromMono<'a> {
let all_code_gen_start = Instant::now();
@ -500,17 +523,17 @@ fn gen_from_mono_module_dev_wasm32<'a>(
stack_bytes: wasm_dev_stack_bytes.unwrap_or(roc_gen_wasm::Env::DEFAULT_STACK_BYTES),
};
let host_bytes = std::fs::read(preprocessed_host_path).unwrap_or_else(|_| {
let host_bytes = std::fs::read(built_host_path).unwrap_or_else(|_| {
internal_error!(
"Failed to read host object file {}! Try omitting --prebuilt-platform",
preprocessed_host_path.display()
built_host_path.display()
)
});
let host_module = roc_gen_wasm::parse_host(arena, &host_bytes).unwrap_or_else(|e| {
internal_error!(
"I ran into a problem with the host object file, {} at offset 0x{:x}:\n{}",
preprocessed_host_path.display(),
built_host_path.display(),
e.offset,
e.message
)
@ -544,6 +567,7 @@ fn gen_from_mono_module_dev_wasm32<'a>(
)
}
#[allow(dead_code)]
fn gen_from_mono_module_dev_assembly<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
@ -720,7 +744,8 @@ pub fn build_file<'a>(
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
build_host: bool,
suppress_build_host_warning: bool,
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
@ -728,7 +753,6 @@ pub fn build_file<'a>(
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
// Step 1: compile the app and generate the .o file
let loaded = roc_load::load_and_monomorphize(
arena,
app_module_path.clone(),
@ -746,7 +770,8 @@ pub fn build_file<'a>(
emit_timings,
link_type,
linking_strategy,
prebuilt_requested,
build_host,
suppress_build_host_warning,
wasm_dev_stack_bytes,
loaded,
compilation_start,
@ -754,6 +779,64 @@ pub fn build_file<'a>(
)
}
#[derive(Debug, PartialEq, Eq)]
/// Opt because of possible None value
// Advice: do not try to wrap this in an Option, that would require cloning in build_loaded_file.
pub enum BuiltHostOpt {
Additive(PathBuf),
Legacy(PathBuf),
// SurgicalHostArtifacts contains metadata, preprocessed_host
Surgical(SurgicalHostArtifacts),
None,
}
fn build_and_preprocess_host(
code_gen_options: CodeGenOptions,
dll_stub_symbols: Vec<String>,
emit_timings: bool,
linking_strategy: LinkingStrategy,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
target: Target,
) -> BuiltHostOpt {
let rebuild_thread = match linking_strategy {
LinkingStrategy::Additive => spawn_wasm32_host_build_thread(
code_gen_options.opt_level,
target,
platform_main_roc.to_owned(),
preprocessed_host_path.to_owned(),
),
LinkingStrategy::Surgical => {
let preprocessed_path =
platform_main_roc.with_file_name(target.prebuilt_surgical_host());
let metadata_path = platform_main_roc.with_file_name(target.metadata_file_name());
spawn_surgical_host_build_thread(
code_gen_options.opt_level,
target,
platform_main_roc.to_owned(),
dll_stub_symbols,
preprocessed_path,
preprocessed_host_path.to_owned(),
metadata_path,
)
}
LinkingStrategy::Legacy => spawn_legacy_host_build_thread(
code_gen_options.opt_level,
target,
platform_main_roc.to_owned(),
),
};
let (rebuild_duration, path) = rebuild_thread.join().expect("Failed to build host.");
if emit_timings {
println!(
"Finished rebuilding the platform host in {} ms\n",
rebuild_duration
);
}
path
}
#[allow(clippy::too_many_arguments)]
fn build_loaded_file<'a>(
arena: &'a Bump,
@ -762,121 +845,59 @@ fn build_loaded_file<'a>(
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
mut linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
linking_strategy: LinkingStrategy,
build_host_requested: bool,
suppress_build_host_warning: bool,
wasm_dev_stack_bytes: Option<u32>,
loaded: roc_load::MonomorphizedModule<'a>,
compilation_start: Instant,
out_path: Option<&Path>,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let platform_main_roc = match &loaded.entry_point {
// get the platform path from the app header
let platform_main_roc_path = match &loaded.entry_point {
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
_ => unreachable!(),
};
// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let output_exe_path = get_exe_path(
out_path,
app_module_path.as_path(),
target,
linking_strategy,
link_type,
);
if is_platform_prebuilt && linking_strategy == LinkingStrategy::Surgical {
// Fallback to legacy linking if the preprocessed host file does not exist, but a legacy host does exist.
let preprocessed_host_path =
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target));
let legacy_host_path = legacy_host_file(target, &platform_main_roc);
if !preprocessed_host_path.exists() && legacy_host_path.exists() {
linking_strategy = LinkingStrategy::Legacy;
}
}
let dll_stub_symbols =
roc_linker::ExposedSymbols::from_exposed_to_host(&loaded.interns, &loaded.exposed_to_host);
// the preprocessed host is stored beside the platform's main.roc
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
if target == Target::Wasm32 {
// when compiling a wasm application, we implicitly assume here that the host is in zig
// and has a file called "host.zig"
platform_main_roc.with_file_name("host.zig")
let built_host_opt =
// Not sure if this is correct for all calls with LinkType::Dylib...
if link_type == LinkType::Dylib || target == Target::Wasm32 {
BuiltHostOpt::None
} else {
legacy_host_file(target, &platform_main_roc)
}
} else {
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target))
};
let prebuilt_host = determine_built_host_path(&platform_main_roc_path, target, build_host_requested, link_type, linking_strategy, suppress_build_host_warning);
let output_exe_path = match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
let ends_with_sep = {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
path.as_os_str().as_bytes().ends_with(&[b'/'])
match prebuilt_host {
BuiltHostOpt::None => {
build_and_preprocess_host(
code_gen_options,
dll_stub_symbols,
emit_timings,
linking_strategy,
&platform_main_roc_path,
&output_exe_path,
target,
)
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let last = path.as_os_str().encode_wide().last();
last == Some(0x002f)// UTF-16 slash
|| last == Some(0x005c) // UTF-16 backslash
BuiltHostOpt::Surgical(ref surgical_artifacts) => {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&surgical_artifacts.preprocessed_host, output_exe_path.as_path()).unwrap();
prebuilt_host
}
};
// If you specified a path that ends in in a directory separator, then
// use that directory, but use the app module's filename for the filename.
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_output_extension(&path.join(filename), target, linking_strategy, link_type)
} else {
path.to_path_buf()
other => other
}
}
None => with_output_extension(&app_module_path, target, linking_strategy, link_type),
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_platform_prebuilt {
if !preprocessed_host_path.exists() {
invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
std::process::exit(1);
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
None
} else {
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get prebuilt platforms from there.
let dll_stub_symbols = roc_linker::ExposedSymbols::from_exposed_to_host(
&loaded.interns,
&loaded.exposed_to_host,
);
let join_handle = spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
platform_main_roc.clone(),
preprocessed_host_path.clone(),
output_exe_path.clone(),
target,
dll_stub_symbols,
);
Some(join_handle)
};
};
let buf = &mut String::with_capacity(1024);
@ -909,31 +930,13 @@ fn build_loaded_file<'a>(
let problems = report_problems_monomorphized(&mut loaded);
let loaded = loaded;
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
None
} else {
Some(rebuild_thread)
}
} else {
None
};
let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
code_gen_options,
&preprocessed_host_path,
&built_host_opt,
wasm_dev_stack_bytes,
);
@ -966,24 +969,18 @@ fn build_loaded_file<'a>(
);
}
if let Some(thread) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
}
// Step 2: link the prebuilt platform and compiled app
// link the prebuilt platform and compiled app
let link_start = Instant::now();
match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => {
let metadata_file = platform_main_roc_path.with_file_name(target.metadata_file_name());
roc_linker::link_preprocessed_host(
target,
&platform_main_roc,
&roc_app_bytes,
&output_exe_path,
metadata_file,
);
}
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
@ -1008,36 +1005,57 @@ fn build_loaded_file<'a>(
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let builtins_host_tempfile = roc_bitcode::host_tempfile()
.expect("failed to write host builtins object to tempfile");
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
// the host has been compiled into a .o or .obj file
inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
let mut host_path = String::new();
match built_host_opt {
BuiltHostOpt::Legacy(p) => {
host_path.push_str(&p.to_string_lossy());
inputs.push(&host_path);
}
BuiltHostOpt::None => {
// In case of link_type == LinkType::Dylib or target == Target::Wasm32
// When compiling a Dylib there is no host, such as when generating glue using `roc glue`.
if target == Target::Wasm32 {
let wasm_host_zig: PathBuf =
platform_main_roc_path.with_file_name("host.zig");
assert!(
wasm_host_zig.exists(),
"No host.zig file found at {} when building wasm32 target.",
wasm_host_zig.display()
);
host_path.push_str(&wasm_host_zig.to_string_lossy());
inputs.push(&host_path);
}
}
other => {
panic!("Unexpected variant of built_host_opt in combination with `LinkingStrategy::Legacy`: {:?}", other);
}
}
let builtins_host_tempfile = roc_bitcode::host_tempfile()
.expect("failed to write host builtins object to tempfile");
if matches!(code_gen_options.backend, CodeGenBackend::Assembly(_)) {
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
}
let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
.map_err(|_| todo!("linker failed to spawn."))?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
.map_err(|_| todo!("linker error after spawning"))?;
// Extend the lifetime of the tempfile so it doesn't get dropped
// (and thus deleted) before the child process is done using it!
let _ = builtins_host_tempfile;
if !exit_status.success() {
todo!(
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
exit_status.code()
);
todo!("linker failed with exit code {:?}", exit_status.code());
}
}
}
@ -1058,124 +1076,291 @@ fn build_loaded_file<'a>(
})
}
fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
let prefix = if prebuilt_requested {
"Because I was run with --prebuilt-platform, "
} else {
""
};
fn determine_built_host_path(
platform_main_roc_path: &Path,
target: Target,
build_host_requested: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
suppress_build_host_warning: bool,
) -> BuiltHostOpt {
if build_host_requested {
if !suppress_build_host_warning {
// TODO
//report_rebuilding_existing_host(&preprocessed_host.to_string_lossy());
unimplemented!()
}
let preprocessed_host_path_str = preprocessed_host_path.to_string_lossy();
let extra_err_msg = if preprocessed_host_path_str.ends_with(".rh") {
"\n\n\tNote: If the platform does have an .rh1 file but no .rh file, it's because it's been built with an older version of roc. Contact the author to release a new build of the platform using a roc release newer than March 21 2023.\n"
match link_type {
LinkType::Executable => BuiltHostOpt::None,
LinkType::Dylib => {
eprintln!("You asked me to build the host, but I don't know how to rebuild a host for a dynamic library.");
std::process::exit(1);
}
LinkType::None => {
eprintln!("You asked me to build the host, but I don't know how to rebuild a host for an unlinked object.");
std::process::exit(1);
}
}
} else {
""
};
match linking_strategy {
LinkingStrategy::Legacy => {
let legacy_host_path_res = target.find_legacy_host(platform_main_roc_path);
match legacy_host_path_res {
Ok(legacy_host_path) => BuiltHostOpt::Legacy(legacy_host_path),
Err(err_msg) => {
eprintln!("Legacy linking failed: {}", err_msg);
#[cfg(target_os = "linux")]
eprintln!(
"\n TIP: Maybe try surgical linking with the flag --linker=surgical"
);
std::process::exit(1);
}
}
}
LinkingStrategy::Surgical => {
let surgical_artifacts = target.find_surgical_host(platform_main_roc_path);
match surgical_artifacts {
Ok(surgical_artifacts) => BuiltHostOpt::Surgical(surgical_artifacts),
Err(paths_str) => {
// TODO improve error message
eprintln!(
"LinkingStrategy was set to Surgical (default), but \
I tried to find the surgical host at any of these paths {} but it does not exist.",
paths_str
);
std::process::exit(1);
}
}
}
LinkingStrategy::Additive => {
unimplemented!()
}
}
}
}
/// Get outut path for the executable.
///
/// If you specified a path that ends in in a directory separator, then
/// use that directory, but use the app module's filename for the filename.
fn get_exe_path(
out_path: Option<&Path>,
app_module_path: &Path,
target: Target,
linking_strategy: LinkingStrategy,
link_type: LinkType,
) -> PathBuf {
match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
let ends_with_sep = {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
path.as_os_str().as_bytes().ends_with(&[b'/'])
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let last = path.as_os_str().encode_wide().last();
last == Some(0x002f)// UTF-16 slash
|| last == Some(0x005c) // UTF-16 backslash
}
};
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_output_extension(&path.join(filename), target, linking_strategy, link_type)
} else {
path.to_path_buf()
}
}
None => with_output_extension(app_module_path, target, linking_strategy, link_type),
}
}
#[allow(dead_code)]
fn report_rebuilding_existing_host(host_path: &str) {
eprintln!(
indoc::indoc!(
r#"
{}I was expecting this file to exist:
WARNING: I found an existing compiled host at:
{}
However, it was not there!{}
However, the --build-host flag was set! I will rebuild the host and overwrite the existing file.
If you have the platform's source code locally, you may be able to generate it by re-running this command omitting --prebuilt-platform
Remove the --build-host flag to use the existing host and silence this warning.
Rebuilding hosts using the roc compiler is deprecated and will be removed in a future version.
"#
),
prefix,
preprocessed_host_path.to_string_lossy(),
extra_err_msg
host_path,
);
}
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread(
#[allow(dead_code)]
fn report_rebuilding_missing_host(host_path: &str) {
eprintln!(
indoc::indoc!(
r#"
WARNING: I was expecting a prebuilt host to exist at:
{}
However, it was not there! I will rebuild the host and write it to that location.
Rebuilding hosts using the roc compiler is deprecated and will be removed in a future version.
"#
),
host_path,
);
}
#[allow(dead_code)]
fn report_missing_prebuilt_host(msg: &str) {
eprintln!(
indoc::indoc!(
r#"
I was expecting a prebuilt host to exist:
{}
However, it was not there!
If you have the platform's source code locally, you may be able to generate it by using a build script.
"#
),
msg
);
}
#[allow(dead_code)]
fn report_refusing_to_rebuild_host(host_path: &str) {
eprintln!(
indoc::indoc!(
r#"
I found a prebuilt host for this platform, but you requested to rebuild it:
{}
Remove the `--build-host` flag to use the prebuilt host.
The `--build-host` flag is deprecated and will be removed in a future release.
"#
),
host_path,
);
}
fn spawn_wasm32_host_build_thread(
opt_level: OptLevel,
linking_strategy: LinkingStrategy,
platform_main_roc: PathBuf,
preprocessed_host_path: PathBuf,
output_exe_path: PathBuf,
target: Target,
dll_stub_symbols: Vec<String>,
) -> std::thread::JoinHandle<u128> {
platform_main_roc: PathBuf,
output_path: PathBuf,
) -> std::thread::JoinHandle<(u128, BuiltHostOpt)> {
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
eprintln!("🔨 Building host ...");
let rebuild_host_start = Instant::now();
let start = Instant::now();
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
let host_dest = rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
build_and_preprocess_host_lowlevel(
opt_level,
target,
platform_main_roc.as_path(),
preprocessed_host_path.as_path(),
&dll_stub_symbols,
);
preprocess_host_wasm32(host_dest.as_path(), &output_path);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
LinkingStrategy::Legacy => {
rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
}
}
rebuild_host_start.elapsed().as_millis()
(
start.elapsed().as_millis(),
BuiltHostOpt::Additive(output_path),
)
})
}
pub fn build_and_preprocess_host(
/// Note this will copy the preprocessed host to the executable location
/// where the surgical linker will modify that copy in-place.
fn spawn_surgical_host_build_thread(
opt_level: OptLevel,
target: Target,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
exposed_symbols: roc_linker::ExposedSymbols,
) {
let stub_dll_symbols = exposed_symbols.stub_dll_symbols();
platform_main_roc: PathBuf,
dll_stub_symbols: Vec<String>,
preprocessed_path: PathBuf,
output_exe_path: PathBuf,
metadata_path: PathBuf,
) -> std::thread::JoinHandle<(u128, BuiltHostOpt)> {
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Building host ...");
build_and_preprocess_host_lowlevel(
opt_level,
target,
platform_main_roc,
preprocessed_host_path,
&stub_dll_symbols,
)
let start = Instant::now();
let stub_lib = roc_linker::generate_stub_lib_from_loaded(
target,
platform_main_roc.as_path(),
dll_stub_symbols.as_slice(),
);
debug_assert!(stub_lib.exists());
let host_exe = rebuild_host(
opt_level,
target,
platform_main_roc.as_path(),
Some(&stub_lib),
);
roc_linker::preprocess_host(
target,
host_exe.as_path(),
metadata_path.as_path(),
preprocessed_path.as_path(),
&stub_lib,
false,
false,
);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_path, &output_exe_path).unwrap();
(
start.elapsed().as_millis(),
BuiltHostOpt::Surgical(SurgicalHostArtifacts {
metadata: metadata_path,
preprocessed_host: preprocessed_path,
}),
)
})
}
fn build_and_preprocess_host_lowlevel(
// Note the output host will be
fn spawn_legacy_host_build_thread(
opt_level: OptLevel,
target: Target,
platform_main_roc: &Path,
_preprocessed_host_path: &Path,
stub_dll_symbols: &[String],
) {
let stub_lib =
roc_linker::generate_stub_lib_from_loaded(target, platform_main_roc, stub_dll_symbols);
platform_main_roc: PathBuf,
) -> std::thread::JoinHandle<(u128, BuiltHostOpt)> {
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Building host ...");
debug_assert!(stub_lib.exists());
let start = Instant::now();
let host_dest = rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
let host_dest = rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
roc_linker::preprocess_host(
target,
host_dest.as_path(),
platform_main_roc,
&stub_lib,
false,
false,
)
(start.elapsed().as_millis(), BuiltHostOpt::Legacy(host_dest))
})
}
#[allow(clippy::too_many_arguments)]
@ -1261,7 +1446,7 @@ pub fn build_str_test<'a>(
arena: &'a Bump,
app_module_path: &Path,
app_module_source: &'a str,
assume_prebuild: bool,
build_host_requested: bool,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let target = target_lexicon::Triple::host().into();
@ -1298,6 +1483,9 @@ pub fn build_str_test<'a>(
)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
// we are in a test, so we don't need to provide a warning about rebuilding the host
let suppress_build_host_warning = true;
build_loaded_file(
arena,
target,
@ -1306,7 +1494,8 @@ pub fn build_str_test<'a>(
emit_timings,
link_type,
linking_strategy,
assume_prebuild,
build_host_requested,
suppress_build_host_warning,
wasm_dev_stack_bytes,
loaded,
compilation_start,

View file

@ -5406,7 +5406,7 @@ mod test_reporting {
2 -> 2
"
),
@r#"
@r###"
UNKNOWN OPERATOR in tmp/when_outdented_branch/Test.roc
This looks like an operator, but it's not one I recognize!
@ -5424,7 +5424,7 @@ mod test_reporting {
In Roc, functions are always written as a lambda, like
increment = \n -> n + 1
"#
"###
);
test_report!(
@ -5635,7 +5635,7 @@ mod test_reporting {
test_report!(
return_space_problem,
"return \t",
@r"
@r###"
TAB CHARACTER in tmp/return_space_problem/Test.roc
I encountered a tab character:
@ -5644,7 +5644,7 @@ mod test_reporting {
^
Tab characters are not allowed in Roc code. Please use spaces instead!
"
"###
);
test_report!(
@ -6253,7 +6253,7 @@ All branches in an `if` must have the same type!
r#"
app "broken"
packages {
pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br",
pf: "generic-test-platform/main.roc",
}
imports [
pf.Stdout,
@ -14659,7 +14659,7 @@ All branches in an `if` must have the same type!
leftover_statement,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14690,7 +14690,7 @@ All branches in an `if` must have the same type!
fx_fn_annotated_as_pure,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14728,7 +14728,7 @@ All branches in an `if` must have the same type!
fx_fn_annotated_as_pure_stmt,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14776,7 +14776,7 @@ All branches in an `if` must have the same type!
nested_function_def_fx_no_bang,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14807,7 +14807,7 @@ All branches in an `if` must have the same type!
ignored_result_stmt,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14839,7 +14839,7 @@ All branches in an `if` must have the same type!
ignored_stmt_forgot_to_call,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14881,7 +14881,7 @@ All branches in an `if` must have the same type!
function_def_leftover_bang,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14910,7 +14910,7 @@ All branches in an `if` must have the same type!
effect_in_top_level_value_def,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14948,7 +14948,7 @@ All branches in an `if` must have the same type!
aliased_fx_fn,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -14978,7 +14978,7 @@ All branches in an `if` must have the same type!
unsuffixed_fx_in_record,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15010,7 +15010,7 @@ All branches in an `if` must have the same type!
suffixed_pure_in_record,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15040,7 +15040,7 @@ All branches in an `if` must have the same type!
unsuffixed_fx_arg,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15077,7 +15077,7 @@ All branches in an `if` must have the same type!
suffixed_pure_arg,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15112,7 +15112,7 @@ All branches in an `if` must have the same type!
unsuffixed_tuple_fx_field,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15156,7 +15156,7 @@ All branches in an `if` must have the same type!
suffixed_tuple_pure_field,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15184,7 +15184,7 @@ All branches in an `if` must have the same type!
unsuffixed_tag_fx_field,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15228,7 +15228,7 @@ All branches in an `if` must have the same type!
suffixed_tag_pure_field,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15256,7 +15256,7 @@ All branches in an `if` must have the same type!
unsuffixed_opaque_fx_field,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15288,7 +15288,7 @@ All branches in an `if` must have the same type!
suffixed_opaque_pure_field,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15318,7 +15318,7 @@ All branches in an `if` must have the same type!
fx_passed_to_untyped_pure_hof,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect
@ -15361,7 +15361,7 @@ All branches in an `if` must have the same type!
fx_passed_to_partially_inferred_pure_hof,
indoc!(
r#"
app [main!] { pf: platform "../../../../../examples/cli/effects-platform/main.roc" }
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
import pf.Effect

View file

@ -1462,6 +1462,8 @@ pub fn load<'a>(
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
enum Threads {
Single,
#[allow(dead_code)]
Many(usize),
}
@ -3250,7 +3252,7 @@ fn finish_specialization<'a>(
.collect();
let module_id = state.root_id;
let uses_prebuilt_platform = match platform_data {
let needs_prebuilt_host = match platform_data {
Some(data) => data.is_prebuilt,
// If there's no platform data (e.g. because we're building a module)
// then there's no prebuilt platform either!
@ -3273,7 +3275,7 @@ fn finish_specialization<'a>(
timings: state.timings,
toplevel_expects,
glue_layouts: GlueLayouts { getters: vec![] },
uses_prebuilt_platform,
needs_prebuilt_host,
})
}

View file

@ -186,7 +186,7 @@ pub struct MonomorphizedModule<'a> {
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
pub expectations: VecMap<ModuleId, Expectations>,
pub uses_prebuilt_platform: bool,
pub needs_prebuilt_host: bool,
pub glue_layouts: GlueLayouts<'a>,
}

View file

@ -1137,15 +1137,15 @@ fn explicit_builtin_import() {
indoc!(
r"
EXPLICIT BUILTIN IMPORT in tmp/explicit_builtin_import/Main.roc
The builtin Bool was imported here:
3 import Bool
^^^^^^^^^^^
Builtins are imported automatically, so you can remove this import.
Tip: Learn more about builtins in the tutorial:
Tip: Learn more about builtins in the tutorial:
<https://www.roc-lang.org/tutorial#builtin-modules>
"
)
@ -1172,15 +1172,15 @@ fn explicit_builtin_import_empty_exposing() {
indoc!(
r"
EXPLICIT BUILTIN IMPORT in tmp/empty_exposing_builtin_import/Main.roc
The builtin Bool was imported here:
3 import Bool exposing []
^^^^^^^^^^^^^^^^^^^^^^^
Builtins are imported automatically, so you can remove this import.
Tip: Learn more about builtins in the tutorial:
Tip: Learn more about builtins in the tutorial:
<https://www.roc-lang.org/tutorial#builtin-modules>
"
)
@ -1211,16 +1211,16 @@ fn explicit_builtin_type_import() {
indoc!(
r"
EXPLICIT BUILTIN IMPORT in tmp/explicit_builtin_type_import/Main.roc
`Dict.Dict` was imported here:
3 import Dict exposing [Dict, isEmpty]
^^^^
All types from builtins are automatically exposed, so you can remove
`Dict` from the exposing list.
Tip: Learn more about builtins in the tutorial:
Tip: Learn more about builtins in the tutorial:
<https://www.roc-lang.org/tutorial#builtin-modules>
"
)
@ -2043,7 +2043,7 @@ fn module_cyclic_import_transitive() {
indoc!(
r"
module []
import Age
"
),
@ -2108,7 +2108,7 @@ fn roc_file_no_extension() {
indoc!(
r#"
app "helloWorld"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br" }
packages { pf: "generic-test-platform/main.roc" }
imports [pf.Stdout]
provides [main] to pf

View file

@ -1790,7 +1790,7 @@ impl<'a> LambdaSet<'a> {
// functions, despite not appearing in the lambda set.
// We don't want to compile them as thunks, so we need to figure out a special-casing for
// them.
// To reproduce: test cli_run
// To reproduce: test cli_tests
//
// debug_assert!(
// self.set

View file

@ -13,7 +13,7 @@ pub fn parse_benchmark(c: &mut Criterion) {
path.push("examples");
path.push("cli");
path.push("false-interpreter");
path.push("False.roc");
path.push("main.roc");
let src = std::fs::read_to_string(&path).unwrap();
b.iter(|| {

View file

@ -3,6 +3,7 @@
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
use std::path::{Path, PathBuf};
use std::str::FromStr;
use roc_error_macros::user_error;
@ -17,6 +18,18 @@ pub enum OperatingSystem {
Windows,
}
impl std::fmt::Display for OperatingSystem {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let arch_str = match self {
OperatingSystem::Freestanding => "freestanding",
OperatingSystem::Linux => "linux",
OperatingSystem::Mac => "macos",
OperatingSystem::Windows => "windows",
};
write!(f, "{}", arch_str)
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PtrWidth {
@ -36,8 +49,8 @@ pub enum Architecture {
impl std::fmt::Display for Architecture {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let arch_str = match self {
Architecture::Aarch32 => "aarch32",
Architecture::Aarch64 => "aarch64",
Architecture::Aarch32 => "arm",
Architecture::Aarch64 => "arm64",
Architecture::Wasm32 => "wasm32",
Architecture::X86_32 => "x86_32",
Architecture::X86_64 => "x86_64",
@ -74,6 +87,12 @@ pub enum Target {
Wasm32,
}
#[derive(Debug, PartialEq, Eq)]
pub struct SurgicalHostArtifacts {
pub metadata: PathBuf,
pub preprocessed_host: PathBuf,
}
impl Target {
pub const fn architecture(&self) -> Architecture {
use Target::*;
@ -122,6 +141,7 @@ impl Target {
self.architecture().ptr_alignment_bytes()
}
// file extension for an object file
pub const fn object_file_ext(&self) -> &str {
use Target::*;
match self {
@ -131,6 +151,7 @@ impl Target {
}
}
// file extension for a static library file
pub const fn static_library_file_ext(&self) -> &str {
use Target::*;
match self {
@ -140,6 +161,18 @@ impl Target {
}
}
// file extension for a dynamic/shared library file
pub const fn dynamic_library_file_ext(&self) -> &str {
use Target::*;
match self {
LinuxX32 | LinuxX64 | LinuxArm64 => "so",
MacX64 | MacArm64 => "dylib",
WinX32 | WinX64 | WinArm64 => "dll",
Wasm32 => "wasm",
}
}
// file extension for an executable file
pub const fn executable_file_ext(&self) -> Option<&str> {
use Target::*;
match self {
@ -148,6 +181,137 @@ impl Target {
Wasm32 => Some("wasm"),
}
}
// file name for a prebuilt host object file
// used for legacy linking
pub fn prebuilt_static_object(&self) -> String {
use Target::*;
match self {
LinuxX32 | LinuxX64 | LinuxArm64 | MacX64 | MacArm64 | Wasm32 => {
format!("{}.o", self)
}
WinX32 | WinX64 | WinArm64 => {
format!("{}.obj", self)
}
}
}
// file name for a prebuilt host static library file
// used for legacy linking
pub fn prebuilt_static_library(&self) -> String {
use Target::*;
match self {
LinuxX32 | LinuxX64 | LinuxArm64 | MacX64 | MacArm64 | Wasm32 => {
format!("{}.a", self)
}
WinX32 | WinX64 | WinArm64 => {
format!("{}.lib", self)
}
}
}
// file name for a preprocessed host executable file
// used for surgical linking
pub fn prebuilt_surgical_host(&self) -> String {
format!("{}.rh", self) // short for roc host
}
// file name for a preprocessed host metadata file
// used for surgical linking
pub fn metadata_file_name(&self) -> String {
format!("metadata_{}.rm", self) // short for roc metadata
}
// file name for a stubbed app dynamic library file
pub fn stub_app_lib_file_name(&self) -> String {
format!("libapp.{}", self.dynamic_library_file_ext())
}
/// Search for a prebuilt legacy host in the platform main directory.
pub fn find_legacy_host(&self, platform_main_roc: &Path) -> Result<PathBuf, String> {
let static_library_path = platform_main_roc.with_file_name(self.prebuilt_static_library());
let static_object_path = platform_main_roc.with_file_name(self.prebuilt_static_object());
let generic_host_path: PathBuf = platform_main_roc
.with_file_name("libhost")
.with_extension(self.static_library_file_ext());
if static_library_path.exists() {
Ok(static_library_path)
} else if generic_host_path.exists() {
Ok(generic_host_path)
} else if static_object_path.exists() {
Ok(static_object_path)
} else {
Err(format!(
"Failed to find any legacy linking files; I need one of these three paths to exist:\n {}\n {}\n {}",
static_library_path.display(),
static_object_path.display(),
generic_host_path.display(),
)
.to_string())
}
}
/// Search for a prebuilt surgical host in the platform main directory.
pub fn find_surgical_host(
&self,
platform_main_roc: &Path,
) -> Result<SurgicalHostArtifacts, String> {
let surgical_metadata = platform_main_roc.with_file_name(self.metadata_file_name());
let surgical_host_path = platform_main_roc.with_file_name(self.prebuilt_surgical_host());
let generic_host_path: PathBuf = platform_main_roc.with_file_name("host.rh");
let generic_metadata: PathBuf = platform_main_roc.with_file_name("metadata_host.rm");
if generic_host_path.exists() && generic_metadata.exists() {
Ok(SurgicalHostArtifacts {
metadata: generic_metadata,
preprocessed_host: generic_host_path,
})
} else if surgical_host_path.exists() && surgical_metadata.exists() {
Ok(SurgicalHostArtifacts {
metadata: surgical_metadata,
preprocessed_host: surgical_host_path,
})
} else {
// TODO further improve the error message
Err(format!(
"Either the generic host files or the surgical host files must exist. \
File status: \
Generic host ({}): {}, \
Generic metadata ({}): {}, \
Surgical host ({}): {}, \
Surgical metadata ({}): {}",
generic_host_path.display(),
if generic_host_path.exists() {
"present"
} else {
"missing"
},
generic_metadata.display(),
if generic_metadata.exists() {
"present"
} else {
"missing"
},
surgical_host_path.display(),
if surgical_host_path.exists() {
"present"
} else {
"missing"
},
surgical_metadata.display(),
if surgical_metadata.exists() {
"present"
} else {
"missing"
}
))
}
}
}
pub enum ParseError {

View file

@ -1,7 +0,0 @@
app [main] {
pf:
"https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br",
}
main =
Stdout.line "I'm a Roc application!"

View file

@ -11,22 +11,15 @@ Full(
),
],
before_packages: [],
packages: @11-134 [
@13-132 SpaceAfter(
PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [
Newline,
],
platform_marker: None,
package_name: @17-132 PackageName(
"https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br",
),
},
[
Newline,
],
),
packages: @11-51 [
@13-49 PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [],
platform_marker: None,
package_name: @17-49 PackageName(
"generic-test-platform/main.roc",
),
},
],
old_imports: None,
old_provides_to_new_package: None,
@ -38,7 +31,7 @@ Full(
EitherIndex(2147483648),
],
regions: [
@136-183,
@53-100,
],
space_before: [
Slice { start: 0, length: 2 },
@ -54,17 +47,17 @@ Full(
type_defs: [],
value_defs: [
Body(
@136-140 Identifier {
@53-57 Identifier {
ident: "main",
},
@147-183 SpaceBefore(
@64-100 SpaceBefore(
Apply(
@147-158 Var {
@64-75 Var {
module_name: "Stdout",
ident: "line",
},
[
@159-183 Str(
@76-100 Str(
PlainLine(
"I'm a Roc application!",
),

View file

@ -1,6 +1,4 @@
app [main] { pf:
"https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br"
}
app [main] { pf: "generic-test-platform/main.roc" }
main =
Stdout.line "I'm a Roc application!"