diff --git a/Cargo.lock b/Cargo.lock index dddf556ddd3..a6baed2244f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bincode" version = "1.3.3" @@ -757,6 +763,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -767,6 +782,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1112,6 +1138,18 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http_req" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6cd45a270dff33553602fd84beb02c89460ee32db638715f10d9060389fd6a" +dependencies = [ + "rustls", + "unicase", + "webpki", + "webpki-roots", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1916,6 +1954,21 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rkyv" version = "0.7.39" @@ -1993,6 +2046,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rustversion" version = "1.0.8" @@ -2026,6 +2092,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sdl2" version = "0.35.2" @@ -2219,6 +2295,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2631,6 +2713,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.2" @@ -2655,6 +2746,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35abed4630bb800f02451a7428205d1f37b8e125001471bfab259beee6a587ed" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "vec_map" version = "0.8.2" @@ -2923,9 +3020,12 @@ dependencies = [ "bytesize", "cfg-if 1.0.0", "colored 2.0.0", + "dirs", "distance", "fern", + "http_req", "log", + "serde_json", "structopt", "tempfile", "unix_mode", @@ -3435,6 +3535,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + [[package]] name = "which" version = "3.1.1" diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 3b206ba90ab..e5d545a3660 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -52,6 +52,9 @@ cfg-if = "1.0" fern = { version = "0.6", features = ["colored"], optional = true } log = { version = "0.4", optional = true } tempfile = "3" +http_req = { version="^0.8", default-features = false, features = ["rust-tls"], optional = true } +dirs = { version = "4.0", optional = true } +serde_json = { version = "1.0", optional = true } [target.'cfg(target_os = "linux")'.dependencies] unix_mode = "0.1.3" @@ -62,6 +65,7 @@ unix_mode = "0.1.3" default = [ "wat", "wast", + "http", "cache", "wasi", "emscripten", @@ -131,3 +135,9 @@ enable-serde = [ "wasmer-types/enable-serde", "wasmer-wasi/enable-serde", ] + +http = [ + "http_req", + "dirs", + "serde_json", +] diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index e2ada9eb064..085a24edd09 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -24,6 +24,24 @@ const WASMER_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_create_exe_main.c"); #[cfg(feature = "static-artifact-create")] const WASMER_STATIC_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_static_create_exe_main.c"); +#[derive(Debug, Clone)] +struct CrossCompile { + /// Cross-compilation library path. + library_path: Option, + + /// Cross-compilation tarball library path. + tarball: Option, + + /// Specify `zig` binary path + zig_binary_path: Option, +} + +struct CrossCompileSetup { + target: Triple, + zig_binary_path: PathBuf, + library: PathBuf, +} + #[derive(Debug, StructOpt)] /// The options for the `wasmer create-exe` subcommand pub struct CreateExe { @@ -39,6 +57,19 @@ pub struct CreateExe { #[structopt(long = "target")] target_triple: Option, + // Cross-compile with `zig` + /// Cross-compilation library path. + #[structopt(long = "library-path")] + library_path: Option, + + /// Cross-compilation tarball library path. + #[structopt(long = "tarball")] + tarball: Option, + + /// Specify `zig` binary path + #[structopt(long = "zig-binary-path")] + zig_binary_path: Option, + /// Object format options /// /// This flag accepts two options: `symbols` or `serialized`. @@ -70,6 +101,27 @@ pub struct CreateExe { impl CreateExe { /// Runs logic for the `compile` subcommand pub fn execute(&self) -> Result<()> { + /* Making library_path, tarball zig_binary_path flags require that target_triple flag + * is set cannot be encoded with structopt, so we have to perform cli flag validation + * manually here */ + let cross_compile: Option = if self.target_triple.is_none() + && (self.library_path.is_some() + || self.tarball.is_some() + || self.zig_binary_path.is_some()) + { + return Err(anyhow!( + "To cross-compile an executable, you must specify a target triple with --target" + )); + } else if self.target_triple.is_some() { + Some(CrossCompile { + library_path: self.library_path.clone(), + zig_binary_path: self.zig_binary_path.clone(), + tarball: self.tarball.clone(), + }) + } else { + None + }; + let target = self .target_triple .as_ref() @@ -81,22 +133,117 @@ impl CreateExe { .fold(CpuFeature::set(), |a, b| a | b); // Cranelift requires SSE2, so we have this "hack" for now to facilitate // usage - features |= CpuFeature::SSE2; + if target_triple.architecture == Architecture::X86_64 { + features |= CpuFeature::SSE2; + } Target::new(target_triple.clone(), features) }) .unwrap_or_default(); - let (store, compiler_type) = self.compiler.get_store_for_target(target.clone())?; - let object_format = self.object_format.unwrap_or(ObjectFormat::Symbols); - - println!("Compiler: {}", compiler_type.to_string()); - println!("Target: {}", target.triple()); - println!("Format: {:?}", object_format); + let object_format = self.object_format.unwrap_or(ObjectFormat::Symbols); 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)?; + let cross_compilation: Option = if let Some(mut cross_subc) = + cross_compile.or_else(|| { + if self.target_triple.is_some() { + Some(CrossCompile { + library_path: None, + tarball: None, + zig_binary_path: None, + }) + } else { + None + } + }) { + if let ObjectFormat::Serialized = object_format { + return Err(anyhow!( + "Cross-compilation with serialized object format is not implemented." + )); + } + + let target = if let Some(target_triple) = self.target_triple.clone() { + target_triple + } else { + return Err(anyhow!( + "To cross-compile an executable, you must specify a target triple with --target" + )); + }; + if let Some(tarball_path) = cross_subc.tarball.as_mut() { + if tarball_path.is_relative() { + *tarball_path = starting_cd.join(&tarball_path); + if !tarball_path.exists() { + return Err(anyhow!( + "Tarball path `{}` does not exist.", + tarball_path.display() + )); + } else if tarball_path.is_dir() { + return Err(anyhow!( + "Tarball path `{}` is a directory.", + tarball_path.display() + )); + } + } + } + let zig_binary_path = + find_zig_binary(cross_subc.zig_binary_path.as_ref().and_then(|p| { + if p.is_absolute() { + p.canonicalize().ok() + } else { + starting_cd.join(p).canonicalize().ok() + } + }))?; + let library = if let Some(v) = cross_subc.library_path.clone() { + v + } else { + { + let libwasmer_path = if self + .target_triple + .clone() + .unwrap_or(Triple::host()) + .operating_system + == wasmer_types::OperatingSystem::Windows + { + "lib/wasmer.lib" + } else { + "lib/libwasmer.a" + }; + let filename = if let Some(local_tarball) = cross_subc.tarball { + let files = untar(local_tarball)?; + files.into_iter().find(|f| f.contains(libwasmer_path)).ok_or_else(|| { + anyhow!("Could not find libwasmer for {} target in the provided tarball path.", target)})? + } else { + #[cfg(feature = "http")] + { + let release = http_fetch::get_latest_release()?; + let tarball = http_fetch::download_release(release, target.clone())?; + let files = untar(tarball)?; + files.into_iter().find(|f| f.contains(libwasmer_path)).ok_or_else(|| { + anyhow!("Could not find libwasmer for {} target in the fetched release from Github: you can download it manually and specify its path with the --cross-compilation-library-path LIBRARY_PATH flag.", target)})? + } + #[cfg(not(feature = "http"))] + return Err(anyhow!("This wasmer binary isn't compiled with an HTTP request library (feature flag `http`). To cross-compile, specify the path of the non-native libwasmer or release tarball with the --library-path LIBRARY_PATH or --tarball TARBALL_PATH flag.")); + }; + filename.into() + } + }; + Some(CrossCompileSetup { + target, + zig_binary_path, + library, + }) + } else { + None + }; + + let (store, compiler_type) = self.compiler.get_store_for_target(target.clone())?; + + println!("Compiler: {}", compiler_type.to_string()); + println!("Target: {}", target.triple()); + println!("Format: {:?}", object_format); + #[cfg(not(windows))] let wasm_object_path = PathBuf::from("wasm.o"); #[cfg(windows)] @@ -105,14 +252,25 @@ impl CreateExe { let wasm_module_path = starting_cd.join(&self.path); if let Some(header_path) = self.header.as_ref() { + /* In this case, since a header file is given, the input file is expected to be an + * object created with `create-obj` subcommand */ let header_path = starting_cd.join(&header_path); std::fs::copy(&header_path, Path::new("static_defs.h")) .context("Could not access given header file")?; - link( - output_path, - wasm_module_path, - std::path::Path::new("static_defs.h").into(), - )?; + if let Some(setup) = cross_compilation.as_ref() { + self.compile_zig( + output_path, + wasm_module_path, + std::path::Path::new("static_defs.h").into(), + setup, + )?; + } else { + self.link( + output_path, + wasm_module_path, + std::path::Path::new("static_defs.h").into(), + )?; + } } else { match object_format { ObjectFormat::Serialized => { @@ -127,7 +285,8 @@ impl CreateExe { writer.flush()?; drop(writer); - self.compile_c(wasm_object_path, output_path)?; + let cli_given_triple = self.target_triple.clone(); + self.compile_c(wasm_object_path, cli_given_triple, output_path)?; } #[cfg(not(feature = "static-artifact-create"))] ObjectFormat::Symbols => { @@ -152,35 +311,57 @@ impl CreateExe { &*symbol_registry, metadata_length, ); - /* Write object file with functions */ + // Write object file with functions let object_file_path: std::path::PathBuf = std::path::Path::new("functions.o").into(); let mut writer = BufWriter::new(File::create(&object_file_path)?); obj.write_stream(&mut writer) .map_err(|err| anyhow::anyhow!(err.to_string()))?; writer.flush()?; - /* Write down header file that includes pointer arrays and the deserialize function - * */ + // Write down header file that includes pointer arrays and the deserialize function let mut writer = BufWriter::new(File::create("static_defs.h")?); writer.write_all(header_file_src.as_bytes())?; writer.flush()?; - link( - output_path, - object_file_path, - std::path::Path::new("static_defs.h").into(), - )?; + if let Some(setup) = cross_compilation.as_ref() { + self.compile_zig( + output_path, + object_file_path, + std::path::Path::new("static_defs.h").into(), + setup, + )?; + } else { + self.link( + output_path, + object_file_path, + std::path::Path::new("static_defs.h").into(), + )?; + } } } } - eprintln!( - "✔ Native executable compiled successfully to `{}`.", - self.output.display(), - ); + + if cross_compilation.is_some() { + eprintln!( + "✔ Cross-compiled executable for `{}` target compiled successfully to `{}`.", + target.triple(), + self.output.display(), + ); + } else { + eprintln!( + "✔ Native executable compiled successfully to `{}`.", + self.output.display(), + ); + } Ok(()) } - fn compile_c(&self, wasm_object_path: PathBuf, output_path: PathBuf) -> anyhow::Result<()> { + fn compile_c( + &self, + wasm_object_path: PathBuf, + target_triple: Option, + output_path: PathBuf, + ) -> anyhow::Result<()> { // write C src to disk let c_src_path = Path::new("wasmer_main.c"); #[cfg(not(windows))] @@ -196,13 +377,13 @@ impl CreateExe { .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()) + run_c_compile(c_src_path, &c_src_obj, 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(), + target: target_triple, ..Default::default() } .run() @@ -210,82 +391,194 @@ impl CreateExe { Ok(()) } -} -#[cfg(feature = "static-artifact-create")] -fn link( - output_path: PathBuf, - object_path: PathBuf, - mut header_code_path: PathBuf, -) -> anyhow::Result<()> { - let linkcode = LinkCode { - object_paths: vec![object_path, "main_obj.obj".into()], - output_path, - ..Default::default() - }; - let c_src_path = Path::new("wasmer_main.c"); - let mut libwasmer_path = get_libwasmer_path()? - .canonicalize() - .context("Failed to find libwasmer")?; - println!("Using libwasmer: {}", libwasmer_path.display()); - let _lib_filename = libwasmer_path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(); - libwasmer_path.pop(); - { - 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_STATIC_MAIN_C_SOURCE)?; - } + fn compile_zig( + &self, + output_path: PathBuf, + object_path: PathBuf, + mut header_code_path: PathBuf, + setup: &CrossCompileSetup, + ) -> anyhow::Result<()> { + let c_src_path = Path::new("wasmer_main.c"); + let CrossCompileSetup { + ref target, + ref zig_binary_path, + ref library, + } = setup; + let mut libwasmer_path = library.to_path_buf(); + + println!("Library Path: {}", libwasmer_path.display()); + /* Cross compilation is only possible with zig */ + println!("Using zig binary: {}", zig_binary_path.display()); + let zig_triple = triple_to_zig_triple(target); + eprintln!("Using zig target triple: {}", &zig_triple); + + let lib_filename = libwasmer_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + libwasmer_path.pop(); + { + 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_STATIC_MAIN_C_SOURCE)?; + } + + if !header_code_path.is_dir() { + header_code_path.pop(); + } + + if header_code_path.display().to_string().is_empty() { + header_code_path = std::env::current_dir()?; + } - if !header_code_path.is_dir() { - header_code_path.pop(); + /* Compile main function */ + let compilation = { + let mut include_dir = libwasmer_path.clone(); + include_dir.pop(); + include_dir.push("include"); + + let mut cmd = Command::new(zig_binary_path); + let mut cmd_mut: &mut Command = cmd + .arg("cc") + .arg("-target") + .arg(&zig_triple) + .arg(&format!("-L{}", libwasmer_path.display())) + .arg(&format!("-l:{}", lib_filename)) + .arg(&format!("-I{}", include_dir.display())) + .arg(&format!("-I{}", header_code_path.display())); + if !zig_triple.contains("windows") { + cmd_mut = cmd_mut.arg("-lunwind"); + } + cmd_mut + .arg(&object_path) + .arg(&c_src_path) + .arg("-o") + .arg(&output_path) + .output() + .context("Could not execute `zig`")? + }; + if !compilation.status.success() { + return Err(anyhow::anyhow!(String::from_utf8_lossy( + &compilation.stderr + ) + .to_string())); + } + Ok(()) } - /* Compile main function */ - let compilation = Command::new("cc") - .arg("-c") - .arg(&c_src_path) - .arg(if linkcode.optimization_flag.is_empty() { - "-O2" - } else { - linkcode.optimization_flag.as_str() - }) - .arg(&format!("-L{}", libwasmer_path.display())) - .arg(&format!("-I{}", get_wasmer_include_directory()?.display())) - //.arg(&format!("-l:{}", lib_filename)) - .arg("-lwasmer") - // Add libraries required per platform. - // We need userenv, sockets (Ws2_32), advapi32 for some system calls and bcrypt for random numbers. - //#[cfg(windows)] - // .arg("-luserenv") - // .arg("-lWs2_32") - // .arg("-ladvapi32") - // .arg("-lbcrypt") - // On unix we need dlopen-related symbols, libmath for a few things, and pthreads. - //#[cfg(not(windows))] - .arg("-ldl") - .arg("-lm") - .arg("-pthread") - .arg(&format!("-I{}", header_code_path.display())) - .arg("-v") - .arg("-o") - .arg("main_obj.obj") - .output()?; - if !compilation.status.success() { - return Err(anyhow::anyhow!(String::from_utf8_lossy( - &compilation.stderr - ) - .to_string())); + #[cfg(feature = "static-artifact-create")] + fn link( + &self, + output_path: PathBuf, + object_path: PathBuf, + mut header_code_path: PathBuf, + ) -> anyhow::Result<()> { + let linkcode = LinkCode { + object_paths: vec![object_path, "main_obj.obj".into()], + output_path, + ..Default::default() + }; + let c_src_path = Path::new("wasmer_main.c"); + let mut libwasmer_path = get_libwasmer_path()? + .canonicalize() + .context("Failed to find libwasmer")?; + + println!("Using libwasmer file: {}", libwasmer_path.display()); + + let lib_filename = libwasmer_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + libwasmer_path.pop(); + { + 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_STATIC_MAIN_C_SOURCE)?; + } + + if !header_code_path.is_dir() { + header_code_path.pop(); + } + + if header_code_path.display().to_string().is_empty() { + header_code_path = std::env::current_dir()?; + } + + /* Compile main function */ + let compilation = { + Command::new("cc") + .arg("-c") + .arg(&c_src_path) + .arg(if linkcode.optimization_flag.is_empty() { + "-O2" + } else { + linkcode.optimization_flag.as_str() + }) + .arg(&format!("-L{}", libwasmer_path.display())) + .arg(&format!("-I{}", get_wasmer_include_directory()?.display())) + .arg(&format!("-l:{}", lib_filename)) + //.arg("-lwasmer") + // Add libraries required per platform. + // We need userenv, sockets (Ws2_32), advapi32 for some system calls and bcrypt for random numbers. + //#[cfg(windows)] + // .arg("-luserenv") + // .arg("-lWs2_32") + // .arg("-ladvapi32") + // .arg("-lbcrypt") + // On unix we need dlopen-related symbols, libmath for a few things, and pthreads. + //#[cfg(not(windows))] + .arg("-ldl") + .arg("-lm") + .arg("-pthread") + .arg(&format!("-I{}", header_code_path.display())) + .arg("-v") + .arg("-o") + .arg("main_obj.obj") + .output()? + }; + if !compilation.status.success() { + return Err(anyhow::anyhow!(String::from_utf8_lossy( + &compilation.stderr + ) + .to_string())); + } + linkcode.run().context("Failed to link objects together")?; + Ok(()) } - linkcode.run().context("Failed to link objects together")?; - Ok(()) +} + +fn triple_to_zig_triple(target_triple: &Triple) -> String { + let arch = match target_triple.architecture { + wasmer_types::Architecture::X86_64 => "x86_64".into(), + wasmer_types::Architecture::Aarch64(wasmer_types::Aarch64Architecture::Aarch64) => { + "aarch64".into() + } + v => v.to_string(), + }; + let os = match target_triple.operating_system { + wasmer_types::OperatingSystem::Linux => "linux".into(), + wasmer_types::OperatingSystem::Darwin => "macos".into(), + wasmer_types::OperatingSystem::Windows => "windows".into(), + v => v.to_string(), + }; + let env = match target_triple.environment { + wasmer_types::Environment::Musl => "musl", + wasmer_types::Environment::Gnu => "gnu", + wasmer_types::Environment::Msvc => "msvc", + _ => "none", + }; + format!("{}-{}-{}", arch, os, env) } fn get_wasmer_dir() -> anyhow::Result { @@ -452,3 +745,247 @@ impl LinkCode { Ok(()) } } + +#[cfg(feature = "http")] +mod http_fetch { + use anyhow::{anyhow, Context, Result}; + use http_req::{ + request::Request, + response::{Response, StatusCode}, + uri::Uri, + }; + use std::convert::TryFrom; + + pub fn get_latest_release() -> Result { + let mut writer = Vec::new(); + let uri = Uri::try_from("https://api.github.com/repos/wasmerio/wasmer/releases").unwrap(); + + let response = Request::new(&uri) + .header("User-Agent", "wasmer") + .header("Accept", "application/vnd.github.v3+json") + .timeout(Some(std::time::Duration::new(30, 0))) + .send(&mut writer) + .map_err(anyhow::Error::new) + .context("Could not lookup wasmer repository on Github.")?; + + if response.status_code() != StatusCode::new(200) { + return Err(anyhow!( + "Github API replied with non-200 status code: {}", + response.status_code() + )); + } + + let v: std::result::Result = serde_json::from_reader(&*writer); + let mut response = v.map_err(anyhow::Error::new)?; + + if let Some(releases) = response.as_array_mut() { + releases.retain(|r| { + r["tag_name"].is_string() && !r["tag_name"].as_str().unwrap().is_empty() + }); + releases.sort_by_cached_key(|r| r["tag_name"].as_str().unwrap_or_default().to_string()); + if let Some(latest) = releases.pop() { + return Ok(latest); + } + } + + Err(anyhow!( + "Could not get expected Github API response.\n\nReason: response format is not recognized:\n{:#?}", "" + )) + } + + pub fn download_release( + mut release: serde_json::Value, + target_triple: wasmer::Triple, + ) -> Result { + if let Some(assets) = release["assets"].as_array_mut() { + assets.retain(|a| { + if let Some(name) = a["name"].as_str() { + match target_triple.architecture { + wasmer_types::Architecture::X86_64 => { + name.contains("x86_64") || name.contains("amd64") + } + wasmer_types::Architecture::Aarch64( + wasmer_types::Aarch64Architecture::Aarch64, + ) => name.contains("arm64") || name.contains("aarch64"), + _ => false, + } + } else { + false + } + }); + assets.retain(|a| { + if let Some(name) = a["name"].as_str() { + match target_triple.vendor { + wasmer_types::Vendor::Apple => { + name.contains("apple") + || name.contains("macos") + || name.contains("darwin") + } + wasmer_types::Vendor::Pc => name.contains("windows"), + _ => true, + } + } else { + false + } + }); + assets.retain(|a| { + if let Some(name) = a["name"].as_str() { + match target_triple.operating_system { + wasmer_types::OperatingSystem::Darwin => { + name.contains("apple") + || name.contains("darwin") + || name.contains("macos") + } + wasmer_types::OperatingSystem::Windows => name.contains("windows"), + wasmer_types::OperatingSystem::Linux => name.contains("linux"), + _ => false, + } + } else { + false + } + }); + assets.retain(|a| { + if let Some(name) = a["name"].as_str() { + match target_triple.environment { + wasmer_types::Environment::Musl => name.contains("musl"), + _ => !name.contains("musl"), + } + } else { + false + } + }); + + if assets.len() == 1 { + let browser_download_url = + if let Some(url) = assets[0]["browser_download_url"].as_str() { + url.to_string() + } else { + return Err(anyhow!( + "Could not get download url from Github API response." + )); + }; + let filename = browser_download_url + .split('/') + .last() + .unwrap_or("output") + .to_string(); + let mut file = std::fs::File::create(&filename)?; + println!("Downloading {} to {}", browser_download_url, &filename); + let download_thread: std::thread::JoinHandle> = + std::thread::spawn(move || { + let uri = Uri::try_from(browser_download_url.as_str())?; + let mut response = Request::new(&uri) + .header("User-Agent", "wasmer") + .send(&mut file) + .map_err(anyhow::Error::new) + .context("Could not lookup wasmer artifact on Github.")?; + if response.status_code() == StatusCode::new(302) { + let redirect_uri = + Uri::try_from(response.headers().get("Location").unwrap().as_str()) + .unwrap(); + response = Request::new(&redirect_uri) + .header("User-Agent", "wasmer") + .send(&mut file) + .map_err(anyhow::Error::new) + .context("Could not lookup wasmer artifact on Github.")?; + } + Ok(response) + }); + let _response = download_thread + .join() + .expect("Could not join downloading thread"); + return Ok(filename.into()); + } + } + Err(anyhow!("Could not get release artifact.")) + } +} + +fn untar(tarball: std::path::PathBuf) -> Result> { + let files = std::process::Command::new("tar") + .arg("-tf") + .arg(&tarball) + .output() + .expect("failed to execute process") + .stdout; + + let files_s = String::from_utf8(files)?; + + let files = files_s + .lines() + .filter(|p| !p.ends_with('/')) + .map(|s| s.to_string()) + .collect::>(); + + let _output = std::process::Command::new("tar") + .arg("-xf") + .arg(&tarball) + .output() + .expect("failed to execute process"); + Ok(files) +} + +fn find_zig_binary(path: Option) -> Result { + use std::env::split_paths; + use std::ffi::OsStr; + #[cfg(unix)] + use std::os::unix::ffi::OsStrExt; + let path_var = std::env::var("PATH").unwrap_or_default(); + #[cfg(unix)] + let system_path_var = std::process::Command::new("getconf") + .args(&["PATH"]) + .output() + .map(|output| output.stdout) + .unwrap_or_default(); + let retval = if let Some(p) = path { + if p.exists() { + p + } else { + return Err(anyhow!("Could not find `zig` binary in {}.", p.display())); + } + } else { + let mut retval = None; + for mut p in split_paths(&path_var).chain(split_paths( + #[cfg(unix)] + { + &OsStr::from_bytes(&system_path_var[..]) + }, + #[cfg(not(unix))] + { + OsStr::new("") + }, + )) { + p.push("zig"); + if p.exists() { + retval = Some(p); + break; + } + } + retval.ok_or_else(|| anyhow!("Could not find `zig` binary in PATH."))? + }; + + let version = std::process::Command::new(&retval) + .arg("version") + .output() + .with_context(|| { + format!( + "Could not execute `zig` binary at path `{}`", + retval.display() + ) + })? + .stdout; + let version_slice = if let Some(pos) = version + .iter() + .position(|c| !(c.is_ascii_digit() || (*c == b'.'))) + { + &version[..pos] + } else { + &version[..] + }; + + if version_slice < b"0.10.0".as_ref() { + Err(anyhow!("`zig` binary in PATH (`{}`) is not a new enough version (`{}`): please use version `0.10.0` or newer.", retval.display(), String::from_utf8_lossy(version_slice))) + } else { + Ok(retval) + } +} diff --git a/lib/cli/src/commands/create_obj.rs b/lib/cli/src/commands/create_obj.rs index 9acff206182..2b13bca8e80 100644 --- a/lib/cli/src/commands/create_obj.rs +++ b/lib/cli/src/commands/create_obj.rs @@ -69,7 +69,9 @@ impl CreateObj { .fold(CpuFeature::set(), |a, b| a | b); // Cranelift requires SSE2, so we have this "hack" for now to facilitate // usage - features |= CpuFeature::SSE2; + if target_triple.architecture == Architecture::X86_64 { + features |= CpuFeature::SSE2; + } Target::new(target_triple.clone(), features) }) .unwrap_or_default(); diff --git a/lib/types/src/compilation/target.rs b/lib/types/src/compilation/target.rs index 9b71c87288f..9ba6d7dd85f 100644 --- a/lib/types/src/compilation/target.rs +++ b/lib/types/src/compilation/target.rs @@ -12,8 +12,8 @@ use enumset::{EnumSet, EnumSetType}; use std::str::FromStr; use std::string::{String, ToString}; pub use target_lexicon::{ - Architecture, BinaryFormat, CallingConvention, Endianness, OperatingSystem, PointerWidth, - Triple, + Aarch64Architecture, Architecture, BinaryFormat, CallingConvention, Endianness, Environment, + OperatingSystem, PointerWidth, Triple, Vendor, }; /// The nomenclature is inspired by the [`cpuid` crate]. diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 20f91736d55..3fcfbe9504d 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -71,8 +71,8 @@ mod value; mod vmoffsets; pub use crate::compilation::target::{ - Architecture, BinaryFormat, CallingConvention, CpuFeature, Endianness, OperatingSystem, - PointerWidth, Target, Triple, + Aarch64Architecture, Architecture, BinaryFormat, CallingConvention, CpuFeature, Endianness, + Environment, OperatingSystem, PointerWidth, Target, Triple, Vendor, }; pub use crate::serialize::{MetadataHeader, SerializableCompilation, SerializableModule}; pub use error::{