diff --git a/Cargo.lock b/Cargo.lock index 32390666e..43d622586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,38 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" +[[package]] +name = "camino" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406c859255d568f4f742b3146d51851f3bfd49f734a2c289d9107c4395ee0062" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.14", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -4092,6 +4124,7 @@ dependencies = [ "anyhow", "atty", "bytesize", + "cargo_metadata", "cfg-if 1.0.0", "chrono", "clap 3.2.23", diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 7450a39e5..1679fb3d4 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -229,8 +229,8 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> { WasmerCLIOptions::Run(Run::from_binfmt_args()) } else { match command.unwrap_or(&"".to_string()).as_ref() { - "add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run" - | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => { + "add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "init" + | "run" | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => { WasmerCLIOptions::parse() } _ => { diff --git a/lib/cli/src/commands/init.rs b/lib/cli/src/commands/init.rs index ee6b03ed9..78c081d40 100644 --- a/lib/cli/src/commands/init.rs +++ b/lib/cli/src/commands/init.rs @@ -1,3 +1,4 @@ +use cargo_metadata::{CargoOpt, MetadataCommand}; use clap::Parser; use std::collections::HashMap; use std::path::Path; @@ -15,9 +16,18 @@ pub struct Init { /// Initialize an empty wapm.toml #[clap(long, name = "empty")] pub empty: bool, - /// Path to the directory to init the wapm.toml in - #[clap(long, name = "dir", env = "DIR", parse(from_os_str))] - pub dir: Option, + /// If the `manifest-dir` contains a Cargo.toml, use that file to initialize the wapm.toml + #[clap(long, name = "manifest-dir")] + pub manifest_dir: Option, + /// Directory of the output file name. wasmer init will error in the target dir already contains a wapm.toml + #[clap(long, short = 'o', name = "out")] + pub out: Option, + /// Force overwriting the wapm.toml, even if it already exists + #[clap(long, name = "overwrite")] + pub overwrite: bool, + /// Don't display debug output + #[clap(long, name = "quiet")] + pub quiet: bool, /// Add default dependencies for common packages (currently supported: `python`, `js`) #[clap(long, name = "template")] pub template: Option, @@ -36,10 +46,26 @@ enum BinOrLib { Empty, } +// minimal version of the Cargo.toml [package] section +struct MiniCargoTomlPackage { + name: String, + version: semver::Version, + description: Option, + homepage: Option, + repository: Option, + license: Option, + readme: Option, + license_file: Option, + #[allow(dead_code)] + workspace_root: PathBuf, + #[allow(dead_code)] + build_dir: PathBuf, +} + impl Init { /// `wasmer init` execution pub fn execute(&self) -> Result<(), anyhow::Error> { - let target_file = match self.dir.as_ref() { + let target_file = match self.out.as_ref() { None => std::env::current_dir()?.join("wapm.toml"), Some(s) => { let _ = std::fs::create_dir_all(s); @@ -47,14 +73,46 @@ impl Init { } }; - if target_file.exists() { + if target_file.exists() && !self.overwrite { return Err(anyhow::anyhow!( "wapm project already initialized in {}", target_file.display() )); } - let package_name = match self.package_name.as_ref() { + // See if the directory has a Cargo.toml file, if yes, copy the license / readme, etc. + let cargo_toml = MetadataCommand::new() + .manifest_path(match self.manifest_dir.as_ref() { + Some(s) => { + if format!("{}", s.display()).ends_with("Cargo.toml") { + s.clone() + } else { + s.join("Cargo.toml") + } + } + None => Path::new("./Cargo.toml").to_path_buf(), + }) + .features(CargoOpt::AllFeatures) + .exec() + .ok(); + + let cargo_toml = cargo_toml.and_then(|metadata| { + let package = metadata.root_package()?; + Some(MiniCargoTomlPackage { + name: package.name.clone(), + version: package.version.clone(), + description: package.description.clone(), + homepage: package.homepage.clone(), + repository: package.repository.clone(), + license: package.license.clone(), + readme: package.readme.clone().map(|s| s.into_std_path_buf()), + license_file: package.license_file.clone().map(|f| f.into_std_path_buf()), + workspace_root: metadata.workspace_root.into_std_path_buf(), + build_dir: metadata.target_directory.into_std_path_buf(), + }) + }); + + let package_name = match cargo_toml.as_ref() { None => { if let Some(parent) = target_file .parent() @@ -70,80 +128,23 @@ impl Init { .to_string() } } - Some(s) => s.to_string(), + Some(s) => s.name.clone(), }; - // See if the directory has a Cargo.toml file, if yes, copy the license / readme, etc. - let cargo_toml = target_file.parent().and_then(|p| { - let file = std::fs::read_to_string(p.join("Cargo.toml")).ok()?; - file.parse::().ok() - }); + let module_name = package_name.split('/').next().unwrap_or(&package_name); let version = cargo_toml .as_ref() - .and_then(|toml| { - semver::Version::parse(toml.get("package")?.as_table()?.get("version")?.as_str()?) - .ok() - }) + .map(|t| t.version.clone()) .unwrap_or_else(|| semver::Version::parse("0.1.0").unwrap()); - - let license = cargo_toml.as_ref().and_then(|toml| { - Some( - toml.get("package")? - .as_table()? - .get("license")? - .as_str()? - .to_string(), - ) - }); - - let license_file = cargo_toml.as_ref().and_then(|toml| { - Some( - Path::new( - toml.get("package")? - .as_table()? - .get("license_file")? - .as_str()?, - ) - .to_path_buf(), - ) - }); - - let readme = cargo_toml.as_ref().and_then(|toml| { - Some(Path::new(toml.get("package")?.as_table()?.get("readme")?.as_str()?).to_path_buf()) - }); - - let repository = cargo_toml.as_ref().and_then(|toml| { - Some( - toml.get("package")? - .as_table()? - .get("repository")? - .as_str()? - .to_string(), - ) - }); - - let homepage = cargo_toml.as_ref().and_then(|toml| { - Some( - toml.get("package")? - .as_table()? - .get("homepage")? - .as_str()? - .to_string(), - ) - }); - + let license = cargo_toml.as_ref().and_then(|t| t.license.clone()); + let license_file = cargo_toml.as_ref().and_then(|t| t.license_file.clone()); + let readme = cargo_toml.as_ref().and_then(|t| t.readme.clone()); + let repository = cargo_toml.as_ref().and_then(|t| t.repository.clone()); + let homepage = cargo_toml.as_ref().and_then(|t| t.homepage.clone()); let description = cargo_toml .as_ref() - .and_then(|toml| { - Some( - toml.get("package")? - .as_table()? - .get("description")? - .as_str()? - .to_string(), - ) - }) - .unwrap_or_else(|| format!("Description of the package {package_name}")); + .and_then(|t| t.description.clone()) + .unwrap_or(format!("Description for package {module_name}")); let bin_or_lib = match (self.empty, self.bin, self.lib) { (true, true, _) | (true, _, true) => { @@ -162,15 +163,16 @@ impl Init { _ => BinOrLib::Bin, }; - let module_name = package_name.split('/').next().unwrap_or(&package_name); - let modules = vec![wapm_toml::Module { name: module_name.to_string(), - source: if cargo_toml.is_some() { - Path::new(&format!("target/wasm32-wasi/release/{module_name}.wasm")).to_path_buf() - } else { - Path::new(&format!("{module_name}.wasm")).to_path_buf() - }, + source: cargo_toml + .as_ref() + .map(|p| { + p.build_dir + .join("release") + .join(&format!("{module_name}.wasm")) + }) + .unwrap_or_else(|| Path::new(&format!("{module_name}.wasm")).to_path_buf()), kind: None, abi: wapm_toml::Abi::Wasi, bindings: match bin_or_lib { @@ -300,12 +302,13 @@ impl Init { cargo_wapm_stdout.lines().count() == 1 && cargo_wapm_stdout.contains("cargo wapm"); let should_add_to_cargo_toml = cargo_toml.is_some() && cargo_wapm_present; - if !cargo_wapm_present && cargo_toml.is_some() { + if !cargo_wapm_present && cargo_toml.is_some() && !self.quiet { eprintln!( "Note: you seem to have a Cargo.toml file, but you haven't installed `cargo wapm`." ); eprintln!("You can build and release Rust projects directly with `cargo wapm publish`: https://crates.io/crates/cargo-wapm"); eprintln!("Install it with:"); + eprintln!(); eprintln!(" cargo install cargo-wapm"); eprintln!(); } @@ -327,16 +330,18 @@ impl Init { let old_cargo = std::fs::read_to_string(&cargo_toml_path).unwrap(); - if old_cargo.contains("metadata.wapm") { + if old_cargo.contains("metadata.wapm") && !self.overwrite { return Err(anyhow::anyhow!( "wapm project already initialized in Cargo.toml file" )); } else { - eprintln!("You have cargo-wapm installed, added metadata to Cargo.toml instead of wapm.toml"); - eprintln!("Build and publish your package with:"); - eprintln!(); - eprintln!(" cargo wapm publish"); - eprintln!(); + if !self.quiet { + eprintln!("You have cargo-wapm installed, added metadata to Cargo.toml instead of wapm.toml"); + eprintln!("Build and publish your package with:"); + eprintln!(); + eprintln!(" cargo wapm publish"); + eprintln!(); + } std::fs::write( &cargo_toml_path, &format!("{old_cargo}\r\n\r\n# See more keys and definitions at https://docs.wasmer.io/ecosystem/wapm/manifest\r\n\r\n{toml_string}"),