diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index ec416d9c8..be137ce53 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -11,7 +11,8 @@ use crate::commands::CreateObj; #[cfg(feature = "wast")] use crate::commands::Wast; use crate::commands::{ - Add, Cache, Config, Init, Inspect, List, Login, Publish, Run, SelfUpdate, Validate, Whoami, + Add, Cache, Config, GenCHeader, Init, Inspect, List, Login, Publish, Run, SelfUpdate, Validate, + Whoami, }; use crate::error::PrettyError; use clap::{CommandFactory, ErrorKind, Parser}; @@ -128,6 +129,9 @@ enum WasmerCLIOptions { #[structopt(name = "create-obj", verbatim_doc_comment)] CreateObj(CreateObj), + /// Generate the C static_defs.h header file for the input .wasm module + GenCHeader(GenCHeader), + /// Get various configuration information needed /// to compile programs which use Wasmer Config(Config), @@ -177,6 +181,7 @@ impl WasmerCLIOptions { Self::List(list) => list.execute(), Self::Login(login) => login.execute(), Self::Publish(publish) => publish.execute(), + Self::GenCHeader(gen_heder) => gen_heder.execute(), #[cfg(feature = "wast")] Self::Wast(wast) => wast.execute(), #[cfg(target_os = "linux")] @@ -235,8 +240,8 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> { } else { match command.unwrap_or(&"".to_string()).as_ref() { "add" | "cache" | "compile" | "config" | "create-obj" | "create-exe" | "help" - | "inspect" | "init" | "run" | "self-update" | "validate" | "wast" | "binfmt" - | "list" | "login" | "publish" => WasmerCLIOptions::parse(), + | "gen-c-header" | "inspect" | "init" | "run" | "self-update" | "validate" | "wast" + | "binfmt" | "list" | "login" | "publish" => WasmerCLIOptions::parse(), _ => { WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| { match e.kind() { diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index 8baf6a014..a70d8d42d 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -10,6 +10,7 @@ mod config; mod create_exe; #[cfg(feature = "static-artifact-create")] mod create_obj; +mod gen_c_header; mod init; mod inspect; mod list; @@ -34,8 +35,8 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "wast")] pub use wast::*; pub use { - add::*, cache::*, config::*, init::*, inspect::*, list::*, login::*, publish::*, run::*, - self_update::*, validate::*, whoami::*, + add::*, cache::*, config::*, gen_c_header::*, init::*, inspect::*, list::*, login::*, + publish::*, run::*, self_update::*, validate::*, whoami::*, }; /// The kind of object format to emit. diff --git a/lib/cli/src/commands/create_exe.rs b/lib/cli/src/commands/create_exe.rs index fa7fc2db9..068ef2dc2 100644 --- a/lib/cli/src/commands/create_exe.rs +++ b/lib/cli/src/commands/create_exe.rs @@ -504,7 +504,7 @@ pub(super) fn compile_pirita_into_directory( /// Prefix map used during compilation of object files #[derive(Debug, Default, PartialEq)] -struct PrefixMapCompilation { +pub(crate) struct PrefixMapCompilation { /// Sha256 hashes for the input files input_hashes: BTreeMap, /// Manual prefixes for input files (file:prefix) @@ -638,7 +638,7 @@ impl PrefixMapCompilation { } } - fn hash_for_bytes(bytes: &[u8]) -> String { + pub(crate) fn hash_for_bytes(bytes: &[u8]) -> String { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(bytes); @@ -992,7 +992,7 @@ fn get_module_infos( } /// Create the static_defs.h header files in the /include directory -fn create_header_files_in_dir( +pub(crate) fn create_header_files_in_dir( directory: &Path, entrypoint: &mut Entrypoint, atoms: &[(String, Vec)], diff --git a/lib/cli/src/commands/gen_c_header.rs b/lib/cli/src/commands/gen_c_header.rs new file mode 100644 index 000000000..fa365c85f --- /dev/null +++ b/lib/cli/src/commands/gen_c_header.rs @@ -0,0 +1,69 @@ +use crate::store::CompilerOptions; +use anyhow::Context; +use clap::Parser; +use std::path::PathBuf; +use wasmer::Target; +use wasmer_compiler::Artifact; +use wasmer_types::compilation::symbols::ModuleMetadataSymbolRegistry; + +use super::normalize_path; + +#[derive(Debug, Parser)] +/// The options for the `wasmer gen-c-header` subcommand +pub struct GenCHeader { + /// Input file + #[clap(name = "FILE", parse(from_os_str))] + path: PathBuf, + + /// Prefix hash (default: SHA256 of input .wasm file) + #[clap(long)] + prefix: Option, + + /// Output file + #[clap(name = "OUTPUT PATH", short = 'o', parse(from_os_str))] + output: PathBuf, +} + +impl GenCHeader { + /// Runs logic for the `gen-c-header` subcommand + pub fn execute(&self) -> Result<(), anyhow::Error> { + let path = crate::commands::normalize_path(&format!("{}", self.path.display())); + let file = std::fs::read(&path) + .map_err(|e| anyhow::anyhow!("{e}")) + .with_context(|| anyhow::anyhow!("{path}"))?; + let prefix = match self.prefix.as_deref() { + Some(s) => s.to_string(), + None => crate::commands::PrefixMapCompilation::hash_for_bytes(&file), + }; + let (store, _) = CompilerOptions::default().get_store_for_target(Target::default())?; + let module_name = format!("WASMER_{}_METADATA", prefix.to_uppercase()); + let engine = store.engine(); + let engine_inner = engine.inner(); + let compiler = engine_inner.compiler()?; + let features = engine_inner.features(); + let tunables = store.tunables(); + let (compile_info, _, _, _) = + Artifact::generate_metadata(&file, compiler, tunables, features)?; + let module_info = compile_info.module; + + let metadata_length = 0; + + let header_file_src = crate::c_gen::staticlib_header::generate_header_file( + &prefix, + &module_name, + &module_info, + &ModuleMetadataSymbolRegistry { + prefix: prefix.clone(), + }, + metadata_length as usize, + ); + + let output = normalize_path(&self.output.display().to_string()); + + std::fs::write(&output, &header_file_src) + .map_err(|e| anyhow::anyhow!("{e}")) + .with_context(|| anyhow::anyhow!("{output}"))?; + + Ok(()) + } +} diff --git a/lib/compiler/src/engine/artifact.rs b/lib/compiler/src/engine/artifact.rs index 24bd7a1ba..84828bc60 100644 --- a/lib/compiler/src/engine/artifact.rs +++ b/lib/compiler/src/engine/artifact.rs @@ -412,7 +412,7 @@ impl Artifact { #[allow(clippy::type_complexity)] #[cfg(feature = "static-artifact-create")] /// Generate a compilation - fn generate_metadata<'data>( + pub fn generate_metadata<'data>( data: &'data [u8], compiler: &dyn Compiler, tunables: &dyn Tunables, diff --git a/tests/integration/cli/tests/gen_c_header.rs b/tests/integration/cli/tests/gen_c_header.rs new file mode 100644 index 000000000..bb51ba9d3 --- /dev/null +++ b/tests/integration/cli/tests/gen_c_header.rs @@ -0,0 +1,54 @@ +use std::path::PathBuf; +use std::process::Command; +use wasmer_integration_tests_cli::get_wasmer_path; +use wasmer_integration_tests_cli::C_ASSET_PATH; + +fn create_exe_wabt_path() -> String { + format!("{}/{}", C_ASSET_PATH, "wabt-1.0.37.wasmer") +} + +fn create_exe_python_wasmer() -> String { + format!("{}/{}", C_ASSET_PATH, "python-0.1.0.wasmer") +} + +fn create_exe_test_wasm_path() -> String { + format!("{}/{}", C_ASSET_PATH, "qjs.wasm") +} + +#[test] +fn gen_c_header_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()); + let out_path = temp_dir.path().join("header.h"); + + let _ = Command::new(get_wasmer_path()) + .arg("gen-c-header") + .arg(&wasm_path) + .arg("-o") + .arg(&out_path) + .output() + .unwrap(); + + let file = std::fs::read_to_string(&out_path).expect("no header.h file"); + assert!(file.contains("wasmer_function_6f62a6bc5c8f8e3e12a54e2ecbc5674ccfe1c75f91d8e4dd6ebb3fec422a4d6c_0"), "no wasmer_function_6f62a6bc5c8f8e3e12a54e2ecbc5674ccfe1c75f91d8e4dd6ebb3fec422a4d6c_0 in file"); + + let cmd = Command::new(get_wasmer_path()) + .arg("gen-c-header") + .arg(&wasm_path) + .arg("-o") + .arg(&out_path) + .arg("--prefix") + .arg("abc123") + .output() + .unwrap(); + + let file = std::fs::read_to_string(&out_path).expect("no header.h file"); + assert!( + file.contains("wasmer_function_abc123_0"), + "no wasmer_function_abc123_0 in file" + ); + + Ok(()) +}