mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-20 02:29:47 +00:00
1399 lines
47 KiB
Rust
1399 lines
47 KiB
Rust
#[macro_use]
|
||
extern crate const_format;
|
||
|
||
use build::BuiltFile;
|
||
use bumpalo::Bump;
|
||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||
use roc_build::link::{LinkType, LinkingStrategy};
|
||
use roc_collections::VecMap;
|
||
use roc_error_macros::{internal_error, user_error};
|
||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||
use roc_load::{Expectations, LoadingProblem, Threading};
|
||
use roc_module::symbol::{Interns, ModuleId};
|
||
use roc_mono::ir::OptLevel;
|
||
use roc_region::all::Region;
|
||
use roc_repl_cli::{expect_mono_module_to_dylib, ToplevelExpect};
|
||
use roc_target::TargetInfo;
|
||
use std::env;
|
||
use std::ffi::{CString, OsStr};
|
||
use std::io;
|
||
use std::os::raw::{c_char, c_int};
|
||
use std::path::{Path, PathBuf};
|
||
use std::process;
|
||
use std::time::Instant;
|
||
use target_lexicon::BinaryFormat;
|
||
use target_lexicon::{
|
||
Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture,
|
||
};
|
||
#[cfg(not(target_os = "linux"))]
|
||
use tempfile::TempDir;
|
||
|
||
pub mod build;
|
||
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_REPL: &str = "repl";
|
||
pub const CMD_EDIT: &str = "edit";
|
||
pub const CMD_DOCS: &str = "docs";
|
||
pub const CMD_CHECK: &str = "check";
|
||
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 FLAG_DEBUG: &str = "debug";
|
||
pub const FLAG_DEV: &str = "dev";
|
||
pub const FLAG_OPTIMIZE: &str = "optimize";
|
||
pub const FLAG_MAX_THREADS: &str = "max-threads";
|
||
pub const FLAG_OPT_SIZE: &str = "opt-size";
|
||
pub const FLAG_LIB: &str = "lib";
|
||
pub const FLAG_NO_LINK: &str = "no-link";
|
||
pub const FLAG_TARGET: &str = "target";
|
||
pub const FLAG_TIME: &str = "time";
|
||
pub const FLAG_LINKER: &str = "linker";
|
||
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
|
||
pub const FLAG_VALGRIND: &str = "valgrind";
|
||
pub const FLAG_CHECK: &str = "check";
|
||
pub const ROC_FILE: &str = "ROC_FILE";
|
||
pub const ROC_DIR: &str = "ROC_DIR";
|
||
pub const GLUE_FILE: &str = "GLUE_FILE";
|
||
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
|
||
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
|
||
|
||
const VERSION: &str = include_str!("../../../version.txt");
|
||
|
||
pub fn build_app<'a>() -> Command<'a> {
|
||
let flag_optimize = Arg::new(FLAG_OPTIMIZE)
|
||
.long(FLAG_OPTIMIZE)
|
||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||
.required(false);
|
||
|
||
let flag_max_threads = Arg::new(FLAG_MAX_THREADS)
|
||
.long(FLAG_MAX_THREADS)
|
||
.help("Limit the number of threads (and hence cores) used during compilation.")
|
||
.takes_value(true)
|
||
.validator(|s| s.parse::<usize>())
|
||
.required(false);
|
||
|
||
let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
|
||
.long(FLAG_OPT_SIZE)
|
||
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
|
||
.required(false);
|
||
|
||
let flag_dev = Arg::new(FLAG_DEV)
|
||
.long(FLAG_DEV)
|
||
.help("Make compilation finish as soon as possible, at the expense of runtime performance.")
|
||
.required(false);
|
||
|
||
let flag_debug = Arg::new(FLAG_DEBUG)
|
||
.long(FLAG_DEBUG)
|
||
.help("Store LLVM debug information in the generated program.")
|
||
.required(false);
|
||
|
||
let flag_valgrind = Arg::new(FLAG_VALGRIND)
|
||
.long(FLAG_VALGRIND)
|
||
.help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
|
||
.required(false);
|
||
|
||
let flag_time = Arg::new(FLAG_TIME)
|
||
.long(FLAG_TIME)
|
||
.help("Prints detailed compilation time information.")
|
||
.required(false);
|
||
|
||
let flag_linker = Arg::new(FLAG_LINKER)
|
||
.long(FLAG_LINKER)
|
||
.help("Sets which linker to use. The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
|
||
.possible_values(["surgical", "legacy"])
|
||
.required(false);
|
||
|
||
let flag_precompiled = Arg::new(FLAG_PRECOMPILED)
|
||
.long(FLAG_PRECOMPILED)
|
||
.help("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
|
||
.possible_values(["true", "false"])
|
||
.required(false);
|
||
|
||
let roc_file_to_run = Arg::new(ROC_FILE)
|
||
.help("The .roc file of an app to run")
|
||
.allow_invalid_utf8(true)
|
||
.required(false)
|
||
.default_value(DEFAULT_ROC_FILENAME);
|
||
|
||
let args_for_app = Arg::new(ARGS_FOR_APP)
|
||
.help("Arguments to pass into the app being run, e.g. `roc run -- arg1 arg2`")
|
||
.allow_invalid_utf8(true)
|
||
.multiple_values(true)
|
||
.takes_value(true)
|
||
.allow_hyphen_values(true)
|
||
.last(true);
|
||
|
||
let app = Command::new("roc")
|
||
.version(concatcp!(VERSION, "\n"))
|
||
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
|
||
.subcommand(Command::new(CMD_BUILD)
|
||
.about("Build a binary from the given .roc file, but don't run it")
|
||
.arg(flag_optimize.clone())
|
||
.arg(flag_max_threads.clone())
|
||
.arg(flag_opt_size.clone())
|
||
.arg(flag_dev.clone())
|
||
.arg(flag_debug.clone())
|
||
.arg(flag_time.clone())
|
||
.arg(flag_linker.clone())
|
||
.arg(flag_precompiled.clone())
|
||
.arg(flag_valgrind.clone())
|
||
.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),
|
||
)
|
||
.arg(
|
||
Arg::new(FLAG_LIB)
|
||
.long(FLAG_LIB)
|
||
.help("Build a C library instead of an executable.")
|
||
.required(false),
|
||
)
|
||
.arg(
|
||
Arg::new(FLAG_NO_LINK)
|
||
.long(FLAG_NO_LINK)
|
||
.help("Does not link. Instead just outputs the `.o` file")
|
||
.required(false),
|
||
)
|
||
.arg(
|
||
Arg::new(ROC_FILE)
|
||
.help("The .roc file to build")
|
||
.allow_invalid_utf8(true)
|
||
.required(false)
|
||
.default_value(DEFAULT_ROC_FILENAME),
|
||
)
|
||
)
|
||
.subcommand(Command::new(CMD_TEST)
|
||
.about("Run all top-level `expect`s in a main module and any modules it imports.")
|
||
.arg(flag_optimize.clone())
|
||
.arg(flag_max_threads.clone())
|
||
.arg(flag_opt_size.clone())
|
||
.arg(flag_dev.clone())
|
||
.arg(flag_debug.clone())
|
||
.arg(flag_time.clone())
|
||
.arg(flag_linker.clone())
|
||
.arg(flag_precompiled.clone())
|
||
.arg(flag_valgrind.clone())
|
||
.arg(
|
||
Arg::new(ROC_FILE)
|
||
.help("The .roc file for the main module")
|
||
.allow_invalid_utf8(true)
|
||
.required(false)
|
||
.default_value(DEFAULT_ROC_FILENAME)
|
||
)
|
||
.arg(args_for_app.clone())
|
||
)
|
||
.subcommand(Command::new(CMD_REPL)
|
||
.about("Launch the interactive Read Eval Print Loop (REPL)")
|
||
)
|
||
.subcommand(Command::new(CMD_RUN)
|
||
.about("Run a .roc file even if it has build errors")
|
||
.arg(flag_optimize.clone())
|
||
.arg(flag_max_threads.clone())
|
||
.arg(flag_opt_size.clone())
|
||
.arg(flag_dev.clone())
|
||
.arg(flag_debug.clone())
|
||
.arg(flag_time.clone())
|
||
.arg(flag_linker.clone())
|
||
.arg(flag_precompiled.clone())
|
||
.arg(flag_valgrind.clone())
|
||
.arg(roc_file_to_run.clone())
|
||
.arg(args_for_app.clone())
|
||
)
|
||
.subcommand(Command::new(CMD_FORMAT)
|
||
.about("Format a .roc file using standard Roc formatting")
|
||
.arg(
|
||
Arg::new(DIRECTORY_OR_FILES)
|
||
.index(1)
|
||
.multiple_values(true)
|
||
.required(false)
|
||
.allow_invalid_utf8(true))
|
||
.arg(
|
||
Arg::new(FLAG_CHECK)
|
||
.long(FLAG_CHECK)
|
||
.help("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
|
||
.required(false),
|
||
)
|
||
)
|
||
.subcommand(Command::new(CMD_VERSION)
|
||
.about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION)))
|
||
.subcommand(Command::new(CMD_CHECK)
|
||
.about("Check the code for problems, but doesn’t build or run it")
|
||
.arg(flag_time.clone())
|
||
.arg(flag_max_threads.clone())
|
||
.arg(
|
||
Arg::new(ROC_FILE)
|
||
.help("The .roc file of an app to check")
|
||
.allow_invalid_utf8(true)
|
||
.required(false)
|
||
.default_value(DEFAULT_ROC_FILENAME),
|
||
)
|
||
)
|
||
.subcommand(
|
||
Command::new(CMD_DOCS)
|
||
.about("Generate documentation for Roc modules (Work In Progress)")
|
||
.arg(Arg::new(DIRECTORY_OR_FILES)
|
||
.multiple_values(true)
|
||
.required(false)
|
||
.help("The directory or files to build documentation for")
|
||
.allow_invalid_utf8(true)
|
||
)
|
||
)
|
||
.subcommand(Command::new(CMD_GLUE)
|
||
.about("Generate glue code between a platform's Roc API and its host language.")
|
||
.arg(
|
||
Arg::new(ROC_FILE)
|
||
.help("The .roc file for the platform module")
|
||
.allow_invalid_utf8(true)
|
||
.required(true)
|
||
)
|
||
.arg(
|
||
Arg::new(GLUE_FILE)
|
||
.help("The filename for the generated glue code. Currently, this must be a .rs file because only Rust glue generation is supported so far.")
|
||
.allow_invalid_utf8(true)
|
||
.required(true)
|
||
)
|
||
)
|
||
.trailing_var_arg(true)
|
||
.arg(flag_optimize)
|
||
.arg(flag_max_threads.clone())
|
||
.arg(flag_opt_size)
|
||
.arg(flag_dev)
|
||
.arg(flag_debug)
|
||
.arg(flag_time)
|
||
.arg(flag_linker)
|
||
.arg(flag_precompiled)
|
||
.arg(flag_valgrind)
|
||
.arg(roc_file_to_run.required(false))
|
||
.arg(args_for_app);
|
||
|
||
if cfg!(feature = "editor") {
|
||
app.subcommand(
|
||
Command::new(CMD_EDIT)
|
||
.about("Launch the Roc editor (Work In Progress)")
|
||
.arg(
|
||
Arg::new(DIRECTORY_OR_FILES)
|
||
.multiple_values(true)
|
||
.required(false)
|
||
.help("(optional) The directory or files to open on launch."),
|
||
),
|
||
)
|
||
} else {
|
||
app
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, PartialEq, Eq)]
|
||
pub enum BuildConfig {
|
||
BuildOnly,
|
||
BuildAndRun,
|
||
BuildAndRunIfNoErrors,
|
||
}
|
||
|
||
pub enum FormatMode {
|
||
Format,
|
||
CheckOnly,
|
||
}
|
||
|
||
const SHM_SIZE: i64 = 1024;
|
||
|
||
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||
let start_time = Instant::now();
|
||
let arena = Bump::new();
|
||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||
let opt_level = match (
|
||
matches.is_present(FLAG_OPTIMIZE),
|
||
matches.is_present(FLAG_OPT_SIZE),
|
||
matches.is_present(FLAG_DEV),
|
||
) {
|
||
(true, false, false) => OptLevel::Optimize,
|
||
(false, true, false) => OptLevel::Size,
|
||
(false, false, true) => OptLevel::Development,
|
||
(false, false, false) => OptLevel::Normal,
|
||
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
|
||
};
|
||
|
||
let threading = match matches
|
||
.value_of(FLAG_MAX_THREADS)
|
||
.and_then(|s| s.parse::<usize>().ok())
|
||
{
|
||
None => Threading::AllAvailable,
|
||
Some(0) => user_error!("cannot build with at most 0 threads"),
|
||
Some(1) => Threading::Single,
|
||
Some(n) => Threading::AtMost(n),
|
||
};
|
||
|
||
let path = Path::new(filename);
|
||
|
||
// Spawn the root task
|
||
let path = path.canonicalize().unwrap_or_else(|err| {
|
||
use io::ErrorKind::*;
|
||
|
||
match err.kind() {
|
||
NotFound => {
|
||
let path_string = path.to_string_lossy();
|
||
|
||
// TODO these should use roc_reporting to display nicer error messages.
|
||
match matches.value_source(ROC_FILE) {
|
||
Some(ValueSource::DefaultValue) => {
|
||
eprintln!(
|
||
"\nNo `.roc` file was specified, and the current directory does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
|
||
DEFAULT_ROC_FILENAME
|
||
)
|
||
}
|
||
_ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", path_string),
|
||
}
|
||
|
||
process::exit(1);
|
||
}
|
||
_ => {
|
||
todo!("TODO Gracefully handle opening {:?} - {:?}", path, err);
|
||
}
|
||
}
|
||
});
|
||
|
||
unsafe {
|
||
let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||
let cstring = CString::new(name).unwrap();
|
||
let shared_fd =
|
||
libc::shm_open(cstring.as_ptr().cast(), libc::O_RDWR | libc::O_CREAT, 0o666);
|
||
|
||
libc::ftruncate(shared_fd, SHM_SIZE);
|
||
|
||
let _shared_ptr = libc::mmap(
|
||
std::ptr::null_mut(),
|
||
4096,
|
||
libc::PROT_WRITE,
|
||
libc::MAP_SHARED,
|
||
shared_fd,
|
||
0,
|
||
);
|
||
}
|
||
|
||
// let target_valgrind = matches.is_present(FLAG_VALGRIND);
|
||
|
||
let arena = &arena;
|
||
let target = &triple;
|
||
let opt_level = opt_level;
|
||
let target_info = TargetInfo::from(target);
|
||
|
||
// Step 1: compile the app and generate the .o file
|
||
let subs_by_module = Default::default();
|
||
|
||
let loaded = roc_load::load_and_monomorphize(
|
||
arena,
|
||
path,
|
||
subs_by_module,
|
||
target_info,
|
||
// TODO: expose this from CLI?
|
||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||
threading,
|
||
)
|
||
.unwrap();
|
||
|
||
let mut loaded = loaded;
|
||
let mut expectations = std::mem::take(&mut loaded.expectations);
|
||
let loaded = loaded;
|
||
|
||
let interns = loaded.interns.clone();
|
||
|
||
let (lib, expects) = expect_mono_module_to_dylib(
|
||
arena,
|
||
target.clone(),
|
||
loaded,
|
||
opt_level,
|
||
LlvmBackendMode::CliTest,
|
||
)
|
||
.unwrap();
|
||
|
||
let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||
let cstring = CString::new(name).unwrap();
|
||
|
||
let arena = &bumpalo::Bump::new();
|
||
let interns = arena.alloc(interns);
|
||
|
||
use roc_gen_llvm::try_run_jit_function;
|
||
|
||
let mut failed = 0;
|
||
let mut passed = 0;
|
||
|
||
unsafe {
|
||
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), libc::O_RDWR, 0o666);
|
||
|
||
libc::ftruncate(shared_fd, SHM_SIZE);
|
||
|
||
let shared_ptr = libc::mmap(
|
||
std::ptr::null_mut(),
|
||
SHM_SIZE as usize,
|
||
libc::PROT_READ | libc::PROT_WRITE,
|
||
libc::MAP_SHARED,
|
||
shared_fd,
|
||
0,
|
||
);
|
||
|
||
for expect in expects {
|
||
let sequence = ExpectSequence::new(shared_ptr.cast());
|
||
|
||
let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
|
||
|
||
let shared_memory_ptr: *const u8 = shared_ptr.cast();
|
||
|
||
if let Err(roc_panic_message) = result {
|
||
failed += 1;
|
||
|
||
render_expect_panic(
|
||
arena,
|
||
expect,
|
||
&roc_panic_message,
|
||
&mut expectations,
|
||
interns,
|
||
);
|
||
println!();
|
||
} else if sequence.count_failures() > 0 {
|
||
failed += 1;
|
||
|
||
let mut offset = ExpectSequence::START_OFFSET;
|
||
|
||
for _ in 0..sequence.count_failures() {
|
||
offset += render_expect_failure(
|
||
arena,
|
||
Some(expect),
|
||
&mut expectations,
|
||
interns,
|
||
shared_memory_ptr,
|
||
offset,
|
||
);
|
||
|
||
println!();
|
||
}
|
||
} else {
|
||
passed += 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
let total_time = start_time.elapsed();
|
||
|
||
if failed == 0 && passed == 0 {
|
||
// TODO print this in a more nicely formatted way!
|
||
println!("No expectations were found.");
|
||
|
||
// If no tests ran, treat that as an error. This is perhaps
|
||
// briefly annoying at the very beginning of a project when
|
||
// you actually have zero tests, but it can save you from
|
||
// having a change to your CI script accidentally stop
|
||
// running tests altogether!
|
||
Ok(2)
|
||
} else {
|
||
let failed_color = if failed == 0 {
|
||
32 // green
|
||
} else {
|
||
31 // red
|
||
};
|
||
|
||
println!(
|
||
"\x1B[{failed_color}m{failed}\x1B[39m failed and \x1B[32m{passed}\x1B[39m passed in {} ms.\n",
|
||
total_time.as_millis(),
|
||
);
|
||
|
||
Ok((failed > 0) as i32)
|
||
}
|
||
}
|
||
|
||
struct ExpectSequence {
|
||
ptr: *const u8,
|
||
}
|
||
|
||
impl ExpectSequence {
|
||
const START_OFFSET: usize = 16;
|
||
|
||
const COUNT_INDEX: usize = 0;
|
||
const OFFSET_INDEX: usize = 1;
|
||
|
||
fn new(ptr: *mut u8) -> Self {
|
||
unsafe {
|
||
libc::memset(ptr.cast(), 0, SHM_SIZE as _);
|
||
*((ptr as *mut usize).add(Self::OFFSET_INDEX)) = Self::START_OFFSET;
|
||
}
|
||
|
||
Self {
|
||
ptr: ptr as *const u8,
|
||
}
|
||
}
|
||
|
||
fn count_failures(&self) -> usize {
|
||
unsafe { *(self.ptr as *const usize).add(Self::COUNT_INDEX) }
|
||
}
|
||
}
|
||
|
||
pub fn build(
|
||
matches: &ArgMatches,
|
||
config: BuildConfig,
|
||
triple: Triple,
|
||
link_type: LinkType,
|
||
) -> io::Result<i32> {
|
||
use build::build_file;
|
||
use BuildConfig::*;
|
||
|
||
let arena = Bump::new();
|
||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||
let opt_level = match (
|
||
matches.is_present(FLAG_OPTIMIZE),
|
||
matches.is_present(FLAG_OPT_SIZE),
|
||
matches.is_present(FLAG_DEV),
|
||
) {
|
||
(true, false, false) => OptLevel::Optimize,
|
||
(false, true, false) => OptLevel::Size,
|
||
(false, false, true) => OptLevel::Development,
|
||
(false, false, false) => OptLevel::Normal,
|
||
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
|
||
};
|
||
let emit_debug_info = matches.is_present(FLAG_DEBUG);
|
||
let emit_timings = matches.is_present(FLAG_TIME);
|
||
|
||
let threading = match matches
|
||
.value_of(FLAG_MAX_THREADS)
|
||
.and_then(|s| s.parse::<usize>().ok())
|
||
{
|
||
None => Threading::AllAvailable,
|
||
Some(0) => user_error!("cannot build with at most 0 threads"),
|
||
Some(1) => Threading::Single,
|
||
Some(n) => Threading::AtMost(n),
|
||
};
|
||
|
||
let wasm_dev_backend = matches!(opt_level, OptLevel::Development)
|
||
&& matches!(triple.architecture, Architecture::Wasm32);
|
||
|
||
let linking_strategy = if wasm_dev_backend {
|
||
LinkingStrategy::Additive
|
||
} else if !roc_linker::supported(link_type, &triple)
|
||
|| matches.value_of(FLAG_LINKER) == Some("legacy")
|
||
{
|
||
LinkingStrategy::Legacy
|
||
} else {
|
||
LinkingStrategy::Surgical
|
||
};
|
||
|
||
let precompiled = if matches.is_present(FLAG_PRECOMPILED) {
|
||
matches.value_of(FLAG_PRECOMPILED) == Some("true")
|
||
} else {
|
||
// When compiling for a different target, default to assuming a precompiled host.
|
||
// Otherwise compilation would most likely fail because many toolchains assume you're compiling for the host
|
||
// We make an exception for Wasm, because cross-compiling is the norm in that case.
|
||
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
|
||
};
|
||
let path = Path::new(filename);
|
||
|
||
// Spawn the root task
|
||
let path = path.canonicalize().unwrap_or_else(|err| {
|
||
use io::ErrorKind::*;
|
||
|
||
match err.kind() {
|
||
NotFound => {
|
||
let path_string = path.to_string_lossy();
|
||
|
||
// TODO these should use roc_reporting to display nicer error messages.
|
||
match matches.value_source(ROC_FILE) {
|
||
Some(ValueSource::DefaultValue) => {
|
||
eprintln!(
|
||
"\nNo `.roc` file was specified, and the current directory does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
|
||
DEFAULT_ROC_FILENAME
|
||
)
|
||
}
|
||
_ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", path_string),
|
||
}
|
||
|
||
process::exit(1);
|
||
}
|
||
_ => {
|
||
todo!("TODO Gracefully handle opening {:?} - {:?}", path, err);
|
||
}
|
||
}
|
||
});
|
||
|
||
let target_valgrind = matches.is_present(FLAG_VALGRIND);
|
||
let res_binary_path = build_file(
|
||
&arena,
|
||
&triple,
|
||
path,
|
||
opt_level,
|
||
emit_debug_info,
|
||
emit_timings,
|
||
link_type,
|
||
linking_strategy,
|
||
precompiled,
|
||
target_valgrind,
|
||
threading,
|
||
);
|
||
|
||
match res_binary_path {
|
||
Ok(BuiltFile {
|
||
binary_path,
|
||
problems,
|
||
total_time,
|
||
expectations,
|
||
interns,
|
||
}) => {
|
||
match config {
|
||
BuildOnly => {
|
||
// If possible, report the generated executable name relative to the current dir.
|
||
let generated_filename = binary_path
|
||
.strip_prefix(env::current_dir().unwrap())
|
||
.unwrap_or(&binary_path);
|
||
|
||
// No need to waste time freeing this memory,
|
||
// since the process is about to exit anyway.
|
||
std::mem::forget(arena);
|
||
|
||
println!(
|
||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while successfully building:\n\n {}",
|
||
if problems.errors == 0 {
|
||
32 // green
|
||
} else {
|
||
33 // yellow
|
||
},
|
||
problems.errors,
|
||
if problems.errors == 1 {
|
||
"error"
|
||
} else {
|
||
"errors"
|
||
},
|
||
if problems.warnings == 0 {
|
||
32 // green
|
||
} else {
|
||
33 // yellow
|
||
},
|
||
problems.warnings,
|
||
if problems.warnings == 1 {
|
||
"warning"
|
||
} else {
|
||
"warnings"
|
||
},
|
||
total_time.as_millis(),
|
||
generated_filename.to_str().unwrap()
|
||
);
|
||
|
||
// Return a nonzero exit code if there were problems
|
||
Ok(problems.exit_code())
|
||
}
|
||
BuildAndRun => {
|
||
if problems.errors > 0 || problems.warnings > 0 {
|
||
println!(
|
||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
||
if problems.errors == 0 {
|
||
32 // green
|
||
} else {
|
||
33 // yellow
|
||
},
|
||
problems.errors,
|
||
if problems.errors == 1 {
|
||
"error"
|
||
} else {
|
||
"errors"
|
||
},
|
||
if problems.warnings == 0 {
|
||
32 // green
|
||
} else {
|
||
33 // yellow
|
||
},
|
||
problems.warnings,
|
||
if problems.warnings == 1 {
|
||
"warning"
|
||
} else {
|
||
"warnings"
|
||
},
|
||
total_time.as_millis(),
|
||
"─".repeat(80)
|
||
);
|
||
}
|
||
|
||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||
|
||
let mut bytes = std::fs::read(&binary_path).unwrap();
|
||
|
||
let x = roc_run(
|
||
arena,
|
||
opt_level,
|
||
triple,
|
||
args,
|
||
&mut bytes,
|
||
expectations,
|
||
interns,
|
||
);
|
||
std::mem::forget(bytes);
|
||
x
|
||
}
|
||
BuildAndRunIfNoErrors => {
|
||
if problems.errors == 0 {
|
||
if problems.warnings > 0 {
|
||
println!(
|
||
"\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||
problems.warnings,
|
||
if problems.warnings == 1 {
|
||
"warning"
|
||
} else {
|
||
"warnings"
|
||
},
|
||
total_time.as_millis(),
|
||
"─".repeat(80)
|
||
);
|
||
}
|
||
|
||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||
|
||
let mut bytes = std::fs::read(&binary_path).unwrap();
|
||
|
||
let x = roc_run(
|
||
arena,
|
||
opt_level,
|
||
triple,
|
||
args,
|
||
&mut bytes,
|
||
expectations,
|
||
interns,
|
||
);
|
||
std::mem::forget(bytes);
|
||
x
|
||
} else {
|
||
let mut output = format!(
|
||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with \x1B[32mroc run",
|
||
if problems.errors == 0 {
|
||
32 // green
|
||
} else {
|
||
33 // yellow
|
||
},
|
||
problems.errors,
|
||
if problems.errors == 1 {
|
||
"error"
|
||
} else {
|
||
"errors"
|
||
},
|
||
if problems.warnings == 0 {
|
||
32 // green
|
||
} else {
|
||
33 // yellow
|
||
},
|
||
problems.warnings,
|
||
if problems.warnings == 1 {
|
||
"warning"
|
||
} else {
|
||
"warnings"
|
||
},
|
||
total_time.as_millis(),
|
||
);
|
||
// If you're running "main.roc" then you can just do `roc run`
|
||
// to re-run the program.
|
||
if filename != DEFAULT_ROC_FILENAME {
|
||
output.push(' ');
|
||
output.push_str(&filename.to_string_lossy());
|
||
}
|
||
|
||
println!("{}\x1B[39m", output);
|
||
|
||
Ok(problems.exit_code())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Err(LoadingProblem::FormattedReport(report)) => {
|
||
print!("{}", report);
|
||
|
||
Ok(1)
|
||
}
|
||
Err(other) => {
|
||
panic!("build_file failed with error:\n{:?}", other);
|
||
}
|
||
}
|
||
}
|
||
|
||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||
opt_level: OptLevel,
|
||
triple: Triple,
|
||
args: I,
|
||
binary_bytes: &mut [u8],
|
||
expectations: VecMap<ModuleId, Expectations>,
|
||
interns: Interns,
|
||
) -> io::Result<i32> {
|
||
match triple.architecture {
|
||
Architecture::Wasm32 => {
|
||
let executable = roc_run_executable_file_path(binary_bytes)?;
|
||
let path = executable.as_path();
|
||
// If possible, report the generated executable name relative to the current dir.
|
||
let generated_filename = path
|
||
.strip_prefix(env::current_dir().unwrap())
|
||
.unwrap_or(path);
|
||
|
||
// No need to waste time freeing this memory,
|
||
// since the process is about to exit anyway.
|
||
std::mem::forget(arena);
|
||
|
||
if cfg!(target_family = "unix") {
|
||
use std::os::unix::ffi::OsStrExt;
|
||
|
||
run_with_wasmer(
|
||
generated_filename,
|
||
args.into_iter().map(|os_str| os_str.as_bytes()),
|
||
);
|
||
} else {
|
||
run_with_wasmer(
|
||
generated_filename,
|
||
args.into_iter().map(|os_str| {
|
||
os_str.to_str().expect(
|
||
"Roc does not currently support passing non-UTF8 arguments to Wasmer.",
|
||
)
|
||
}),
|
||
);
|
||
}
|
||
|
||
Ok(0)
|
||
}
|
||
_ => roc_run_native(arena, opt_level, args, binary_bytes, expectations, interns),
|
||
}
|
||
}
|
||
|
||
fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||
arena: &'a Bump,
|
||
executable: &ExecutableFile,
|
||
args: I,
|
||
) -> (
|
||
bumpalo::collections::Vec<'a, CString>,
|
||
bumpalo::collections::Vec<'a, CString>,
|
||
) {
|
||
use bumpalo::collections::CollectIn;
|
||
use std::os::unix::ffi::OsStrExt;
|
||
|
||
let path = executable.as_path();
|
||
let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||
|
||
// argv is an array of pointers to strings passed to the new program
|
||
// as its command-line arguments. By convention, the first of these
|
||
// strings (i.e., argv[0]) should contain the filename associated
|
||
// with the file being executed. The argv array must be terminated
|
||
// by a NULL pointer. (Thus, in the new program, argv[argc] will be NULL.)
|
||
let it = args
|
||
.into_iter()
|
||
.map(|x| CString::new(x.as_ref().as_bytes()).unwrap());
|
||
|
||
let argv_cstrings: bumpalo::collections::Vec<CString> =
|
||
std::iter::once(path_cstring).chain(it).collect_in(arena);
|
||
|
||
// envp is an array of pointers to strings, conventionally of the
|
||
// form key=value, which are passed as the environment of the new
|
||
// program. The envp array must be terminated by a NULL pointer.
|
||
let envp_cstrings: bumpalo::collections::Vec<CString> = std::env::vars_os()
|
||
.flat_map(|(k, v)| {
|
||
[
|
||
CString::new(k.as_bytes()).unwrap(),
|
||
CString::new(v.as_bytes()).unwrap(),
|
||
]
|
||
})
|
||
.collect_in(arena);
|
||
|
||
(argv_cstrings, envp_cstrings)
|
||
}
|
||
|
||
/// Run on the native OS (not on wasm)
|
||
#[cfg(target_family = "unix")]
|
||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||
arena: Bump,
|
||
opt_level: OptLevel,
|
||
args: I,
|
||
binary_bytes: &mut [u8],
|
||
expectations: VecMap<ModuleId, Expectations>,
|
||
interns: Interns,
|
||
) -> std::io::Result<i32> {
|
||
use bumpalo::collections::CollectIn;
|
||
|
||
unsafe {
|
||
let executable = roc_run_executable_file_path(binary_bytes)?;
|
||
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
|
||
|
||
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
|
||
.iter()
|
||
.map(|s| s.as_ptr())
|
||
.chain([std::ptr::null()])
|
||
.collect_in(&arena);
|
||
|
||
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
||
.iter()
|
||
.map(|s| s.as_ptr())
|
||
.chain([std::ptr::null()])
|
||
.collect_in(&arena);
|
||
|
||
match opt_level {
|
||
OptLevel::Development => {
|
||
roc_run_native_debug(executable, &argv, &envp, expectations, interns)
|
||
}
|
||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
||
roc_run_native_fast(executable, &argv, &envp);
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(1)
|
||
}
|
||
|
||
unsafe fn roc_run_native_fast(
|
||
executable: ExecutableFile,
|
||
argv: &[*const c_char],
|
||
envp: &[*const c_char],
|
||
) {
|
||
if executable.execve(argv, envp) != 0 {
|
||
internal_error!(
|
||
"libc::{}({:?}, ..., ...) failed: {:?}",
|
||
ExecutableFile::SYSCALL,
|
||
executable.as_path(),
|
||
errno::errno()
|
||
);
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
enum ExecutableFile {
|
||
#[cfg(target_os = "linux")]
|
||
MemFd(libc::c_int, PathBuf),
|
||
#[cfg(not(target_os = "linux"))]
|
||
OnDisk(TempDir, PathBuf),
|
||
}
|
||
|
||
impl ExecutableFile {
|
||
#[cfg(target_os = "linux")]
|
||
const SYSCALL: &'static str = "fexecve";
|
||
|
||
#[cfg(not(target_os = "linux"))]
|
||
const SYSCALL: &'static str = "execve";
|
||
|
||
fn as_path(&self) -> &Path {
|
||
match self {
|
||
#[cfg(target_os = "linux")]
|
||
ExecutableFile::MemFd(_, path_buf) => path_buf.as_ref(),
|
||
#[cfg(not(target_os = "linux"))]
|
||
ExecutableFile::OnDisk(_, path_buf) => path_buf.as_ref(),
|
||
}
|
||
}
|
||
|
||
unsafe fn execve(&self, argv: &[*const c_char], envp: &[*const c_char]) -> c_int {
|
||
match self {
|
||
#[cfg(target_os = "linux")]
|
||
ExecutableFile::MemFd(fd, _path) => libc::fexecve(*fd, argv.as_ptr(), envp.as_ptr()),
|
||
|
||
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
|
||
ExecutableFile::OnDisk(_, path) => {
|
||
use std::os::unix::ffi::OsStrExt;
|
||
|
||
let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||
libc::execve(path_cstring.as_ptr().cast(), argv.as_ptr(), envp.as_ptr())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// with Expect
|
||
unsafe fn roc_run_native_debug(
|
||
executable: ExecutableFile,
|
||
argv: &[*const c_char],
|
||
envp: &[*const c_char],
|
||
mut expectations: VecMap<ModuleId, Expectations>,
|
||
interns: Interns,
|
||
) {
|
||
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
|
||
|
||
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
|
||
|
||
match libc::fork() {
|
||
0 => {
|
||
// we are the child
|
||
|
||
if executable.execve(argv, envp) < 0 {
|
||
// Get the current value of errno
|
||
let e = errno::errno();
|
||
|
||
// Extract the error code as an i32
|
||
let code = e.0;
|
||
|
||
// Display a human-friendly error message
|
||
println!("💥 Error {}: {}", code, e);
|
||
}
|
||
}
|
||
-1 => {
|
||
// something failed
|
||
|
||
// Get the current value of errno
|
||
let e = errno::errno();
|
||
|
||
// Extract the error code as an i32
|
||
let code = e.0;
|
||
|
||
// Display a human-friendly error message
|
||
println!("Error {}: {}", code, e);
|
||
|
||
process::exit(1)
|
||
}
|
||
1.. => {
|
||
let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||
let cstring = CString::new(name).unwrap();
|
||
|
||
let arena = &bumpalo::Bump::new();
|
||
let interns = arena.alloc(interns);
|
||
|
||
for sig in &mut signals {
|
||
match sig {
|
||
SIGCHLD => {
|
||
// clean up
|
||
libc::shm_unlink(cstring.as_ptr().cast());
|
||
|
||
// done!
|
||
process::exit(0);
|
||
}
|
||
SIGUSR1 => {
|
||
// this is the signal we use for an expect failure. Let's see what the child told us
|
||
let shared_fd =
|
||
libc::shm_open(cstring.as_ptr().cast(), libc::O_RDONLY, 0o666);
|
||
|
||
libc::ftruncate(shared_fd, SHM_SIZE);
|
||
|
||
let shared_ptr = libc::mmap(
|
||
std::ptr::null_mut(),
|
||
SHM_SIZE as usize,
|
||
libc::PROT_READ,
|
||
libc::MAP_SHARED,
|
||
shared_fd,
|
||
0,
|
||
);
|
||
|
||
let shared_memory_ptr: *const u8 = shared_ptr.cast();
|
||
|
||
render_expect_failure(
|
||
arena,
|
||
None,
|
||
&mut expectations,
|
||
interns,
|
||
shared_memory_ptr,
|
||
ExpectSequence::START_OFFSET,
|
||
);
|
||
}
|
||
_ => println!("received signal {}", sig),
|
||
}
|
||
}
|
||
}
|
||
_ => unreachable!(),
|
||
}
|
||
}
|
||
|
||
fn render_expect_panic<'a>(
|
||
arena: &'a Bump,
|
||
expect: ToplevelExpect,
|
||
message: &str,
|
||
expectations: &mut VecMap<ModuleId, Expectations>,
|
||
interns: &'a Interns,
|
||
) {
|
||
let module_id = expect.symbol.module_id();
|
||
let data = expectations.get_mut(&module_id).unwrap();
|
||
|
||
let path = &data.path;
|
||
let filename = data.path.to_owned();
|
||
let source = std::fs::read_to_string(path).unwrap();
|
||
|
||
use roc_reporting::error::expect::Renderer;
|
||
|
||
let renderer = Renderer::new(arena, interns, module_id, filename, &source);
|
||
let buf = renderer.render_panic(message, expect.region);
|
||
println!("{}", buf);
|
||
}
|
||
|
||
struct ExpectFrame {
|
||
region: Region,
|
||
module_id: ModuleId,
|
||
start_offset: usize,
|
||
}
|
||
|
||
impl ExpectFrame {
|
||
fn at_offset(start: *const u8, offset: usize) -> Self {
|
||
let region_bytes: [u8; 8] = unsafe { *(start.add(offset).cast()) };
|
||
let region: Region = unsafe { std::mem::transmute(region_bytes) };
|
||
|
||
let module_id_bytes: [u8; 4] = unsafe { *(start.add(offset + 8).cast()) };
|
||
let module_id: ModuleId = unsafe { std::mem::transmute(module_id_bytes) };
|
||
|
||
// skip to frame, 8 bytes for region, 4 for module id
|
||
let start_offset = offset + 12;
|
||
|
||
Self {
|
||
region,
|
||
module_id,
|
||
start_offset,
|
||
}
|
||
}
|
||
}
|
||
|
||
fn render_expect_failure<'a>(
|
||
arena: &'a Bump,
|
||
expect: Option<ToplevelExpect>,
|
||
expectations: &mut VecMap<ModuleId, Expectations>,
|
||
interns: &'a Interns,
|
||
start: *const u8,
|
||
offset: usize,
|
||
) -> usize {
|
||
// we always run programs as the host
|
||
let target_info = (&target_lexicon::Triple::host()).into();
|
||
|
||
let frame = ExpectFrame::at_offset(start, offset);
|
||
let module_id = frame.module_id;
|
||
|
||
let failure_region = frame.region;
|
||
let expect_region = expect.map(|e| e.region);
|
||
|
||
let data = expectations.get_mut(&module_id).unwrap();
|
||
let filename = data.path.to_owned();
|
||
let source = std::fs::read_to_string(&data.path).unwrap();
|
||
|
||
let current = match data.expectations.get(&failure_region) {
|
||
None => panic!("region not in list of expects"),
|
||
Some(current) => current,
|
||
};
|
||
let subs = arena.alloc(&mut data.subs);
|
||
|
||
let (symbols, variables): (Vec<_>, Vec<_>) = current.iter().map(|(a, b)| (*a, *b)).unzip();
|
||
|
||
let (offset, expressions) = roc_repl_expect::get_values(
|
||
target_info,
|
||
arena,
|
||
subs,
|
||
interns,
|
||
start,
|
||
frame.start_offset,
|
||
&variables,
|
||
)
|
||
.unwrap();
|
||
|
||
use roc_reporting::error::expect::Renderer;
|
||
|
||
let renderer = Renderer::new(arena, interns, module_id, filename, &source);
|
||
let buf = renderer.render_failure(
|
||
subs,
|
||
&symbols,
|
||
&variables,
|
||
&expressions,
|
||
expect_region,
|
||
failure_region,
|
||
);
|
||
|
||
println!("{}", buf);
|
||
|
||
offset
|
||
}
|
||
|
||
#[cfg(target_os = "linux")]
|
||
fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<ExecutableFile> {
|
||
// on linux, we use the `memfd_create` function to create an in-memory anonymous file.
|
||
let flags = 0;
|
||
let anonymous_file_name = "roc_file_descriptor\0";
|
||
let fd = unsafe { libc::memfd_create(anonymous_file_name.as_ptr().cast(), flags) };
|
||
|
||
if fd == 0 {
|
||
internal_error!(
|
||
"libc::memfd_create({:?}, {}) failed: file descriptor is 0",
|
||
anonymous_file_name,
|
||
flags
|
||
);
|
||
}
|
||
|
||
let path = PathBuf::from(format!("/proc/self/fd/{}", fd));
|
||
|
||
std::fs::write(&path, binary_bytes)?;
|
||
|
||
Ok(ExecutableFile::MemFd(fd, path))
|
||
}
|
||
|
||
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
|
||
fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<ExecutableFile> {
|
||
use std::fs::OpenOptions;
|
||
use std::io::Write;
|
||
use std::os::unix::fs::OpenOptionsExt;
|
||
|
||
let temp_dir = tempfile::tempdir()?;
|
||
|
||
// We have not found a way to use a virtual file on non-Linux OSes.
|
||
// Hence we fall back to just writing the file to the file system, and using that file.
|
||
let app_path_buf = temp_dir.path().join("roc_app_binary");
|
||
let mut file = OpenOptions::new()
|
||
.create(true)
|
||
.write(true)
|
||
.mode(0o777) // create the file as executable
|
||
.open(&app_path_buf)?;
|
||
|
||
file.write_all(binary_bytes)?;
|
||
|
||
// We store the TempDir in this variant alongside the path to the executable,
|
||
// so that the TempDir doesn't get dropped until after we're done with the path.
|
||
// If we didn't do that, then the tempdir would potentially get deleted by the
|
||
// TempDir's Drop impl before the file had been executed.
|
||
Ok(ExecutableFile::OnDisk(temp_dir, app_path_buf))
|
||
}
|
||
|
||
/// Run on the native OS (not on wasm)
|
||
#[cfg(not(target_family = "unix"))]
|
||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||
_arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||
_args: I,
|
||
_binary_bytes: &mut [u8],
|
||
_expectations: VecMap<ModuleId, Expectations>,
|
||
_interns: Interns,
|
||
) -> io::Result<i32> {
|
||
todo!("TODO support running roc programs on non-UNIX targets");
|
||
// let mut cmd = std::process::Command::new(&binary_path);
|
||
|
||
// // Run the compiled app
|
||
// let exit_status = cmd
|
||
// .spawn()
|
||
// .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
|
||
// .wait()
|
||
// .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
|
||
|
||
// // `roc [FILE]` exits with the same status code as the app it ran.
|
||
// //
|
||
// // If you want to know whether there were compilation problems
|
||
// // via status code, use either `roc build` or `roc check` instead!
|
||
// match exit_status.code() {
|
||
// Some(code) => Ok(code),
|
||
// None => {
|
||
// todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
|
||
// }
|
||
// }
|
||
}
|
||
|
||
#[cfg(feature = "run-wasm32")]
|
||
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(wasm_path: &std::path::Path, args: I) {
|
||
use wasmer::{Instance, Module, Store};
|
||
|
||
let store = Store::default();
|
||
let module = Module::from_file(&store, &wasm_path).unwrap();
|
||
|
||
// First, we create the `WasiEnv`
|
||
use wasmer_wasi::WasiState;
|
||
let mut wasi_env = WasiState::new("hello").args(args).finalize().unwrap();
|
||
|
||
// Then, we get the import object related to our WASI
|
||
// and attach it to the Wasm instance.
|
||
let import_object = wasi_env.import_object(&module).unwrap();
|
||
|
||
let instance = Instance::new(&module, &import_object).unwrap();
|
||
|
||
let start = instance.exports.get_function("_start").unwrap();
|
||
|
||
use wasmer_wasi::WasiError;
|
||
match start.call(&[]) {
|
||
Ok(_) => {}
|
||
Err(e) => match e.downcast::<WasiError>() {
|
||
Ok(WasiError::Exit(0)) => {
|
||
// we run the `_start` function, so exit(0) is expected
|
||
}
|
||
other => panic!("Wasmer error: {:?}", other),
|
||
},
|
||
}
|
||
}
|
||
|
||
#[cfg(not(feature = "run-wasm32"))]
|
||
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) {
|
||
println!("Running wasm files is not supported on this target.");
|
||
}
|
||
|
||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||
pub enum Target {
|
||
System,
|
||
Linux32,
|
||
Linux64,
|
||
Wasm32,
|
||
}
|
||
|
||
impl Default for Target {
|
||
fn default() -> Self {
|
||
Target::System
|
||
}
|
||
}
|
||
|
||
impl Target {
|
||
const fn as_str(&self) -> &'static str {
|
||
use Target::*;
|
||
|
||
match self {
|
||
System => "system",
|
||
Linux32 => "linux32",
|
||
Linux64 => "linux64",
|
||
Wasm32 => "wasm32",
|
||
}
|
||
}
|
||
|
||
/// NOTE keep up to date!
|
||
const OPTIONS: &'static [&'static str] = &[
|
||
Target::System.as_str(),
|
||
Target::Linux32.as_str(),
|
||
Target::Linux64.as_str(),
|
||
Target::Wasm32.as_str(),
|
||
];
|
||
|
||
pub fn to_triple(self) -> Triple {
|
||
use Target::*;
|
||
|
||
match self {
|
||
System => Triple::host(),
|
||
Linux32 => Triple {
|
||
architecture: Architecture::X86_32(X86_32Architecture::I386),
|
||
vendor: Vendor::Unknown,
|
||
operating_system: OperatingSystem::Linux,
|
||
environment: Environment::Musl,
|
||
binary_format: BinaryFormat::Elf,
|
||
},
|
||
Linux64 => Triple {
|
||
architecture: Architecture::X86_64,
|
||
vendor: Vendor::Unknown,
|
||
operating_system: OperatingSystem::Linux,
|
||
environment: Environment::Musl,
|
||
binary_format: BinaryFormat::Elf,
|
||
},
|
||
Wasm32 => Triple {
|
||
architecture: Architecture::Wasm32,
|
||
vendor: Vendor::Unknown,
|
||
operating_system: OperatingSystem::Unknown,
|
||
environment: Environment::Unknown,
|
||
binary_format: BinaryFormat::Wasm,
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<&Target> for Triple {
|
||
fn from(target: &Target) -> Self {
|
||
target.to_triple()
|
||
}
|
||
}
|
||
|
||
impl std::fmt::Display for Target {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||
write!(f, "{}", self.as_str())
|
||
}
|
||
}
|
||
|
||
impl std::str::FromStr for Target {
|
||
type Err = String;
|
||
|
||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||
match string {
|
||
"system" => Ok(Target::System),
|
||
"linux32" => Ok(Target::Linux32),
|
||
"linux64" => Ok(Target::Linux64),
|
||
"wasm32" => Ok(Target::Wasm32),
|
||
_ => Err(format!("Roc does not know how to compile to {}", string)),
|
||
}
|
||
}
|
||
}
|