diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 9318e1094c6..d74303e3f64 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -150,19 +150,25 @@ jobs: run: | make build-wapm if: needs.setup.outputs.DOING_RELEASE == '1' + - name: Package Wasmer for integration tests + run: make package-without-wapm-for-integration-tests + if: needs.setup.outputs.DOING_RELEASE != '1' + - name: Package Wasmer + run: | + make package + if: needs.setup.outputs.DOING_RELEASE == '1' - name: Run integration tests (Windows) shell: cmd run: | call refreshenv + set WASMER_DIR=%CD%\package make test-integration if: matrix.run_integration_tests && matrix.os == 'windows-latest' - name: Run integration tests (Unix) - run: make test-integration - if: matrix.run_integration_tests && matrix.os != 'windows-latest' - - name: Package Wasmer run: | - make package - if: needs.setup.outputs.DOING_RELEASE == '1' + export WASMER_DIR=`pwd`/package + make test-integration + if: matrix.run_integration_tests && matrix.os != 'windows-latest' - name: Upload Artifacts uses: actions/upload-artifact@v2 if: needs.setup.outputs.DOING_RELEASE == '1' diff --git a/Cargo.lock b/Cargo.lock index 2aef3c278fd..76e9d096e8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2215,6 +2215,7 @@ dependencies = [ "fern", "log", "structopt", + "tempfile", "wasmer", "wasmer-cache", "wasmer-compiler", diff --git a/Makefile b/Makefile index 2155fdb1129..7b7f944c511 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,7 @@ package-capi: mkdir -p "package/lib" cp lib/c-api/wasmer.h* package/include cp lib/c-api/wasmer_wasm.h* package/include + cp lib/c-api/wasm.h* package/include cp lib/c-api/doc/deprecated/index.md package/include/README.md ifeq ($(OS), Windows_NT) cp target/release/wasmer_c_api.dll package/lib @@ -223,6 +224,9 @@ else cp ./wasmer.tar.gz ./dist/$(shell ./scripts/binary-name.sh) endif +# command for simulating installing Wasmer without wapm. +package-without-wapm-for-integration-tests: package-wasmer package-capi + ################# # Miscellaneous # ################# diff --git a/lib/c-api/build.rs b/lib/c-api/build.rs index e43285a6263..d0769dda3bd 100644 --- a/lib/c-api/build.rs +++ b/lib/c-api/build.rs @@ -361,6 +361,8 @@ fn exclude_items_from_wasm_c_api(builder: Builder) -> Builder { builder .exclude_item("wasi_config_arg") .exclude_item("wasi_config_env") + .exclude_item("wasi_config_mapdir") + .exclude_item("wasi_config_preopen_dir") .exclude_item("wasi_config_inherit_stderr") .exclude_item("wasi_config_inherit_stdin") .exclude_item("wasi_config_inherit_stdout") diff --git a/lib/c-api/src/wasm_c_api/wasi/mod.rs b/lib/c-api/src/wasm_c_api/wasi/mod.rs index cf0a08b73d4..fb0047c7379 100644 --- a/lib/c-api/src/wasm_c_api/wasi/mod.rs +++ b/lib/c-api/src/wasm_c_api/wasi/mod.rs @@ -75,6 +75,63 @@ pub unsafe extern "C" fn wasi_config_arg(config: &mut wasi_config_t, arg: *const config.state_builder.arg(arg_bytes); } +#[no_mangle] +pub unsafe extern "C" fn wasi_config_preopen_dir( + config: &mut wasi_config_t, + dir: *const c_char, +) -> bool { + let dir_cstr = CStr::from_ptr(dir); + let dir_bytes = dir_cstr.to_bytes(); + let dir_str = match std::str::from_utf8(dir_bytes) { + Ok(dir_str) => dir_str, + Err(e) => { + update_last_error(e); + return false; + } + }; + + if let Err(e) = config.state_builder.preopen_dir(dir_str) { + update_last_error(e); + return false; + } + + true +} + +#[no_mangle] +pub unsafe extern "C" fn wasi_config_mapdir( + config: &mut wasi_config_t, + alias: *const c_char, + dir: *const c_char, +) -> bool { + let alias_cstr = CStr::from_ptr(alias); + let alias_bytes = alias_cstr.to_bytes(); + let alias_str = match std::str::from_utf8(alias_bytes) { + Ok(alias_str) => alias_str, + Err(e) => { + update_last_error(e); + return false; + } + }; + + let dir_cstr = CStr::from_ptr(dir); + let dir_bytes = dir_cstr.to_bytes(); + let dir_str = match std::str::from_utf8(dir_bytes) { + Ok(dir_str) => dir_str, + Err(e) => { + update_last_error(e); + return false; + } + }; + + if let Err(e) = config.state_builder.map_dir(alias_str, dir_str) { + update_last_error(e); + return false; + } + + true +} + #[no_mangle] pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) { config.inherit_stdout = true; diff --git a/lib/c-api/wasmer_wasm.h b/lib/c-api/wasmer_wasm.h index 6dffd665d45..a3f6dfa7487 100644 --- a/lib/c-api/wasmer_wasm.h +++ b/lib/c-api/wasmer_wasm.h @@ -101,10 +101,18 @@ void wasi_config_inherit_stdin(wasi_config_t *config); void wasi_config_inherit_stdout(wasi_config_t *config); #endif +#if defined(WASMER_WASI_ENABLED) +bool wasi_config_mapdir(wasi_config_t *config, const char *alias, const char *dir); +#endif + #if defined(WASMER_WASI_ENABLED) wasi_config_t *wasi_config_new(const char *program_name); #endif +#if defined(WASMER_WASI_ENABLED) +bool wasi_config_preopen_dir(wasi_config_t *config, const char *dir); +#endif + #if defined(WASMER_WASI_ENABLED) void wasi_env_delete(wasi_env_t *_state); #endif diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index d7705771944..2ffdede2d3c 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -45,6 +45,7 @@ cfg-if = "0.1" # For debug feature fern = { version = "0.6", features = ["colored"], optional = true } log = { version = "0.4", optional = true } +tempfile = "3" [features] # Don't add the compiler features in default, please add them on the Makefile diff --git a/lib/cli/src/bin/wasmer.rs b/lib/cli/src/bin/wasmer.rs index 2b1d5792edc..509bbdeded5 100644 --- a/lib/cli/src/bin/wasmer.rs +++ b/lib/cli/src/bin/wasmer.rs @@ -1,4 +1,6 @@ use anyhow::Result; +#[cfg(all(feature = "object-file", feature = "compiler"))] +use wasmer_cli::commands::CreateExe; #[cfg(feature = "wast")] use wasmer_cli::commands::Wast; use wasmer_cli::commands::{Cache, Compile, Config, Inspect, Run, SelfUpdate, Validate}; @@ -26,6 +28,11 @@ enum WasmerCLIOptions { #[structopt(name = "compile")] Compile(Compile), + /// Compile a WebAssembly binary into a native executable + #[cfg(all(feature = "object-file", feature = "compiler"))] + #[structopt(name = "create-exe")] + CreateExe(CreateExe), + /// Get various configuration information needed /// to compile programs which use Wasmer #[structopt(name = "config")] @@ -53,6 +60,8 @@ impl WasmerCLIOptions { Self::Cache(cache) => cache.execute(), Self::Validate(validate) => validate.execute(), Self::Compile(compile) => compile.execute(), + #[cfg(all(feature = "object-file", feature = "compiler"))] + Self::CreateExe(create_exe) => create_exe.execute(), Self::Config(config) => config.execute(), Self::Inspect(inspect) => inspect.execute(), #[cfg(feature = "wast")] diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index fe7036e3625..7107150ccf4 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -2,6 +2,8 @@ mod cache; mod compile; mod config; +#[cfg(all(feature = "object-file", feature = "compiler"))] +mod create_exe; mod inspect; mod run; mod self_update; @@ -9,6 +11,8 @@ mod validate; #[cfg(feature = "wast")] mod wast; +#[cfg(all(feature = "object-file", feature = "compiler"))] +pub use create_exe::*; #[cfg(feature = "wast")] pub use wast::*; pub use {cache::*, compile::*, config::*, inspect::*, run::*, self_update::*, validate::*}; diff --git a/lib/cli/src/commands/compile.rs b/lib/cli/src/commands/compile.rs index f2e158a324a..2d4e0c4065a 100644 --- a/lib/cli/src/commands/compile.rs +++ b/lib/cli/src/commands/compile.rs @@ -38,8 +38,7 @@ impl Compile { .context(format!("failed to compile `{}`", self.path.display())) } - fn get_recommend_extension( - &self, + pub(crate) fn get_recommend_extension( engine_type: &EngineType, target_triple: &Triple, ) -> &'static str { @@ -82,7 +81,7 @@ impl Compile { .file_stem() .map(|osstr| osstr.to_string_lossy().to_string()) .unwrap_or_default(); - let recommended_extension = self.get_recommend_extension(&engine_type, target.triple()); + let recommended_extension = Self::get_recommend_extension(&engine_type, target.triple()); match self.output.extension() { Some(ext) => { if ext != recommended_extension { diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs new file mode 100644 index 00000000000..62b9a42bd1f --- /dev/null +++ b/lib/cli/src/commands/create_exe.rs @@ -0,0 +1,306 @@ +//! Create a standalone native executable for a given Wasm file. + +use crate::store::{CompilerOptions, EngineType}; +use anyhow::{Context, Result}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use structopt::StructOpt; +use wasmer::*; + +const WASMER_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_create_exe_main.c"); + +#[derive(Debug, StructOpt)] +/// The options for the `wasmer create-exe` subcommand +pub struct CreateExe { + /// Input file + #[structopt(name = "FILE", parse(from_os_str))] + path: PathBuf, + + /// Output file + #[structopt(name = "OUTPUT PATH", short = "o", parse(from_os_str))] + output: PathBuf, + + /// Compilation Target triple + #[structopt(long = "target")] + target_triple: Option, + + #[structopt(flatten)] + compiler: CompilerOptions, + + #[structopt(short = "m", multiple = true)] + cpu_features: Vec, + + /// Additional libraries to link against. + /// This is useful for fixing linker errors that may occur on some systems. + #[structopt(short = "l", multiple = true)] + libraries: Vec, +} + +impl CreateExe { + /// Runs logic for the `compile` subcommand + pub fn execute(&self) -> Result<()> { + let target = self + .target_triple + .as_ref() + .map(|target_triple| { + let mut features = self + .cpu_features + .clone() + .into_iter() + .fold(CpuFeature::set(), |a, b| a | b); + // Cranelift requires SSE2, so we have this "hack" for now to facilitate + // usage + features |= CpuFeature::SSE2; + Target::new(target_triple.clone(), features) + }) + .unwrap_or_default(); + let engine_type = EngineType::ObjectFile; + let (store, compiler_type) = self + .compiler + .get_store_for_target_and_engine(target.clone(), engine_type)?; + + println!("Engine: {}", engine_type.to_string()); + println!("Compiler: {}", compiler_type.to_string()); + println!("Target: {}", target.triple()); + + let working_dir = tempfile::tempdir()?; + let starting_cd = env::current_dir()?; + let output_path = starting_cd.join(&self.output); + env::set_current_dir(&working_dir)?; + + #[cfg(not(windows))] + let wasm_object_path = PathBuf::from("wasm.o"); + #[cfg(windows)] + let wasm_object_path = PathBuf::from("wasm.obj"); + + let wasm_module_path = starting_cd.join(&self.path); + + let module = + Module::from_file(&store, &wasm_module_path).context("failed to compile Wasm")?; + let _ = module.serialize_to_file(&wasm_object_path)?; + + let artifact: &wasmer_engine_object_file::ObjectFileArtifact = + module.artifact().as_ref().downcast_ref().context( + "Engine type is ObjectFile but could not downcast artifact into ObjectFileArtifact", + )?; + let symbol_registry = artifact.symbol_registry(); + let metadata_length = artifact.metadata_length(); + let module_info = module.info(); + let header_file_src = crate::c_gen::object_file_header::generate_header_file( + module_info, + symbol_registry, + metadata_length, + ); + + generate_header(header_file_src.as_bytes())?; + self.compile_c(wasm_object_path, output_path)?; + + eprintln!( + "✔ Native executable compiled successfully to `{}`.", + self.output.display(), + ); + + Ok(()) + } + + fn compile_c(&self, wasm_object_path: PathBuf, output_path: PathBuf) -> anyhow::Result<()> { + use std::io::Write; + + // write C src to disk + let c_src_path = Path::new("wasmer_main.c"); + #[cfg(not(windows))] + let c_src_obj = PathBuf::from("wasmer_main.o"); + #[cfg(windows)] + let c_src_obj = PathBuf::from("wasmer_main.obj"); + + { + let mut c_src_file = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&c_src_path) + .context("Failed to open C source code file")?; + c_src_file.write_all(WASMER_MAIN_C_SOURCE)?; + } + run_c_compile(&c_src_path, &c_src_obj, self.target_triple.clone()) + .context("Failed to compile C source code")?; + LinkCode { + object_paths: vec![c_src_obj, wasm_object_path], + output_path, + additional_libraries: self.libraries.clone(), + target: self.target_triple.clone(), + ..Default::default() + } + .run() + .context("Failed to link objects together")?; + + Ok(()) + } +} + +fn generate_header(header_file_src: &[u8]) -> anyhow::Result<()> { + let header_file_path = Path::new("my_wasm.h"); + let mut header = std::fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&header_file_path)?; + + use std::io::Write; + header.write(header_file_src)?; + + Ok(()) +} + +fn get_wasmer_dir() -> anyhow::Result { + Ok(PathBuf::from( + env::var("WASMER_DIR").context("Trying to read env var `WASMER_DIR`")?, + )) +} + +fn get_wasmer_include_directory() -> anyhow::Result { + let mut path = get_wasmer_dir()?; + path.push("include"); + Ok(path) +} + +/// path to the static libwasmer +fn get_libwasmer_path() -> anyhow::Result { + let mut path = get_wasmer_dir()?; + path.push("lib"); + + // TODO: prefer headless Wasmer if/when it's a separate library. + #[cfg(not(windows))] + path.push("libwasmer.a"); + #[cfg(windows)] + path.push("libwasmer.lib"); + + Ok(path) +} + +/// Compile the C code. +fn run_c_compile( + path_to_c_src: &Path, + output_name: &Path, + target: Option, +) -> anyhow::Result<()> { + #[cfg(not(windows))] + let c_compiler = "cc"; + // We must use a C++ compiler on Windows because wasm.h uses `static_assert` + // which isn't available in `clang` on Windows. + #[cfg(windows)] + let c_compiler = "clang++"; + + let mut command = Command::new(c_compiler); + let command = command + .arg("-O2") + .arg("-c") + .arg(path_to_c_src) + .arg("-I") + .arg(get_wasmer_include_directory()?); + + let command = if let Some(target) = target { + command.arg("-target").arg(format!("{}", target)) + } else { + command + }; + + let output = command.arg("-o").arg(output_name).output()?; + + if !output.status.success() { + bail!( + "C code compile failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) +} + +/// Data used to run a linking command for generated artifacts. +#[derive(Debug)] +struct LinkCode { + /// Path to the linker used to run the linking command. + linker_path: PathBuf, + /// String used as an optimization flag. + optimization_flag: String, + /// Paths of objects to link. + object_paths: Vec, + /// Additional libraries to link against. + additional_libraries: Vec, + /// Path to the output target. + output_path: PathBuf, + /// Path to the dir containing the static libwasmer library. + libwasmer_path: PathBuf, + /// The target to link the executable for. + target: Option, +} + +impl Default for LinkCode { + fn default() -> Self { + #[cfg(not(windows))] + let linker = "cc"; + #[cfg(windows)] + let linker = "clang"; + Self { + linker_path: PathBuf::from(linker), + optimization_flag: String::from("-O2"), + object_paths: vec![], + additional_libraries: vec![], + output_path: PathBuf::from("a.out"), + libwasmer_path: get_libwasmer_path().unwrap(), + target: None, + } + } +} + +impl LinkCode { + fn run(&self) -> anyhow::Result<()> { + let mut command = Command::new(&self.linker_path); + let command = command + .arg(&self.optimization_flag) + .args( + self.object_paths + .iter() + .map(|path| path.canonicalize().unwrap()), + ) + .arg( + &self + .libwasmer_path + .canonicalize() + .context("Failed to find libwasmer")?, + ); + let command = if let Some(target) = &self.target { + command.arg("-target").arg(format!("{}", target)) + } else { + command + }; + // Add libraries required per platform. + // We need userenv, sockets (Ws2_32), and advapi32 to call a system call (for random numbers I think). + #[cfg(windows)] + let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); + // On unix we need dlopen-related symbols, libmath for a few things, and pthreads. + #[cfg(not(windows))] + let command = command.arg("-ldl").arg("-lm").arg("-pthread"); + let link_aganist_extra_libs = self + .additional_libraries + .iter() + .map(|lib| format!("-l{}", lib)); + let command = command.args(link_aganist_extra_libs); + let output = command.arg("-o").arg(&self.output_path).output()?; + + if !output.status.success() { + bail!( + "linking failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c new file mode 100644 index 00000000000..11adb1656a8 --- /dev/null +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -0,0 +1,172 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include "wasmer_wasm.h" +#include "wasm.h" +#include "my_wasm.h" + +#include +#include + +// TODO: make this define templated so that the Rust code can toggle it on/off +#define WASI + +#ifdef __cplusplus +} +#endif + +void print_wasmer_error() +{ + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char* error_str = (char*) malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("%s\n", error_str); +} + +#ifdef WASI +int find_colon(char* string) { + int colon_location = 0; + for (int j = 0; j < strlen(string); ++j) { + if (string[j] == ':') { + colon_location = j; + break; + } + } + return colon_location; +} + +void pass_mapdir_arg(wasi_config_t* wasi_config, char* mapdir) { + int colon_location = find_colon(mapdir); + if (colon_location == 0) { + // error malformed argument + fprintf(stderr, "Expected mapdir argument of the form alias:directory\n"); + exit(-1); + } + int dir_len = strlen(mapdir) - colon_location; + char* alias = (char*)malloc(colon_location + 1); + char* dir = (char*)malloc(dir_len + 1); + int j = 0; + for (j = 0; j < colon_location; ++j) { + alias[j] = mapdir[j]; + } + alias[j] = 0; + for (j = 0; j < dir_len; ++j) { + dir[j] = mapdir[j + colon_location + 1]; + } + dir[j] = 0; + + wasi_config_mapdir(wasi_config, alias, dir); + free(alias); + free(dir); +} + +// We try to parse out `--dir` and `--mapdir` ahead of time and process those +// specially. All other arguments are passed to the guest program. +void handle_arguments(wasi_config_t* wasi_config, int argc, char* argv[]) { + for (int i = 1; i < argc; ++i) { + // We probably want special args like `--dir` and `--mapdir` to not be passed directly + if (strcmp(argv[i], "--dir") == 0) { + // next arg is a preopen directory + if ((i + 1) < argc ) { + i++; + wasi_config_preopen_dir(wasi_config, argv[i]); + } else { + fprintf(stderr, "--dir expects a following argument specifying which directory to preopen\n"); + exit(-1); + } + } + else if (strcmp(argv[i], "--mapdir") == 0) { + // next arg is a mapdir + if ((i + 1) < argc ) { + i++; + pass_mapdir_arg(wasi_config, argv[i]); + } else { + fprintf(stderr, "--mapdir expects a following argument specifying which directory to preopen in the form alias:directory\n"); + exit(-1); + } + } + else if (strncmp(argv[i], "--dir=", strlen("--dir=")) == 0 ) { + // this arg is a preopen dir + char* dir = argv[i] + strlen("--dir="); + wasi_config_preopen_dir(wasi_config, dir); + } + else if (strncmp(argv[i], "--mapdir=", strlen("--mapdir=")) == 0 ) { + // this arg is a mapdir + char* mapdir = argv[i] + strlen("--mapdir="); + pass_mapdir_arg(wasi_config, mapdir); + } + else { + // guest argument + wasi_config_arg(wasi_config, argv[i]); + } + } +} +#endif + +int main(int argc, char* argv[]) { + wasm_config_t* config = wasm_config_new(); + wasm_config_set_engine(config, OBJECT_FILE); + wasm_engine_t* engine = wasm_engine_new_with_config(config); + wasm_store_t* store = wasm_store_new(engine); + + wasm_module_t* module = wasmer_object_file_engine_new(store, argv[0]); + if (! module) { + fprintf(stderr, "Failed to create module\n"); + print_wasmer_error(); + return -1; + } + + // We have now finished the memory buffer book keeping and we have a valid Module. + + #ifdef WASI + wasi_config_t* wasi_config = wasi_config_new(argv[0]); + handle_arguments(wasi_config, argc, argv); + + wasi_env_t* wasi_env = wasi_env_new(wasi_config); + if (!wasi_env) { + fprintf(stderr, "Error building WASI env!\n"); + print_wasmer_error(); + return 1; + } + #endif + + wasm_importtype_vec_t import_types; + wasm_module_imports(module, &import_types); + int num_imports = import_types.size; + wasm_extern_t** imports = (wasm_extern_t**) malloc(num_imports * sizeof(wasm_extern_t*)); + wasm_importtype_vec_delete(&import_types); + + #ifdef WASI + bool get_imports_result = wasi_get_imports(store, module, wasi_env, imports); + if (!get_imports_result) { + fprintf(stderr, "Error getting WASI imports!\n"); + print_wasmer_error(); + return 1; + } + #endif + + wasm_instance_t* instance = wasm_instance_new(store, module, (const wasm_extern_t* const*) imports, NULL); + if (! instance) { + fprintf(stderr, "Failed to create instance\n"); + print_wasmer_error(); + return -1; + } + + #ifdef WASI + wasi_env_set_instance(wasi_env, instance); + #endif + + void* vmctx = wasm_instance_get_vmctx_ptr(instance); + wasm_val_t* inout[2] = { NULL, NULL }; + + // We're able to call our compiled function directly through a trampoline. + wasmer_trampoline_function_call__1(vmctx, wasmer_function__1, &inout); + + wasm_instance_delete(instance); + wasm_module_delete(module); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 0; +} diff --git a/lib/cli/src/store.rs b/lib/cli/src/store.rs index 795fcf792fe..9404ac2bcb7 100644 --- a/lib/cli/src/store.rs +++ b/lib/cli/src/store.rs @@ -14,8 +14,27 @@ use wasmer::*; use wasmer_compiler::CompilerConfig; #[derive(Debug, Clone, StructOpt)] -/// The compiler options +/// The compiler and engine options pub struct StoreOptions { + #[structopt(flatten)] + compiler: CompilerOptions, + + /// Use JIT Engine. + #[structopt(long, conflicts_with_all = &["native", "object_file"])] + jit: bool, + + /// Use Native Engine. + #[structopt(long, conflicts_with_all = &["jit", "object_file"])] + native: bool, + + /// Use ObjectFile Engine. + #[structopt(long, conflicts_with_all = &["jit", "native"])] + object_file: bool, +} + +#[derive(Debug, Clone, StructOpt)] +/// The compiler options +pub struct CompilerOptions { /// Use Singlepass compiler. #[structopt(long, conflicts_with_all = &["cranelift", "llvm", "backend"])] singlepass: bool, @@ -36,19 +55,7 @@ pub struct StoreOptions { #[structopt(long, parse(from_os_str))] llvm_debug_dir: Option, - /// Use JIT Engine. - #[structopt(long, conflicts_with_all = &["native", "object_file"])] - jit: bool, - - /// Use Native Engine. - #[structopt(long, conflicts_with_all = &["jit", "object_file"])] - native: bool, - - /// Use ObjectFile Engine. - #[structopt(long, conflicts_with_all = &["jit", "native"])] - object_file: bool, - - /// The deprecated backend flag - Please not use + /// The deprecated backend flag - Please do not use #[structopt(long = "backend", hidden = true, conflicts_with_all = &["singlepass", "cranelift", "llvm"])] backend: Option, @@ -56,80 +63,8 @@ pub struct StoreOptions { features: WasmFeatures, } -/// The compiler used for the store -#[derive(Debug, PartialEq, Eq)] -pub enum CompilerType { - /// Singlepass compiler - Singlepass, - /// Cranelift compiler - Cranelift, - /// LLVM compiler - LLVM, - /// Headless compiler - Headless, -} - -impl CompilerType { - /// Return all enabled compilers - pub fn enabled() -> Vec { - vec![ - #[cfg(feature = "singlepass")] - Self::Singlepass, - #[cfg(feature = "cranelift")] - Self::Cranelift, - #[cfg(feature = "llvm")] - Self::LLVM, - ] - } -} - -impl ToString for CompilerType { - fn to_string(&self) -> String { - match self { - Self::Singlepass => "singlepass".to_string(), - Self::Cranelift => "cranelift".to_string(), - Self::LLVM => "llvm".to_string(), - Self::Headless => "headless".to_string(), - } - } -} - -impl FromStr for CompilerType { - type Err = Error; - fn from_str(s: &str) -> Result { - match s { - "singlepass" => Ok(Self::Singlepass), - "cranelift" => Ok(Self::Cranelift), - "llvm" => Ok(Self::LLVM), - "headless" => Ok(Self::Headless), - backend => bail!("The `{}` compiler does not exist.", backend), - } - } -} - -/// The engine used for the store -#[derive(Debug, PartialEq, Eq)] -pub enum EngineType { - /// JIT Engine - JIT, - /// Native Engine - Native, - /// Object File Engine - ObjectFile, -} - -impl ToString for EngineType { - fn to_string(&self) -> String { - match self { - Self::JIT => "jit".to_string(), - Self::Native => "native".to_string(), - Self::ObjectFile => "objectfile".to_string(), - } - } -} - -#[cfg(all(feature = "compiler", feature = "engine"))] -impl StoreOptions { +#[cfg(feature = "compiler")] +impl CompilerOptions { fn get_compiler(&self) -> Result { if self.cranelift { Ok(CompilerType::Cranelift) @@ -161,7 +96,7 @@ impl StoreOptions { } } - /// Get the Target architecture + /// Get the enaled Wasm features. pub fn get_features(&self, mut features: Features) -> Result { if self.features.threads || self.features.all { features.threads(true); @@ -181,6 +116,63 @@ impl StoreOptions { Ok(features) } + /// Gets the Store for a given target and engine. + pub fn get_store_for_target_and_engine( + &self, + target: Target, + engine_type: EngineType, + ) -> Result<(Store, CompilerType)> { + let (compiler_config, compiler_type) = self.get_compiler_config()?; + let engine = self.get_engine_by_type(target, compiler_config, engine_type)?; + let store = Store::new(&*engine); + Ok((store, compiler_type)) + } + + fn get_engine_by_type( + &self, + target: Target, + compiler_config: Box, + engine_type: EngineType, + ) -> Result> { + let features = self.get_features(compiler_config.default_features_for_target(&target))?; + let engine: Box = match engine_type { + #[cfg(feature = "jit")] + EngineType::JIT => Box::new( + wasmer_engine_jit::JIT::new(&*compiler_config) + .features(features) + .target(target) + .engine(), + ), + #[cfg(feature = "native")] + EngineType::Native => { + let mut compiler_config = compiler_config; + Box::new( + wasmer_engine_native::Native::new(&mut *compiler_config) + .target(target) + .features(features) + .engine(), + ) + } + #[cfg(feature = "object-file")] + EngineType::ObjectFile => { + let mut compiler_config = compiler_config; + Box::new( + wasmer_engine_object_file::ObjectFile::new(&mut *compiler_config) + .target(target) + .features(features) + .engine(), + ) + } + #[cfg(not(all(feature = "jit", feature = "native", feature = "object-file")))] + engine => bail!( + "The `{}` engine is not included in this binary.", + engine.to_string() + ), + }; + + Ok(engine) + } + /// Get the Compiler Config for the current options #[allow(unused_variables)] pub(crate) fn get_compiler_config(&self) -> Result<(Box, CompilerType)> { @@ -315,7 +307,82 @@ impl StoreOptions { #[allow(unreachable_code)] Ok((compiler_config, compiler)) } +} + +/// The compiler used for the store +#[derive(Debug, PartialEq, Eq)] +pub enum CompilerType { + /// Singlepass compiler + Singlepass, + /// Cranelift compiler + Cranelift, + /// LLVM compiler + LLVM, + /// Headless compiler + Headless, +} + +impl CompilerType { + /// Return all enabled compilers + pub fn enabled() -> Vec { + vec![ + #[cfg(feature = "singlepass")] + Self::Singlepass, + #[cfg(feature = "cranelift")] + Self::Cranelift, + #[cfg(feature = "llvm")] + Self::LLVM, + ] + } +} +impl ToString for CompilerType { + fn to_string(&self) -> String { + match self { + Self::Singlepass => "singlepass".to_string(), + Self::Cranelift => "cranelift".to_string(), + Self::LLVM => "llvm".to_string(), + Self::Headless => "headless".to_string(), + } + } +} + +impl FromStr for CompilerType { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "singlepass" => Ok(Self::Singlepass), + "cranelift" => Ok(Self::Cranelift), + "llvm" => Ok(Self::LLVM), + "headless" => Ok(Self::Headless), + backend => bail!("The `{}` compiler does not exist.", backend), + } + } +} + +/// The engine used for the store +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum EngineType { + /// JIT Engine + JIT, + /// Native Engine + Native, + /// Object File Engine + ObjectFile, +} + +impl ToString for EngineType { + fn to_string(&self) -> String { + match self { + Self::JIT => "jit".to_string(), + Self::Native => "native".to_string(), + Self::ObjectFile => "objectfile".to_string(), + } + } +} + +#[cfg(all(feature = "compiler", feature = "engine"))] +impl StoreOptions { /// Gets the store for the host target, with the engine name and compiler name selected pub fn get_store(&self) -> Result<(Store, EngineType, CompilerType)> { let target = Target::default(); @@ -327,7 +394,7 @@ impl StoreOptions { &self, target: Target, ) -> Result<(Store, EngineType, CompilerType)> { - let (compiler_config, compiler_type) = self.get_compiler_config()?; + let (compiler_config, compiler_type) = self.compiler.get_compiler_config()?; let (engine, engine_type) = self.get_engine_with_compiler(target, compiler_config)?; let store = Store::new(&*engine); Ok((store, engine_type, compiler_type)) @@ -339,41 +406,10 @@ impl StoreOptions { compiler_config: Box, ) -> Result<(Box, EngineType)> { let engine_type = self.get_engine()?; - let features = self.get_features(compiler_config.default_features_for_target(&target))?; - let engine: Box = match engine_type { - #[cfg(feature = "jit")] - EngineType::JIT => Box::new( - wasmer_engine_jit::JIT::new(&*compiler_config) - .features(features) - .target(target) - .engine(), - ), - #[cfg(feature = "native")] - EngineType::Native => { - let mut compiler_config = compiler_config; - Box::new( - wasmer_engine_native::Native::new(&mut *compiler_config) - .target(target) - .features(features) - .engine(), - ) - } - #[cfg(feature = "object-file")] - EngineType::ObjectFile => { - let mut compiler_config = compiler_config; - Box::new( - wasmer_engine_object_file::ObjectFile::new(&mut *compiler_config) - .target(target) - .features(features) - .engine(), - ) - } - #[cfg(not(all(feature = "jit", feature = "native", feature = "object-file")))] - engine => bail!( - "The `{}` engine is not included in this binary.", - engine.to_string() - ), - }; + let engine = self + .compiler + .get_engine_by_type(target, compiler_config, engine_type)?; + Ok((engine, engine_type)) } } diff --git a/tests/integration/cli/src/assets.rs b/tests/integration/cli/src/assets.rs new file mode 100644 index 00000000000..4113e0b5cb8 --- /dev/null +++ b/tests/integration/cli/src/assets.rs @@ -0,0 +1,32 @@ +use std::env; +use std::path::PathBuf; + +pub const ASSET_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets"); + +pub const WASMER_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/release/wasmer" +); + +#[cfg(not(windows))] +pub const LIBWASMER_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/release/libwasmer_c_api.a" +); +#[cfg(windows)] +pub const LIBWASMER_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/release/wasmer_c_api.lib" +); + +/// Get the path to the `libwasmer.a` static library. +pub fn get_libwasmer_path() -> PathBuf { + PathBuf::from( + env::var("WASMER_TEST_LIBWASMER_PATH").unwrap_or_else(|_| LIBWASMER_PATH.to_string()), + ) +} + +/// Get the path to the `wasmer` executable to be used in this test. +pub fn get_wasmer_path() -> PathBuf { + PathBuf::from(env::var("WASMER_TEST_WASMER_PATH").unwrap_or_else(|_| WASMER_PATH.to_string())) +} diff --git a/tests/integration/cli/src/compile.rs b/tests/integration/cli/src/compile.rs deleted file mode 100644 index b14635b37f7..00000000000 --- a/tests/integration/cli/src/compile.rs +++ /dev/null @@ -1,306 +0,0 @@ -//! CLI tests for the compile subcommand. - -use anyhow::{bail, Context}; -use std::env; -use std::fs; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::process::Command; - -const CLI_INTEGRATION_TESTS_ASSETS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets"); - -const OBJECT_FILE_ENGINE_TEST_C_SOURCE: &[u8] = - include_bytes!("object_file_engine_test_c_source.c"); -// TODO: -const OBJECT_FILE_ENGINE_TEST_WASM_PATH: &str = - concat!(env!("CARGO_MANIFEST_DIR"), "/assets/qjs.wasm"); - -const WASMER_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/release/wasmer" -); - -#[cfg(not(windows))] -const LIBWASMER_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/release/libwasmer_c_api.a" -); -#[cfg(windows)] -const LIBWASMER_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/release/wasmer_c_api.lib" -); - -/// Get the path to the `wasmer` executable to be used in this test. -fn get_wasmer_path() -> PathBuf { - PathBuf::from(env::var("WASMER_TEST_WASMER_PATH").unwrap_or_else(|_| WASMER_PATH.to_string())) -} - -/// Get the path to the `libwasmer.a` static library. -fn get_libwasmer_path() -> PathBuf { - PathBuf::from( - env::var("WASMER_TEST_LIBWASMER_PATH").unwrap_or_else(|_| LIBWASMER_PATH.to_string()), - ) -} - -#[allow(dead_code)] -#[derive(Debug, Copy, Clone)] -pub enum Engine { - Jit, - Native, - ObjectFile, -} - -impl Engine { - // TODO: make this `const fn` when Wasmer moves to Rust 1.46.0+ - pub fn to_flag(self) -> &'static str { - match self { - Engine::Jit => "--jit", - Engine::Native => "--native", - Engine::ObjectFile => "--object-file", - } - } -} - -#[allow(dead_code)] -#[derive(Debug, Copy, Clone)] -pub enum Compiler { - Cranelift, - LLVM, - Singlepass, -} - -impl Compiler { - // TODO: make this `const fn` when Wasmer moves to Rust 1.46.0+ - pub fn to_flag(self) -> &'static str { - match self { - Compiler::Cranelift => "--cranelift", - Compiler::LLVM => "--llvm", - Compiler::Singlepass => "--singlepass", - } - } -} - -/// Data used to run the `wasmer compile` command. -#[derive(Debug)] -struct WasmerCompile { - /// Path to wasmer executable used to run the command. - wasmer_path: PathBuf, - /// Path to the Wasm file to compile. - wasm_path: PathBuf, - /// Path to the object file produced by compiling the Wasm. - wasm_object_path: PathBuf, - /// Path to output the generated header to. - header_output_path: PathBuf, - /// Compiler with which to compile the Wasm. - compiler: Compiler, - /// Engine with which to use to generate the artifacts. - engine: Engine, -} - -impl Default for WasmerCompile { - fn default() -> Self { - #[cfg(not(windows))] - let wasm_obj_path = "wasm.o"; - #[cfg(windows)] - let wasm_obj_path = "wasm.obj"; - Self { - wasmer_path: get_wasmer_path(), - wasm_path: PathBuf::from(OBJECT_FILE_ENGINE_TEST_WASM_PATH), - wasm_object_path: PathBuf::from(wasm_obj_path), - header_output_path: PathBuf::from("my_wasm.h"), - compiler: Compiler::Cranelift, - engine: Engine::ObjectFile, - } - } -} - -impl WasmerCompile { - fn run(&self) -> anyhow::Result<()> { - let output = Command::new(&self.wasmer_path) - .arg("compile") - .arg(&self.wasm_path.canonicalize()?) - .arg(&self.compiler.to_flag()) - .arg(&self.engine.to_flag()) - .arg("-o") - .arg(&self.wasm_object_path) - .arg("--header") - .arg(&self.header_output_path) - .output()?; - - if !output.status.success() { - bail!( - "wasmer compile failed with: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - Ok(()) - } -} - -/// Compile the C code. -fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> { - #[cfg(not(windows))] - let c_compiler = "cc"; - #[cfg(windows)] - let c_compiler = "clang++"; - - let output = Command::new(c_compiler) - .arg("-O2") - .arg("-c") - .arg(path_to_c_src) - .arg("-I") - .arg(CLI_INTEGRATION_TESTS_ASSETS) - .arg("-o") - .arg(output_name) - .output()?; - - if !output.status.success() { - bail!( - "C code compile failed with: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - Ok(()) -} - -/// Data used to run a linking command for generated artifacts. -#[derive(Debug)] -struct LinkCode { - /// Path to the linker used to run the linking command. - linker_path: PathBuf, - /// String used as an optimization flag. - optimization_flag: String, - /// Paths of objects to link. - object_paths: Vec, - /// Path to the output target. - output_path: PathBuf, - /// Path to the static libwasmer library. - libwasmer_path: PathBuf, -} - -impl Default for LinkCode { - fn default() -> Self { - #[cfg(not(windows))] - let linker = "cc"; - #[cfg(windows)] - let linker = "clang"; - Self { - linker_path: PathBuf::from(linker), - optimization_flag: String::from("-O2"), - object_paths: vec![], - output_path: PathBuf::from("a.out"), - libwasmer_path: get_libwasmer_path(), - } - } -} - -impl LinkCode { - fn run(&self) -> anyhow::Result<()> { - let mut command = Command::new(&self.linker_path); - let command = command - .arg(&self.optimization_flag) - .args( - self.object_paths - .iter() - .map(|path| path.canonicalize().unwrap()), - ) - .arg(&self.libwasmer_path.canonicalize()?); - #[cfg(windows)] - let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); - #[cfg(not(windows))] - let command = command.arg("-ldl").arg("-lm").arg("-pthread"); - let output = command.arg("-o").arg(&self.output_path).output()?; - - if !output.status.success() { - bail!( - "linking failed with: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - Ok(()) - } -} - -fn run_code(executable_path: &Path) -> anyhow::Result { - let output = Command::new(executable_path.canonicalize()?).output()?; - - if !output.status.success() { - bail!( - "running executable failed: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - let output = - std::str::from_utf8(&output.stdout).expect("output from running executable is not utf-8"); - - Ok(output.to_owned()) -} - -#[test] -fn object_file_engine_works() -> anyhow::Result<()> { - let operating_dir = tempfile::tempdir()?; - - std::env::set_current_dir(&operating_dir)?; - - let wasm_path = PathBuf::from(OBJECT_FILE_ENGINE_TEST_WASM_PATH); - #[cfg(not(windows))] - let wasm_object_path = PathBuf::from("wasm.o"); - #[cfg(windows)] - let wasm_object_path = PathBuf::from("wasm.obj"); - let header_output_path = PathBuf::from("my_wasm.h"); - - WasmerCompile { - wasm_path: wasm_path.clone(), - wasm_object_path: wasm_object_path.clone(), - header_output_path, - compiler: Compiler::Cranelift, - engine: Engine::ObjectFile, - ..Default::default() - } - .run() - .context("Failed to compile wasm with Wasmer")?; - - let c_src_file_name = Path::new("c_src.c"); - #[cfg(not(windows))] - let c_object_path = PathBuf::from("c_src.o"); - #[cfg(windows)] - let c_object_path = PathBuf::from("c_src.obj"); - let executable_path = PathBuf::from("a.out"); - - // TODO: adjust C source code based on locations of things - { - let mut c_src_file = fs::OpenOptions::new() - .create_new(true) - .write(true) - .open(&c_src_file_name) - .context("Failed to open C source code file")?; - c_src_file.write_all(OBJECT_FILE_ENGINE_TEST_C_SOURCE)?; - } - run_c_compile(&c_src_file_name, &c_object_path).context("Failed to compile C source code")?; - LinkCode { - object_paths: vec![c_object_path, wasm_object_path], - output_path: executable_path.clone(), - ..Default::default() - } - .run() - .context("Failed to link objects together")?; - - let result = run_code(&executable_path).context("Failed to run generated executable")?; - let result_lines = result.lines().collect::>(); - assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); - - Ok(()) -} diff --git a/tests/integration/cli/src/lib.rs b/tests/integration/cli/src/lib.rs index 69d222b5eab..43d6ada6a69 100644 --- a/tests/integration/cli/src/lib.rs +++ b/tests/integration/cli/src/lib.rs @@ -1,6 +1,10 @@ #![forbid(unsafe_code)] -#![cfg(test)] //! CLI integration tests -mod compile; +pub mod assets; +pub mod link_code; +pub mod util; + +pub use assets::*; +pub use util::*; diff --git a/tests/integration/cli/src/link_code.rs b/tests/integration/cli/src/link_code.rs new file mode 100644 index 00000000000..3dce2363526 --- /dev/null +++ b/tests/integration/cli/src/link_code.rs @@ -0,0 +1,69 @@ +use crate::assets::*; +use anyhow::bail; +use std::path::PathBuf; +use std::process::Command; + +/// Data used to run a linking command for generated artifacts. +#[derive(Debug)] +pub struct LinkCode { + /// The directory to operate in. + pub current_dir: PathBuf, + /// Path to the linker used to run the linking command. + pub linker_path: PathBuf, + /// String used as an optimization flag. + pub optimization_flag: String, + /// Paths of objects to link. + pub object_paths: Vec, + /// Path to the output target. + pub output_path: PathBuf, + /// Path to the static libwasmer library. + pub libwasmer_path: PathBuf, +} + +impl Default for LinkCode { + fn default() -> Self { + #[cfg(not(windows))] + let linker = "cc"; + #[cfg(windows)] + let linker = "clang"; + Self { + current_dir: std::env::current_dir().unwrap(), + linker_path: PathBuf::from(linker), + optimization_flag: String::from("-O2"), + object_paths: vec![], + output_path: PathBuf::from("a.out"), + libwasmer_path: get_libwasmer_path(), + } + } +} + +impl LinkCode { + pub fn run(&self) -> anyhow::Result<()> { + let mut command = Command::new(&self.linker_path); + let command = command + .current_dir(&self.current_dir) + .arg(&self.optimization_flag) + .args( + self.object_paths + .iter() + .map(|path| path.canonicalize().unwrap()), + ) + .arg(&self.libwasmer_path.canonicalize()?); + #[cfg(windows)] + let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32"); + #[cfg(not(windows))] + let command = command.arg("-ldl").arg("-lm").arg("-pthread"); + let output = command.arg("-o").arg(&self.output_path).output()?; + + if !output.status.success() { + bail!( + "linking failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} diff --git a/tests/integration/cli/src/util.rs b/tests/integration/cli/src/util.rs new file mode 100644 index 00000000000..30006732798 --- /dev/null +++ b/tests/integration/cli/src/util.rs @@ -0,0 +1,62 @@ +use anyhow::bail; +use std::path::Path; +use std::process::Command; + +#[derive(Debug, Copy, Clone)] +pub enum Compiler { + Cranelift, + LLVM, + Singlepass, +} + +impl Compiler { + pub const fn to_flag(self) -> &'static str { + match self { + Compiler::Cranelift => "--cranelift", + Compiler::LLVM => "--llvm", + Compiler::Singlepass => "--singlepass", + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Engine { + Jit, + Native, + ObjectFile, +} + +impl Engine { + pub const fn to_flag(self) -> &'static str { + match self { + Engine::Jit => "--jit", + Engine::Native => "--native", + Engine::ObjectFile => "--object-file", + } + } +} + +pub fn run_code( + operating_dir: &Path, + executable_path: &Path, + args: &[String], +) -> anyhow::Result { + let output = Command::new(executable_path.canonicalize()?) + .current_dir(operating_dir) + .args(args) + .output()?; + + if !output.status.success() { + bail!( + "running executable failed: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + let output = + std::str::from_utf8(&output.stdout).expect("output from running executable is not utf-8"); + + Ok(output.to_owned()) +} diff --git a/tests/integration/cli/tests/compile.rs b/tests/integration/cli/tests/compile.rs new file mode 100644 index 00000000000..71fa6bd58c9 --- /dev/null +++ b/tests/integration/cli/tests/compile.rs @@ -0,0 +1,173 @@ +//! CLI tests for the compile subcommand. + +use anyhow::{bail, Context}; +use std::fs; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use wasmer_integration_tests_cli::link_code::*; +use wasmer_integration_tests_cli::*; + +const OBJECT_FILE_ENGINE_TEST_C_SOURCE: &[u8] = + include_bytes!("object_file_engine_test_c_source.c"); + +fn object_file_engine_test_wasm_path() -> String { + format!("{}/{}", ASSET_PATH, "qjs.wasm") +} + +/// Data used to run the `wasmer compile` command. +#[derive(Debug)] +struct WasmerCompile { + /// The directory to operate in. + current_dir: PathBuf, + /// Path to wasmer executable used to run the command. + wasmer_path: PathBuf, + /// Path to the Wasm file to compile. + wasm_path: PathBuf, + /// Path to the object file produced by compiling the Wasm. + wasm_object_path: PathBuf, + /// Path to output the generated header to. + header_output_path: PathBuf, + /// Compiler with which to compile the Wasm. + compiler: Compiler, + /// Engine with which to use to generate the artifacts. + engine: Engine, +} + +impl Default for WasmerCompile { + fn default() -> Self { + #[cfg(not(windows))] + let wasm_obj_path = "wasm.o"; + #[cfg(windows)] + let wasm_obj_path = "wasm.obj"; + Self { + current_dir: std::env::current_dir().unwrap(), + wasmer_path: get_wasmer_path(), + wasm_path: PathBuf::from(object_file_engine_test_wasm_path()), + wasm_object_path: PathBuf::from(wasm_obj_path), + header_output_path: PathBuf::from("my_wasm.h"), + compiler: Compiler::Cranelift, + engine: Engine::ObjectFile, + } + } +} + +impl WasmerCompile { + fn run(&self) -> anyhow::Result<()> { + let output = Command::new(&self.wasmer_path) + .current_dir(&self.current_dir) + .arg("compile") + .arg(&self.wasm_path.canonicalize()?) + .arg(&self.compiler.to_flag()) + .arg(&self.engine.to_flag()) + .arg("-o") + .arg(&self.wasm_object_path) + .arg("--header") + .arg(&self.header_output_path) + .output()?; + + if !output.status.success() { + bail!( + "wasmer compile failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} + +/// Compile the C code. +fn run_c_compile( + current_dir: &Path, + path_to_c_src: &Path, + output_name: &Path, +) -> anyhow::Result<()> { + #[cfg(not(windows))] + let c_compiler = "cc"; + #[cfg(windows)] + let c_compiler = "clang++"; + + let output = Command::new(c_compiler) + .current_dir(current_dir) + .arg("-O2") + .arg("-c") + .arg(path_to_c_src) + .arg("-I") + .arg(ASSET_PATH) + .arg("-o") + .arg(output_name) + .output()?; + + if !output.status.success() { + bail!( + "C code compile failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) +} + +#[test] +fn object_file_engine_works() -> anyhow::Result<()> { + let temp_dir = tempfile::tempdir()?; + let operating_dir: PathBuf = temp_dir.path().to_owned(); + + let wasm_path = operating_dir.join(object_file_engine_test_wasm_path()); + #[cfg(not(windows))] + let wasm_object_path = operating_dir.join("wasm.o"); + #[cfg(windows)] + let wasm_object_path = operating_dir.join("wasm.obj"); + let header_output_path = operating_dir.join("my_wasm.h"); + + WasmerCompile { + current_dir: operating_dir.clone(), + wasm_path: wasm_path.clone(), + wasm_object_path: wasm_object_path.clone(), + header_output_path, + compiler: Compiler::Cranelift, + engine: Engine::ObjectFile, + ..Default::default() + } + .run() + .context("Failed to compile wasm with Wasmer")?; + + let c_src_file_name = operating_dir.join("c_src.c"); + #[cfg(not(windows))] + let c_object_path = operating_dir.join("c_src.o"); + #[cfg(windows)] + let c_object_path = operating_dir.join("c_src.obj"); + let executable_path = operating_dir.join("a.out"); + + // TODO: adjust C source code based on locations of things + { + let mut c_src_file = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&c_src_file_name) + .context("Failed to open C source code file")?; + c_src_file.write_all(OBJECT_FILE_ENGINE_TEST_C_SOURCE)?; + } + run_c_compile(&operating_dir, &c_src_file_name, &c_object_path) + .context("Failed to compile C source code")?; + LinkCode { + current_dir: operating_dir.clone(), + object_paths: vec![c_object_path, wasm_object_path], + output_path: executable_path.clone(), + ..Default::default() + } + .run() + .context("Failed to link objects together")?; + + let result = run_code(&operating_dir, &executable_path, &[]) + .context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["Initializing...", "\"Hello, World\""],); + + Ok(()) +} diff --git a/tests/integration/cli/tests/create_exe.rs b/tests/integration/cli/tests/create_exe.rs new file mode 100644 index 00000000000..b8302bccd5a --- /dev/null +++ b/tests/integration/cli/tests/create_exe.rs @@ -0,0 +1,162 @@ +//! Tests of the `wasmer create-exe` command. + +use anyhow::{bail, Context}; +use std::fs; +use std::io::prelude::*; +use std::path::PathBuf; +use std::process::Command; +use wasmer_integration_tests_cli::*; + +fn create_exe_test_wasm_path() -> String { + format!("{}/{}", ASSET_PATH, "qjs.wasm") +} +const JS_TEST_SRC_CODE: &[u8] = + b"function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));\n"; + +/// Data used to run the `wasmer compile` command. +#[derive(Debug)] +struct WasmerCreateExe { + /// The directory to operate in. + current_dir: PathBuf, + /// Path to wasmer executable used to run the command. + wasmer_path: PathBuf, + /// Path to the Wasm file to compile. + wasm_path: PathBuf, + /// Path to the native executable produced by compiling the Wasm. + native_executable_path: PathBuf, + /// Compiler with which to compile the Wasm. + compiler: Compiler, +} + +impl Default for WasmerCreateExe { + fn default() -> Self { + #[cfg(not(windows))] + let native_executable_path = PathBuf::from("wasm.out"); + #[cfg(windows)] + let native_executable_path = PathBuf::from("wasm.exe"); + Self { + current_dir: std::env::current_dir().unwrap(), + wasmer_path: get_wasmer_path(), + wasm_path: PathBuf::from(create_exe_test_wasm_path()), + native_executable_path, + compiler: Compiler::Cranelift, + } + } +} + +impl WasmerCreateExe { + fn run(&self) -> anyhow::Result<()> { + let output = Command::new(&self.wasmer_path) + .current_dir(&self.current_dir) + .arg("create-exe") + .arg(&self.wasm_path.canonicalize()?) + .arg(&self.compiler.to_flag()) + .arg("-o") + .arg(&self.native_executable_path) + .output()?; + + if !output.status.success() { + bail!( + "wasmer create-exe failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + Ok(()) + } +} + +#[test] +fn create_exe_works() -> anyhow::Result<()> { + let temp_dir = tempfile::tempdir()?; + let operating_dir: PathBuf = temp_dir.path().to_owned(); + + let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + #[cfg(not(windows))] + let executable_path = operating_dir.join("wasm.out"); + #[cfg(windows)] + let executable_path = operating_dir.join("wasm.exe"); + + WasmerCreateExe { + current_dir: operating_dir.clone(), + wasm_path: wasm_path.clone(), + native_executable_path: executable_path.clone(), + compiler: Compiler::Cranelift, + ..Default::default() + } + .run() + .context("Failed to create-exe wasm with Wasmer")?; + + let result = run_code( + &operating_dir, + &executable_path, + &["--eval".to_string(), "function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));".to_string()], + ) + .context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["\"Hello, World\""],); + + Ok(()) +} + +#[test] +fn create_exe_works_with_file() -> anyhow::Result<()> { + let temp_dir = tempfile::tempdir()?; + let operating_dir: PathBuf = temp_dir.path().to_owned(); + + let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + #[cfg(not(windows))] + let executable_path = operating_dir.join("wasm.out"); + #[cfg(windows)] + let executable_path = operating_dir.join("wasm.exe"); + + WasmerCreateExe { + current_dir: operating_dir.clone(), + wasm_path: wasm_path.clone(), + native_executable_path: executable_path.clone(), + compiler: Compiler::Cranelift, + ..Default::default() + } + .run() + .context("Failed to create-exe wasm with Wasmer")?; + + { + let mut f = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(operating_dir.join("test.js"))?; + f.write_all(JS_TEST_SRC_CODE)?; + } + + // test with `--dir` + let result = run_code( + &operating_dir, + &executable_path, + &[ + "--dir=.".to_string(), + "--script".to_string(), + "test.js".to_string(), + ], + ) + .context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["\"Hello, World\""],); + + // test with `--mapdir` + let result = run_code( + &operating_dir, + &executable_path, + &[ + "--mapdir=abc:.".to_string(), + "--script".to_string(), + "abc/test.js".to_string(), + ], + ) + .context("Failed to run generated executable")?; + let result_lines = result.lines().collect::>(); + assert_eq!(result_lines, vec!["\"Hello, World\""],); + + Ok(()) +} diff --git a/tests/integration/cli/src/object_file_engine_test_c_source.c b/tests/integration/cli/tests/object_file_engine_test_c_source.c similarity index 100% rename from tests/integration/cli/src/object_file_engine_test_c_source.c rename to tests/integration/cli/tests/object_file_engine_test_c_source.c