From 25bd11b51e7975d896ab42dffaf89f7f44c847f5 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Thu, 19 Sep 2024 18:43:02 +0300 Subject: [PATCH] feature(cargo-miden): support building Wasm component from a Cargo project --- Cargo.lock | 132 +-- Cargo.toml | 2 + midenc-debug/Cargo.toml | 4 +- tools/cargo-miden/Cargo.toml | 6 +- tools/cargo-miden/src/commands/mod.rs | 3 + .../src/{ => commands}/new_project.rs | 0 .../src/{build.rs => compile_masm.rs} | 2 +- tools/cargo-miden/src/config.rs | 789 ------------------ tools/cargo-miden/src/lib.rs | 146 +++- tools/cargo-miden/src/main.rs | 22 +- tools/cargo-miden/src/non_component.rs | 148 ++++ tools/cargo-miden/src/run_cargo_command.rs | 136 --- tools/cargo-miden/src/target.rs | 20 +- tools/cargo-miden/tests/build.rs | 1 + 14 files changed, 365 insertions(+), 1046 deletions(-) create mode 100644 tools/cargo-miden/src/commands/mod.rs rename tools/cargo-miden/src/{ => commands}/new_project.rs (100%) rename tools/cargo-miden/src/{build.rs => compile_masm.rs} (98%) delete mode 100644 tools/cargo-miden/src/config.rs create mode 100644 tools/cargo-miden/src/non_component.rs delete mode 100644 tools/cargo-miden/src/run_cargo_command.rs diff --git a/Cargo.lock b/Cargo.lock index 1690530e1..f5c55b9d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "anymap2" @@ -170,9 +170,9 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -392,9 +392,9 @@ dependencies = [ [[package]] name = "auth-git2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51bd0e4592409df8631ca807716dc1e5caafae5d01ce0157c966c71c7e49c3c" +checksum = "3810b5af212b013fe7302b12d86616c6c39a48e18f2e4b812a5a9e5710213791" dependencies = [ "dirs", "git2", @@ -599,9 +599,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "camino" @@ -614,9 +614,8 @@ dependencies = [ [[package]] name = "cargo-component" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deedd1f20ddb574d9d198d979499f628f2865718190830523cb329e264ef1469" +version = "0.17.0-dev" +source = "git+https://github.com/greenhat/cargo-component?branch=use-as-lib#88b37eeb383b8e030758fd7d0c62f513b0becbac" dependencies = [ "anyhow", "bytes", @@ -641,24 +640,23 @@ dependencies = [ "tempfile", "tokio", "tokio-util", - "toml_edit 0.22.20", + "toml_edit 0.22.21", "url", "wasi-preview1-component-adapter-provider", - "wasm-metadata 0.215.0", + "wasm-metadata 0.216.0", "wasm-pkg-client", - "wasmparser 0.215.0", + "wasmparser 0.216.0", "which", "wit-bindgen-core", "wit-bindgen-rust", - "wit-component 0.215.0", - "wit-parser 0.215.0", + "wit-component 0.216.0", + "wit-parser 0.216.0", ] [[package]] name = "cargo-component-core" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2942b6673d038ed6da6c7a5964372c540253dee1508c55b0f5f7614fb2c6c371" +version = "0.17.0-dev" +source = "git+https://github.com/greenhat/cargo-component?branch=use-as-lib#88b37eeb383b8e030758fd7d0c62f513b0becbac" dependencies = [ "anyhow", "clap", @@ -672,13 +670,13 @@ dependencies = [ "serde 1.0.210", "tokio", "tokio-util", - "toml_edit 0.22.20", + "toml_edit 0.22.21", "unicode-width", "url", "wasm-pkg-client", "windows-sys 0.52.0", - "wit-component 0.215.0", - "wit-parser 0.215.0", + "wit-component 0.216.0", + "wit-parser 0.216.0", ] [[package]] @@ -690,7 +688,7 @@ dependencies = [ "home", "serde 1.0.210", "serde_derive", - "toml_edit 0.22.20", + "toml_edit 0.22.21", ] [[package]] @@ -742,6 +740,7 @@ dependencies = [ "anyhow", "cargo-component", "cargo-component-core", + "cargo-config2", "cargo-generate", "cargo_metadata", "clap", @@ -752,6 +751,7 @@ dependencies = [ "parse_arg", "path-absolutize", "semver 1.0.23", + "tokio", ] [[package]] @@ -826,9 +826,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -2318,9 +2318,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2867,18 +2867,18 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logos" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1ceb190eb9bdeecdd8f1ad6a71d6d632a50905948771718741b5461fb01e13" +checksum = "1c6b6e02facda28ca5fb8dbe4b152496ba3b1bd5a4b40bb2b1b2d8ad74e0f39b" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90be66cb7bd40cb5cc2e9cfaf2d1133b04a3d93b72344267715010a466e0915a" +checksum = "b32eb6b5f26efacd015b000bfc562186472cd9b34bdba3f6b264e2a052676d10" dependencies = [ "beef", "fnv", @@ -2891,9 +2891,9 @@ dependencies = [ [[package]] name = "logos-derive" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45154231e8e96586b39494029e58f12f8ffcb5ecf80333a603a13aa205ea8cbd" +checksum = "3e5d0c5463c911ef55624739fc353238b4e310f0144be1f875dc42fec6bfd5ec" dependencies = [ "logos-codegen", ] @@ -3022,9 +3022,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6fad06fc3af260ed3c4235821daa2132813d993f96d446856036ae97e9606dd" +checksum = "8a69f8362ca496a79c88cf8e5b9b349bf9c6ed49fa867d0548e670afc1f3fca5" dependencies = [ "blake3", "cc", @@ -3130,9 +3130,9 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e7b212b152b69373e89b069a18cb01742ef2c3f9c328e7b24c44e44f022e52" +checksum = "04a128e20400086c9a985f4e5702e438ba781338fb0bdf9acff16d996c640087" dependencies = [ "miden-air", "miden-core", @@ -3954,9 +3954,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -3965,9 +3965,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -3975,9 +3975,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", @@ -3988,9 +3988,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -4135,9 +4135,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -5686,7 +5686,7 @@ dependencies = [ "serde 1.0.210", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.21", ] [[package]] @@ -5711,9 +5711,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap 2.5.0", "serde 1.0.210", @@ -5922,18 +5922,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" @@ -6482,7 +6482,6 @@ dependencies = [ "hashbrown 0.14.5", "indexmap 2.5.0", "semver 1.0.23", - "serde 1.0.210", ] [[package]] @@ -6496,6 +6495,7 @@ dependencies = [ "hashbrown 0.14.5", "indexmap 2.5.0", "semver 1.0.23", + "serde 1.0.210", ] [[package]] @@ -6542,9 +6542,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -6927,29 +6927,29 @@ checksum = "c3d71ec2c97685c7e7460a30e27f955d26b8426e7c2db0ddb55a6e0537141f53" [[package]] name = "wit-bindgen-core" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7e3df01cd43cfa1cb52602e4fc05cb2b62217655f6705639b6953eb0a3fed2" +checksum = "175a2edcf18d1efd1412cb560b895dd09903f4fe73c3a597fbb0075ad86a03e8" dependencies = [ "anyhow", "heck 0.5.0", - "wit-parser 0.215.0", + "wit-parser 0.216.0", ] [[package]] name = "wit-bindgen-rust" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a767d1a8eb4e908bfc53febc48b87ada545703b16fe0148ee7736a29a01417" +checksum = "5a1c119b99edc4fc44ebfd3c461337b18f9db35388358344750b4fcfc788986b" dependencies = [ "anyhow", "heck 0.5.0", "indexmap 2.5.0", "prettyplease", "syn 2.0.77", - "wasm-metadata 0.215.0", + "wasm-metadata 0.216.0", "wit-bindgen-core", - "wit-component 0.215.0", + "wit-component 0.216.0", ] [[package]] @@ -7047,9 +7047,9 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zbus" diff --git a/Cargo.toml b/Cargo.toml index 26bfea1f4..44b314560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,8 @@ cargo-miden = { version = "0.0.7", path = "tools/cargo-miden" } miden-integration-tests = { version = "0.0.0", path = "tests/integration" } wat = "1.0.69" blake3 = "1.5" +tokio = { version = "1.39.2", features = ["rt", "time", "macros"] } +tokio-util = "0.7.11" [profile.dev] lto = false diff --git a/midenc-debug/Cargo.toml b/midenc-debug/Cargo.toml index 72f24e0fd..024b4abb1 100644 --- a/midenc-debug/Cargo.toml +++ b/midenc-debug/Cargo.toml @@ -31,8 +31,8 @@ serde.workspace = true ratatui = "0.28.0" crossterm = { version = "0.28.1", features = ["event-stream"] } tui-input = "0.10" -tokio = { version = "1.39.2", features = ["rt", "time", "macros"] } -tokio-util = "0.7.11" +tokio.workspace = true +tokio-util.workspace = true futures = "0.3.30" signal-hook = "0.3.17" syntect = { version = "5.2.0", default-features = false, features = [ diff --git a/tools/cargo-miden/Cargo.toml b/tools/cargo-miden/Cargo.toml index 5dfdac61d..cbf72a530 100644 --- a/tools/cargo-miden/Cargo.toml +++ b/tools/cargo-miden/Cargo.toml @@ -28,12 +28,14 @@ env_logger.workspace = true log.workspace = true clap.workspace = true anyhow.workspace = true -cargo-component = "0.16" -cargo-component-core = "0.16" +cargo-component = { version = "0.17.0-dev", git = "https://github.com/greenhat/cargo-component", branch = "use-as-lib" } +cargo-component-core = { version = "0.17.0-dev", git = "https://github.com/greenhat/cargo-component", branch = "use-as-lib" } cargo_metadata = "0.18" cargo-generate = "0.22" semver = "1.0.20" parse_arg = "0.1.4" path-absolutize = "3.1.1" +tokio.workspace = true +cargo-config2 = "0.1.24" [dev-dependencies] diff --git a/tools/cargo-miden/src/commands/mod.rs b/tools/cargo-miden/src/commands/mod.rs new file mode 100644 index 000000000..4ef48446b --- /dev/null +++ b/tools/cargo-miden/src/commands/mod.rs @@ -0,0 +1,3 @@ +mod new_project; + +pub use new_project::*; diff --git a/tools/cargo-miden/src/new_project.rs b/tools/cargo-miden/src/commands/new_project.rs similarity index 100% rename from tools/cargo-miden/src/new_project.rs rename to tools/cargo-miden/src/commands/new_project.rs diff --git a/tools/cargo-miden/src/build.rs b/tools/cargo-miden/src/compile_masm.rs similarity index 98% rename from tools/cargo-miden/src/build.rs rename to tools/cargo-miden/src/compile_masm.rs index 5b88a071d..d8e25f76d 100644 --- a/tools/cargo-miden/src/build.rs +++ b/tools/cargo-miden/src/compile_masm.rs @@ -9,7 +9,7 @@ use midenc_session::{ InputFile, OutputType, }; -pub fn build_masm( +pub fn wasm_to_masm( wasm_file_path: &Path, output_folder: &Path, is_bin: bool, diff --git a/tools/cargo-miden/src/config.rs b/tools/cargo-miden/src/config.rs deleted file mode 100644 index 593416639..000000000 --- a/tools/cargo-miden/src/config.rs +++ /dev/null @@ -1,789 +0,0 @@ -//! Module for cargo-miden configuration. -//! -//! This implements an argument parser because `clap` is not -//! designed for parsing unknown or unsupported arguments. -//! -//! See https://github.com/clap-rs/clap/issues/1404 for some -//! discussion around this issue. -//! -//! To properly "wrap" `cargo` commands, we need to be able to -//! detect certain arguments, but not error out if the arguments -//! are otherwise unknown as they will be passed to `cargo` directly. -//! -//! This will allow `cargo-miden` to be used as a drop-in -//! replacement for `cargo` without having to be fully aware of -//! the many subcommands and options that `cargo` supports. -//! -//! What is detected here is the minimal subset of the arguments -//! that `cargo` supports which are necessary for `cargo-miden` -//! to function. - -use std::{collections::BTreeMap, fmt, fmt::Display, path::PathBuf, str::FromStr}; - -use anyhow::{anyhow, bail, Context, Result}; -use cargo_component_core::terminal::{Color, Terminal}; -use parse_arg::{iter_short, match_arg}; -use semver::Version; - -/// Represents a cargo package specifier. -/// -/// See `cargo help pkgid` for more information. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct CargoPackageSpec { - /// The name of the package, e.g. `foo`. - pub name: String, - /// The version of the package, if specified. - pub version: Option, -} - -impl CargoPackageSpec { - /// Creates a new package specifier from a string. - pub fn new(spec: impl Into) -> Result { - let spec = spec.into(); - - // Bail out if the package specifier contains a URL. - if spec.contains("://") { - bail!("URL package specifier `{spec}` is not supported"); - } - - Ok(match spec.split_once('@') { - Some((name, version)) => Self { - name: name.to_string(), - version: Some( - version - .parse() - .with_context(|| format!("invalid package specified `{spec}`"))?, - ), - }, - None => Self { - name: spec, - version: None, - }, - }) - } -} - -impl FromStr for CargoPackageSpec { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - Self::new(s) - } -} - -impl fmt::Display for CargoPackageSpec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{name}", name = self.name)?; - if let Some(version) = &self.version { - write!(f, "@{version}")?; - } - - Ok(()) - } -} - -#[derive(Debug, Clone)] -enum Arg { - Flag { - name: &'static str, - short: Option, - value: bool, - }, - Single { - name: &'static str, - value_name: &'static str, - short: Option, - value: Option, - }, - Multiple { - name: &'static str, - value_name: &'static str, - short: Option, - values: Vec, - }, - Counting { - name: &'static str, - short: Option, - value: usize, - }, -} - -impl Arg { - fn name(&self) -> &'static str { - match self { - Self::Flag { name, .. } - | Self::Single { name, .. } - | Self::Multiple { name, .. } - | Self::Counting { name, .. } => name, - } - } - - fn short(&self) -> Option { - match self { - Self::Flag { short, .. } - | Self::Single { short, .. } - | Self::Multiple { short, .. } - | Self::Counting { short, .. } => *short, - } - } - - fn expects_value(&self) -> bool { - matches!(self, Self::Single { .. } | Self::Multiple { .. }) - } - - fn set_value(&mut self, v: String) -> Result<()> { - match self { - Self::Single { value, .. } => { - if value.is_some() { - bail!("the argument '{self}' cannot be used multiple times"); - } - - *value = Some(v); - Ok(()) - } - Self::Multiple { values, .. } => { - values.push(v); - Ok(()) - } - _ => unreachable!(), - } - } - - fn set_present(&mut self) -> Result<()> { - match self { - Self::Flag { value, .. } => { - if *value { - bail!("the argument '{self}' cannot be used multiple times"); - } - - *value = true; - Ok(()) - } - Self::Counting { value, .. } => { - *value += 1; - Ok(()) - } - _ => unreachable!(), - } - } - - fn take_single(&mut self) -> Option { - match self { - Self::Single { value, .. } => value.take(), - _ => None, - } - } - - fn take_multiple(&mut self) -> Vec { - match self { - Self::Multiple { values, .. } => std::mem::take(values), - _ => Vec::new(), - } - } - - fn count(&self) -> usize { - match self { - Arg::Flag { value, .. } => *value as usize, - Arg::Single { value, .. } => value.is_some() as usize, - Arg::Multiple { values, .. } => values.len(), - Arg::Counting { value, .. } => *value, - } - } - - #[cfg(test)] - fn reset(&mut self) { - match self { - Arg::Flag { value, .. } => *value = false, - Arg::Single { value, .. } => *value = None, - Arg::Multiple { values, .. } => values.clear(), - Arg::Counting { value, .. } => *value = 0, - } - } -} - -impl Display for Arg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{name}", name = self.name())?; - match self { - Self::Single { value_name, .. } | Self::Multiple { value_name, .. } => { - write!(f, " <{value_name}>") - } - _ => Ok(()), - } - } -} - -#[derive(Default, Debug, Clone)] -struct Args { - args: Vec, - long: BTreeMap<&'static str, usize>, - short: BTreeMap, -} - -impl Args { - fn flag(self, name: &'static str, short: Option) -> Self { - self.insert(Arg::Flag { - name, - short, - value: false, - }) - } - - fn single(self, name: &'static str, value_name: &'static str, short: Option) -> Self { - self.insert(Arg::Single { - name, - value_name, - short, - value: None, - }) - } - - fn multiple(self, name: &'static str, value_name: &'static str, short: Option) -> Self { - self.insert(Arg::Multiple { - name, - value_name, - short, - values: Vec::new(), - }) - } - - fn counting(self, name: &'static str, short: Option) -> Self { - self.insert(Arg::Counting { - name, - short, - value: 0, - }) - } - - fn get(&mut self, name: &str) -> Option<&Arg> { - self.long.get(name).copied().map(|i| &self.args[i]) - } - - fn get_mut(&mut self, name: &str) -> Option<&mut Arg> { - self.long.get(name).copied().map(|i| &mut self.args[i]) - } - - fn get_short_mut(&mut self, short: char) -> Option<&mut Arg> { - self.short.get(&short).copied().map(|i| &mut self.args[i]) - } - - fn insert(mut self, arg: Arg) -> Self { - let name = arg.name(); - let short = arg.short(); - - let index = self.args.len(); - self.args.push(arg); - - let prev = self.long.insert(name, index); - assert!(prev.is_none(), "duplicate argument `{name}` provided"); - - if let Some(short) = short { - let prev = self.short.insert(short, index); - assert!(prev.is_none(), "duplicate argument `-{short}` provided"); - } - - self - } - - /// Parses an argument as an option. - /// - /// Returns `Ok(true)` if the argument is an option. - /// - /// Returns `Ok(false)` if the argument is not an option. - fn parse(&mut self, arg: &str, iter: &mut impl Iterator) -> Result { - // Handle short options - if let Some(mut short) = iter_short(arg) { - while let Some(c) = short.next() { - if let Some(option) = self.get_short_mut(c) { - if option.expects_value() { - let value: String = short.parse_remaining(iter).map_err(|_| { - anyhow!("a value is required for '{option}' but none was supplied") - })?; - - // Strip a leading `=` out of the value if present - option - .set_value(value.strip_prefix('=').map(Into::into).unwrap_or(value))?; - return Ok(true); - } - - option.set_present()?; - } - } - - // The argument is an option - return Ok(true); - } - - // Handle long options - if arg.starts_with("--") { - if let Some(option) = self.get_mut(arg.split_once('=').map(|(n, _)| n).unwrap_or(arg)) { - if option.expects_value() { - if let Some(v) = match_arg(option.name(), &arg, iter) { - option.set_value(v.map_err(|_| { - anyhow!("a value is required for '{option}' but none was supplied") - })?)?; - } - } else if option.name() == arg { - option.set_present()?; - } - } - - // The argument is an option - return Ok(true); - } - - // Not an option - Ok(false) - } -} - -/// Represents known cargo arguments. -/// -/// This is a subset of the arguments that cargo supports that -/// are necessary for cargo-miden to function. -#[derive(Default, Debug, Clone, Eq, PartialEq)] -pub struct CargoArguments { - /// The --color argument. - pub color: Option, - /// The (count of) --verbose argument. - pub verbose: usize, - /// The --quiet argument. - pub quiet: bool, - /// The --target argument. - pub targets: Vec, - /// The --manifest-path argument. - pub manifest_path: Option, - /// The --frozen argument. - pub frozen: bool, - /// The --locked argument. - pub locked: bool, - /// The --release argument. - pub release: bool, - /// The --offline argument. - pub offline: bool, - /// The --workspace argument. - pub workspace: bool, - /// The --package argument. - pub packages: Vec, -} - -impl CargoArguments { - /// Determines if network access is allowed based on the configuration. - pub fn network_allowed(&self) -> bool { - !self.frozen && !self.offline - } - - /// Determines if an update to the lock file is allowed based on the configuration. - pub fn lock_update_allowed(&self) -> bool { - !self.frozen && !self.locked - } - - /// Parses the arguments from the environment. - pub fn parse() -> Result { - Self::parse_from(std::env::args().skip(1)) - } - - /// Parses the arguments from an iterator. - pub fn parse_from(iter: impl Iterator) -> Result - where - T: Into, - { - let mut args = Args::default() - .single("--color", "WHEN", Some('c')) - .single("--manifest-path", "PATH", None) - .multiple("--package", "SPEC", Some('p')) - .multiple("--target", "TRIPLE", None) - .flag("--release", Some('r')) - .flag("--frozen", None) - .flag("--locked", None) - .flag("--offline", None) - .flag("--all", None) - .flag("--workspace", None) - .counting("--verbose", Some('v')) - .flag("--quiet", Some('q')); - - let mut iter = iter.map(Into::into).peekable(); - - // Skip the first argument if it is `miden` - if let Some(arg) = iter.peek() { - if arg == "miden" { - iter.next().unwrap(); - } - } - - while let Some(arg) = iter.next() { - // Break out of processing at the first `--` - if arg == "--" { - break; - } - - // Parse options - if args.parse(&arg, &mut iter)? { - continue; - } - } - - Ok(Self { - color: args.get_mut("--color").unwrap().take_single().map(|v| v.parse()).transpose()?, - verbose: args.get("--verbose").unwrap().count(), - quiet: args.get("--quiet").unwrap().count() > 0, - manifest_path: args - .get_mut("--manifest-path") - .unwrap() - .take_single() - .map(PathBuf::from), - targets: args.get_mut("--target").unwrap().take_multiple(), - frozen: args.get("--frozen").unwrap().count() > 0, - locked: args.get("--locked").unwrap().count() > 0, - offline: args.get("--offline").unwrap().count() > 0, - release: args.get("--release").unwrap().count() > 0, - workspace: args.get("--workspace").unwrap().count() > 0 - || args.get("--all").unwrap().count() > 0, - packages: args - .get_mut("--package") - .unwrap() - .take_multiple() - .into_iter() - .map(CargoPackageSpec::new) - .collect::>()?, - }) - } -} - -/// Configuration information for cargo-miden. -/// -/// This is used to configure the behavior of cargo-miden. -#[derive(Debug)] -pub struct Config { - /// The terminal to use. - terminal: Terminal, -} - -impl Config { - /// Create a new `Config` with the given terminal. - pub fn new(terminal: Terminal) -> Result { - Ok(Self { terminal }) - } - - /// Gets a reference to the terminal for writing messages. - pub fn terminal(&self) -> &Terminal { - &self.terminal - } -} - -#[cfg(test)] -mod test { - use std::iter::empty; - - use super::*; - - #[test] - fn it_parses_flags() { - let mut args = Args::default().flag("--flag", Some('f')); - - // Test not the flag - args.parse("--not-flag", &mut empty::()).unwrap(); - let arg = args.get("--flag").unwrap(); - assert_eq!(arg.count(), 0); - - // Test the flag - args.parse("--flag", &mut empty::()).unwrap(); - assert_eq!( - args.parse("--flag", &mut empty::()).unwrap_err().to_string(), - "the argument '--flag' cannot be used multiple times" - ); - let arg = args.get_mut("--flag").unwrap(); - assert_eq!(arg.count(), 1); - arg.reset(); - - // Test not the short flag - args.parse("-rxd", &mut empty::()).unwrap(); - let arg = args.get("--flag").unwrap(); - assert_eq!(arg.count(), 0); - - // Test the short flag - args.parse("-rfx", &mut empty::()).unwrap(); - assert_eq!( - args.parse("-fxz", &mut empty::()).unwrap_err().to_string(), - "the argument '--flag' cannot be used multiple times" - ); - let arg = args.get("--flag").unwrap(); - assert_eq!(arg.count(), 1); - - // Test it prints correctly - assert_eq!(arg.to_string(), "--flag") - } - - #[test] - fn it_parses_single_values() { - let mut args = Args::default().single("--option", "VALUE", Some('o')); - - // Test not the option - args.parse("--not-option", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), None); - - // Test missing value - assert_eq!( - args.parse("--option", &mut empty::()).unwrap_err().to_string(), - "a value is required for '--option ' but none was supplied" - ); - - // Test the option with equals - args.parse("--option=value", &mut empty::()).unwrap(); - assert_eq!( - args.parse("--option=value", &mut empty::()).unwrap_err().to_string(), - "the argument '--option ' cannot be used multiple times" - ); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), Some("value".to_string())); - arg.reset(); - - // Test the option with space - let mut iter = ["value".to_string()].into_iter(); - args.parse("--option", &mut iter).unwrap(); - assert!(iter.next().is_none()); - let mut iter = ["value".to_string()].into_iter(); - assert_eq!( - args.parse("--option", &mut iter).unwrap_err().to_string(), - "the argument '--option ' cannot be used multiple times" - ); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), Some("value".to_string())); - arg.reset(); - - // Test not the short option - args.parse("-xyz", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), None); - - assert_eq!( - args.parse("-fo", &mut empty::()).unwrap_err().to_string(), - "a value is required for '--option ' but none was supplied" - ); - - // Test the short option without equals - args.parse("-xofoo", &mut empty::()).unwrap(); - assert_eq!( - args.parse("-zyobar", &mut iter).unwrap_err().to_string(), - "the argument '--option ' cannot be used multiple times" - ); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), Some(String::from("foo"))); - - // Test the short option with equals - args.parse("-xo=foo", &mut empty::()).unwrap(); - assert_eq!( - args.parse("-zyo=bar", &mut iter).unwrap_err().to_string(), - "the argument '--option ' cannot be used multiple times" - ); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), Some(String::from("foo"))); - - // Test the short option with space - let mut iter = ["value".to_string()].into_iter(); - args.parse("-xo", &mut iter).unwrap(); - let mut iter = ["value".to_string()].into_iter(); - assert_eq!( - args.parse("-zyo", &mut iter).unwrap_err().to_string(), - "the argument '--option ' cannot be used multiple times" - ); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), Some(String::from("value"))); - - // Test it prints correctly - assert_eq!(arg.to_string(), "--option ") - } - - #[test] - fn it_parses_multiple_values() { - let mut args = Args::default().multiple("--option", "VALUE", Some('o')); - - // Test not the option - args.parse("--not-option", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_multiple(), Vec::::new()); - - // Test missing value - assert_eq!( - args.parse("--option", &mut empty::()).unwrap_err().to_string(), - "a value is required for '--option ' but none was supplied" - ); - - // Test the option with equals - args.parse("--option=foo", &mut empty::()).unwrap(); - args.parse("--option=bar", &mut empty::()).unwrap(); - args.parse("--option=baz", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!( - arg.take_multiple(), - vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),] - ); - arg.reset(); - - // Test the option with space - let mut iter = ["foo".to_string()].into_iter(); - args.parse("--option", &mut iter).unwrap(); - assert!(iter.next().is_none()); - let mut iter = ["bar".to_string()].into_iter(); - args.parse("--option", &mut iter).unwrap(); - assert!(iter.next().is_none()); - let mut iter = ["baz".to_string()].into_iter(); - args.parse("--option", &mut iter).unwrap(); - assert!(iter.next().is_none()); - let arg = args.get_mut("--option").unwrap(); - assert_eq!( - arg.take_multiple(), - vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),] - ); - arg.reset(); - - // Test not the short option - args.parse("-xyz", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!(arg.take_single(), None); - - // Test missing shot option value - assert_eq!( - args.parse("-fo", &mut empty::()).unwrap_err().to_string(), - "a value is required for '--option ' but none was supplied" - ); - - // Test the short option without equals - args.parse("-xofoo", &mut empty::()).unwrap(); - args.parse("-yobar", &mut empty::()).unwrap(); - args.parse("-zobaz", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!( - arg.take_multiple(), - vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),] - ); - - // Test the short option with equals - args.parse("-xo=foo", &mut empty::()).unwrap(); - args.parse("-yo=bar", &mut empty::()).unwrap(); - args.parse("-zo=baz", &mut empty::()).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!( - arg.take_multiple(), - vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),] - ); - - // Test the short option with space - let mut iter = ["foo".to_string()].into_iter(); - args.parse("-xo", &mut iter).unwrap(); - let mut iter = ["bar".to_string()].into_iter(); - args.parse("-yo", &mut iter).unwrap(); - let mut iter = ["baz".to_string()].into_iter(); - args.parse("-zo", &mut iter).unwrap(); - let arg = args.get_mut("--option").unwrap(); - assert_eq!( - arg.take_multiple(), - vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),] - ); - - // Test it prints correctly - assert_eq!(arg.to_string(), "--option ") - } - - #[test] - fn it_parses_counting_flag() { - let mut args = Args::default().counting("--flag", Some('f')); - - // Test not the flag - args.parse("--not-flag", &mut empty::()).unwrap(); - let arg = args.get("--flag").unwrap(); - assert_eq!(arg.count(), 0); - - // Test the flag - args.parse("--flag", &mut empty::()).unwrap(); - args.parse("--flag", &mut empty::()).unwrap(); - args.parse("--flag", &mut empty::()).unwrap(); - let arg = args.get_mut("--flag").unwrap(); - assert_eq!(arg.count(), 3); - arg.reset(); - - // Test the short flag - args.parse("-xfzf", &mut empty::()).unwrap(); - args.parse("-pfft", &mut empty::()).unwrap(); - args.parse("-abcd", &mut empty::()).unwrap(); - let arg = args.get_mut("--flag").unwrap(); - assert_eq!(arg.count(), 4); - - // Test it prints correctly - assert_eq!(arg.to_string(), "--flag") - } - - #[test] - fn it_parses_cargo_arguments() { - let args: CargoArguments = - CargoArguments::parse_from(["miden", "build", "--workspace"].into_iter()).unwrap(); - assert_eq!( - args, - CargoArguments { - color: None, - verbose: 0, - quiet: false, - targets: Vec::new(), - manifest_path: None, - release: false, - frozen: false, - locked: false, - offline: false, - workspace: true, - packages: Vec::new(), - } - ); - - let args = CargoArguments::parse_from( - [ - "miden", - "publish", - "-vvv", - "--color=auto", - "--manifest-path", - "Cargo.toml", - "--release", - "--package", - "package1", - "-p=package2@1.1.1", - "--target=foo", - "--target", - "bar", - "--quiet", - "--frozen", - "--locked", - "--offline", - "--all", - "--not-an-option", - ] - .into_iter(), - ) - .unwrap(); - assert_eq!( - args, - CargoArguments { - color: Some(Color::Auto), - verbose: 3, - quiet: true, - targets: vec!["foo".to_string(), "bar".to_string()], - manifest_path: Some("Cargo.toml".into()), - release: true, - frozen: true, - locked: true, - offline: true, - workspace: true, - packages: vec![ - CargoPackageSpec { - name: "package1".to_string(), - version: None - }, - CargoPackageSpec { - name: "package2".to_string(), - version: Some(Version::parse("1.1.1").unwrap()) - } - ], - } - ); - } -} diff --git a/tools/cargo-miden/src/lib.rs b/tools/cargo-miden/src/lib.rs index d413e8d5f..168c8c5d6 100644 --- a/tools/cargo-miden/src/lib.rs +++ b/tools/cargo-miden/src/lib.rs @@ -1,17 +1,22 @@ use std::path::PathBuf; -use cargo_component::load_metadata; +use anyhow::bail; +use cargo_component::{ + config::{CargoArguments, Config}, + load_component_metadata, load_metadata, run_cargo_command, +}; +use cargo_component_core::{ + command::{CACHE_DIR_ENV_VAR, CONFIG_FILE_ENV_VAR}, + terminal::{Color, Terminal, Verbosity}, +}; use clap::{CommandFactory, Parser}; -use config::CargoArguments; -use midenc_session::diagnostics::Report; -use new_project::NewCommand; +use commands::NewCommand; +use compile_masm::wasm_to_masm; +use non_component::run_cargo_command_for_non_component; -use crate::run_cargo_command::run_cargo_command; - -mod build; -pub mod config; -mod new_project; -mod run_cargo_command; +mod commands; +mod compile_masm; +mod non_component; mod target; fn version() -> &'static str { @@ -24,6 +29,13 @@ const BUILTIN_COMMANDS: &[&str] = &[ "new", ]; +/// The list of commands that are explicitly unsupported by `cargo-miden`. +/// +/// These commands are intended to integrate with `crates.io` and have no +/// analog in `cargo-miden` currently. +const UNSUPPORTED_COMMANDS: &[&str] = + &["install", "login", "logout", "owner", "package", "search", "uninstall"]; + const AFTER_HELP: &str = "Unrecognized subcommands will be passed to cargo verbatim and the artifacts will be processed afterwards (e.g. `build` command compiles MASM). \nSee `cargo help` for more information on available cargo commands."; @@ -79,7 +91,8 @@ where None } -pub fn run(args: T) -> Result, Report> +// TODO: Use Report instead of anyhow? +pub fn run(args: T) -> anyhow::Result> where T: Iterator, { @@ -91,11 +104,19 @@ where Some(cmd) if BUILTIN_COMMANDS.contains(&cmd) => { match CargoMiden::parse_from(args.clone()) { CargoMiden::Miden(cmd) | CargoMiden::Command(cmd) => match cmd { - Command::New(cmd) => vec![cmd.exec().map_err(Report::msg)?], + Command::New(cmd) => vec![cmd.exec()?], }, } } - + // Check for explicitly unsupported commands (e.g. those that deal with crates.io) + Some(cmd) if UNSUPPORTED_COMMANDS.contains(&cmd) => { + let terminal = Terminal::new(Verbosity::Normal, Color::Auto); + terminal.error(format!( + "command `{cmd}` is not supported by `cargo component`\n\nuse `cargo {cmd}` \ + instead" + ))?; + std::process::exit(1); + } // If no subcommand was detected, None => { // Attempt to parse the supported CLI (expected to fail) @@ -103,25 +124,104 @@ where // If somehow the CLI parsed correctly despite no subcommand, // print the help instead - CargoMiden::command().print_long_help().map_err(Report::msg)?; + CargoMiden::command().print_long_help()?; Vec::new() } _ => { // Not a built-in command, run the cargo command - let cargo_args = - CargoArguments::parse_from(args.clone().into_iter()).map_err(Report::msg)?; - let metadata = - load_metadata(cargo_args.manifest_path.as_deref()).map_err(Report::msg)?; - if metadata.packages.is_empty() { - return Err(Report::msg(format!( + let cargo_args = CargoArguments::parse_from(args.clone().into_iter())?; + dbg!(&cargo_args); + let cache_dir = std::env::var(CACHE_DIR_ENV_VAR).map(PathBuf::from).ok(); + let config_file = std::env::var(CONFIG_FILE_ENV_VAR).map(PathBuf::from).ok(); + let config = Config::new( + Terminal::new( + if cargo_args.quiet { + Verbosity::Quiet + } else { + match cargo_args.verbose { + 0 => Verbosity::Normal, + _ => Verbosity::Verbose, + } + }, + cargo_args.color.unwrap_or_default(), + ), + config_file, + )?; + let metadata = load_metadata(cargo_args.manifest_path.as_deref())?; + let packages = load_component_metadata( + &metadata, + cargo_args.packages.iter(), + cargo_args.workspace, + )?; + + if packages.is_empty() { + bail!( "manifest `{path}` contains no package or the workspace has no members", path = metadata.workspace_root.join("Cargo.toml") - ))); + ); + } + let mut spawn_args: Vec<_> = args.into_iter().skip(1).collect(); + spawn_args.extend_from_slice( + &[ + "-Z", + // compile std as part of crate graph compilation + // https://doc.rust-lang.org/cargo/reference/unstable.html#build-std + // to abort on panic below + "build-std=std,core,alloc,panic_abort", + "-Z", + // abort on panic without message formatting (core::fmt uses call_indirect) + "build-std-features=panic_immediate_abort", + ] + .map(|s| s.to_string()), + ); + + let mut builder = tokio::runtime::Builder::new_current_thread(); + let rt = builder.enable_all().build()?; + let mut wasm_outputs = rt.block_on(async { + let client = config.client(cache_dir, cargo_args.offline).await?; + run_cargo_command( + client, + &config, + &metadata, + &packages, + subcommand.as_deref(), + &cargo_args, + &spawn_args, + ) + .await + })?; + // TODO: analyze `packages` and find the ones that don't have a WIT component and get Wasm binary (core module) for them with our own version of run_cargo_command + if wasm_outputs.is_empty() { + // crates that don't have a WIT component are ignored by the `cargo-component` run_cargo_command + // build them with our own version of run_cargo_command + wasm_outputs = run_cargo_command_for_non_component( + &config, + subcommand.as_deref(), + &cargo_args, + &spawn_args, + )?; + } + dbg!(&wasm_outputs); + + let miden_out_dir = + metadata.target_directory.join("miden").join(if cargo_args.release { + "release" + } else { + "debug" + }); + if !miden_out_dir.exists() { + std::fs::create_dir_all(&miden_out_dir)?; } - let spawn_args: Vec<_> = args.into_iter().skip(1).collect(); - run_cargo_command(&metadata, subcommand.as_deref(), &cargo_args, &spawn_args)? + let mut outputs = Vec::new(); + for wasm in wasm_outputs { + let is_bin = false; + let output = wasm_to_masm(&wasm, miden_out_dir.as_std_path(), is_bin) + .map_err(|e| anyhow::anyhow!("{e}"))?; + outputs.push(output); + } + outputs } }; Ok(outputs) diff --git a/tools/cargo-miden/src/main.rs b/tools/cargo-miden/src/main.rs index 6129d0672..770dcde7b 100644 --- a/tools/cargo-miden/src/main.rs +++ b/tools/cargo-miden/src/main.rs @@ -1,5 +1,6 @@ -use cargo_component_core::terminal::{Terminal, Verbosity}; -use cargo_miden::{config::CargoArguments, run}; +use anyhow::Ok; +use cargo_component_core::terminal::{Color, Terminal, Verbosity}; +use cargo_miden::run; fn main() -> anyhow::Result<()> { // Initialize logger @@ -8,23 +9,10 @@ fn main() -> anyhow::Result<()> { builder.format_timestamp(None); builder.init(); - let cargo_args = CargoArguments::parse_from(std::env::args())?; - let terminal = Terminal::new( - if cargo_args.quiet { - Verbosity::Quiet - } else { - match cargo_args.verbose { - 0 => Verbosity::Normal, - _ => Verbosity::Verbose, - } - }, - cargo_args.color.unwrap_or_default(), - ); - if let Err(e) = run(std::env::args()) { - terminal.error(format!("{e:?}"))?; + let terminal = Terminal::new(Verbosity::Normal, Color::Auto); + terminal.error(format!("{e}"))?; std::process::exit(1); } - Ok(()) } diff --git a/tools/cargo-miden/src/non_component.rs b/tools/cargo-miden/src/non_component.rs new file mode 100644 index 000000000..d0a2f4c3a --- /dev/null +++ b/tools/cargo-miden/src/non_component.rs @@ -0,0 +1,148 @@ +use std::{ + io::{BufRead, BufReader}, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +use anyhow::{bail, Context, Result}; +use cargo_component::config::{CargoArguments, Config}; +use cargo_metadata::{Artifact, Message}; + +use crate::target::install_wasm32_wasip1; + +fn is_wasm_target(target: &str) -> bool { + target == "wasm32-wasi" || target == "wasm32-wasip1" || target == "wasm32-unknown-unknown" +} + +pub fn run_cargo_command_for_non_component( + config: &Config, + subcommand: Option<&str>, + cargo_args: &CargoArguments, + spawn_args: &[String], +) -> anyhow::Result> { + let cargo_path = std::env::var("CARGO") + .map(PathBuf::from) + .ok() + .unwrap_or_else(|| PathBuf::from("cargo")); + + let is_build = matches!(subcommand, Some("b") | Some("build")); + + let (build_args, _output_args) = match spawn_args.iter().position(|a| a == "--") { + Some(position) => spawn_args.split_at(position), + None => (spawn_args, &[] as _), + }; + + let mut args = build_args.iter().peekable(); + if let Some(arg) = args.peek() { + if *arg == "miden" { + args.next().unwrap(); + } + } + + // Spawn the actual cargo command + log::debug!( + "spawning cargo `{path}` with arguments `{args:?}`", + path = cargo_path.display(), + args = args.clone().collect::>(), + ); + + let mut cargo = Command::new(&cargo_path); + cargo.args(args); + + let cargo_config = cargo_config2::Config::load()?; + + // Handle the target for build command + if is_build { + install_wasm32_wasip1(config)?; + + // Add an implicit wasm32-wasip1 target if there isn't a wasm target present + if !cargo_args.targets.iter().any(|t| is_wasm_target(t)) + && !cargo_config + .build + .target + .as_ref() + .is_some_and(|v| v.iter().any(|t| is_wasm_target(t.triple()))) + { + cargo.arg("--target").arg("wasm32-wasip1"); + } + + if let Some(format) = &cargo_args.message_format { + if format != "json-render-diagnostics" { + bail!("unsupported cargo message format `{format}`"); + } + } + + // It will output the message as json so we can extract the wasm files + // that will be componentized + cargo.arg("--message-format").arg("json-render-diagnostics"); + cargo.stdout(Stdio::piped()); + } else { + cargo.stdout(Stdio::inherit()); + } + + let artifacts = spawn_cargo(cargo, &cargo_path, cargo_args, is_build)?; + Ok(artifacts + .into_iter() + .flat_map(|a| { + a.filenames.into_iter().filter(|p| p.extension() == Some("wasm") && p.exists()) + }) + .map(|p| p.as_std_path().to_path_buf()) + .collect()) +} + +fn spawn_cargo( + mut cmd: Command, + cargo: &Path, + cargo_args: &CargoArguments, + process_messages: bool, +) -> Result> { + log::debug!("spawning command {:?}", cmd); + + let mut child = cmd + .spawn() + .context(format!("failed to spawn `{cargo}`", cargo = cargo.display()))?; + + let mut artifacts = Vec::new(); + if process_messages { + let stdout = child.stdout.take().expect("no stdout"); + let reader = BufReader::new(stdout); + for line in reader.lines() { + let line = line.context("failed to read output from `cargo`")?; + + // If the command line arguments also had `--message-format`, echo the line + if cargo_args.message_format.is_some() { + println!("{line}"); + } + + if line.is_empty() { + continue; + } + + for message in Message::parse_stream(line.as_bytes()) { + if let Message::CompilerArtifact(artifact) = + message.context("unexpected JSON message from cargo")? + { + for path in &artifact.filenames { + match path.extension() { + Some("wasm") => { + artifacts.push(artifact); + break; + } + _ => continue, + } + } + } + } + } + } + + let status = child + .wait() + .context(format!("failed to wait for `{cargo}` to finish", cargo = cargo.display()))?; + + if !status.success() { + std::process::exit(status.code().unwrap_or(1)); + } + + Ok(artifacts) +} diff --git a/tools/cargo-miden/src/run_cargo_command.rs b/tools/cargo-miden/src/run_cargo_command.rs deleted file mode 100644 index c35d4c9fc..000000000 --- a/tools/cargo-miden/src/run_cargo_command.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::{path::PathBuf, process::Command}; - -use cargo_metadata::Metadata; -use midenc_session::diagnostics::{IntoDiagnostic, Report}; - -use crate::{ - build::build_masm, - config::CargoArguments, - target::{install_wasm32_wasi, WASM32_WASI_TARGET}, -}; - -fn is_wasm_target(target: &str) -> bool { - target == WASM32_WASI_TARGET -} - -/// Runs the cargo command as specified in the configuration. -/// -/// Returns any relevant output artifacts. -pub fn run_cargo_command( - metadata: &Metadata, - subcommand: Option<&str>, - cargo_args: &CargoArguments, - spawn_args: &[String], -) -> Result, Report> { - let cargo = std::env::var("CARGO") - .map(PathBuf::from) - .ok() - .unwrap_or_else(|| PathBuf::from("cargo")); - - let mut args = spawn_args.iter().peekable(); - if let Some(arg) = args.peek() { - if *arg == "miden" { - args.next().unwrap(); - } - } - - // Spawn the actual cargo command - log::debug!( - "spawning cargo `{cargo}` with arguments `{args:?}`", - cargo = cargo.display(), - args = args.clone().collect::>(), - ); - - let mut cmd = Command::new(&cargo); - cmd.args(args); - - let is_build = matches!(subcommand, Some("b") | Some("build")); - - // Handle the target for build commands - if is_build { - install_wasm32_wasi().map_err(Report::msg)?; - - // Add an implicit wasm32-wasi target if there isn't a wasm target present - if !cargo_args.targets.iter().any(|t| is_wasm_target(t)) { - cmd.arg("--target").arg(WASM32_WASI_TARGET); - } - } - - cmd.arg("-Z") - // compile std as part of crate graph compilation - // https://doc.rust-lang.org/cargo/reference/unstable.html#build-std - // to abort on panic below - .arg("build-std=std,core,alloc,panic_abort") - .arg("-Z") - // abort on panic without message formatting (core::fmt uses call_indirect) - .arg("build-std-features=panic_immediate_abort"); - - match cmd.status() { - Ok(status) => { - if !status.success() { - return Err(Report::msg(format!( - "cargo failed with exit code {}", - status.code().unwrap_or(1) - ))); - } - } - Err(e) => { - return Err(Report::msg(format!( - "failed to spawn `{cargo}`: {e}", - cargo = cargo.display() - ))); - } - } - let mut outputs = Vec::new(); - if is_build { - log::debug!("searching for WebAssembly modules to compile to MASM"); - let targets = cargo_args - .targets - .iter() - .map(String::as_str) - .filter(|t| is_wasm_target(t)) - .chain(cargo_args.targets.is_empty().then_some(WASM32_WASI_TARGET)); - - for target in targets { - let out_dir = metadata.target_directory.join(target).join(if cargo_args.release { - "release" - } else { - "debug" - }); - - let miden_out_dir = - metadata.target_directory.join("miden").join(if cargo_args.release { - "release" - } else { - "debug" - }); - if !miden_out_dir.exists() { - std::fs::create_dir_all(&miden_out_dir).into_diagnostic()?; - } - - for package in &metadata.packages { - let is_bin = package.targets.iter().any(|t| t.is_bin()); - - // First try for .wasm - let path = out_dir.join(&package.name).with_extension("wasm"); - if path.exists() { - let output = - build_masm(path.as_std_path(), miden_out_dir.as_std_path(), is_bin)?; - outputs.push(output); - } else { - let path = out_dir.join(package.name.replace('-', "_")).with_extension("wasm"); - if path.exists() { - let output = - build_masm(path.as_std_path(), miden_out_dir.as_std_path(), is_bin)?; - outputs.push(output); - } else { - log::debug!("no output found for package `{name}`", name = package.name); - return Err(Report::msg("Cargo build failed, no Wasm artifact found")); - } - } - } - } - } - - Ok(outputs) -} diff --git a/tools/cargo-miden/src/target.rs b/tools/cargo-miden/src/target.rs index 51d257d1f..8b0fa16f7 100644 --- a/tools/cargo-miden/src/target.rs +++ b/tools/cargo-miden/src/target.rs @@ -5,34 +5,34 @@ use std::{ }; use anyhow::{bail, Result}; +use cargo_component::config::Config; -pub const WASM32_WASI_TARGET: &str = "wasm32-wasip1"; - -pub fn install_wasm32_wasi() -> Result<()> { - log::info!("Installing {WASM32_WASI_TARGET} target"); +pub fn install_wasm32_wasip1(config: &Config) -> Result<()> { let sysroot = get_sysroot()?; - if sysroot.join(format!("lib/rustlib/{}", WASM32_WASI_TARGET)).exists() { + if sysroot.join("lib/rustlib/wasm32-wasip1").exists() { return Ok(()); } if env::var_os("RUSTUP_TOOLCHAIN").is_none() { bail!( - "failed to find the `{WASM32_WASI_TARGET}` target and `rustup` is not available. If \ - you're using rustup make sure that it's correctly installed; if not, make sure to \ - install the `{WASM32_WASI_TARGET}` target before using this command", + "failed to find the `wasm32-wasip1` target and `rustup` is not available. If you're \ + using rustup make sure that it's correctly installed; if not, make sure to install \ + the `wasm32-wasip1` target before using this command" ); } + config.terminal().status("Installing", "wasm32-wasip1 target")?; + let output = Command::new("rustup") .arg("target") .arg("add") - .arg(WASM32_WASI_TARGET) + .arg("wasm32-wasip1") .stderr(Stdio::inherit()) .stdout(Stdio::inherit()) .output()?; if !output.status.success() { - bail!("failed to install the `{WASM32_WASI_TARGET}` target"); + bail!("failed to install the `wasm32-wasip1` target"); } Ok(()) diff --git a/tools/cargo-miden/tests/build.rs b/tools/cargo-miden/tests/build.rs index 12e155e80..558586055 100644 --- a/tools/cargo-miden/tests/build.rs +++ b/tools/cargo-miden/tests/build.rs @@ -15,6 +15,7 @@ fn new_project_args(project_name: &str, template_path: Option<&str>) -> Vec