diff --git a/Cargo.lock b/Cargo.lock index b0412661a..b4e488bbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,7 +730,10 @@ dependencies = [ "midenc-compile", "midenc-session", "path-absolutize", + "serde", + "serde_json", "tokio", + "toml", ] [[package]] diff --git a/tests/integration/src/compiler_test.rs b/tests/integration/src/compiler_test.rs index 0a41815a0..eb2ae89d7 100644 --- a/tests/integration/src/compiler_test.rs +++ b/tests/integration/src/compiler_test.rs @@ -182,7 +182,7 @@ pub struct CompilerTestBuilder { /// The extra MASM modules to link to the compiled MASM program link_masm_modules: LinkMasmModules, /// Extra flags to pass to the midenc driver - midenc_flags: Vec>, + midenc_flags: Vec, /// Extra RUSTFLAGS to set when compiling Rust code rustflags: Vec>, /// The cargo workspace directory of the compiler @@ -225,8 +225,7 @@ impl CompilerTestBuilder { ]); let mut midenc_flags = vec!["--debug".into(), "--verbose".into()]; if let Some(entrypoint) = entrypoint { - midenc_flags - .extend(["--entrypoint".into(), format!("{}", entrypoint.display()).into()]); + midenc_flags.extend(["--entrypoint".into(), format!("{}", entrypoint.display())]); } Self { config: Default::default(), @@ -269,7 +268,7 @@ impl CompilerTestBuilder { None => (), } self.midenc_flags - .extend(["--entrypoint".into(), format!("{}", entrypoint.display()).into()]); + .extend(["--entrypoint".into(), format!("{}", entrypoint.display())]); self } @@ -278,7 +277,7 @@ impl CompilerTestBuilder { &mut self, flags: impl IntoIterator>, ) -> &mut Self { - self.midenc_flags.extend(flags); + self.midenc_flags.extend(flags.into_iter().map(|s| s.to_string())); self } @@ -306,7 +305,7 @@ impl CompilerTestBuilder { /// Consume the builder, invoke any tools required to obtain the inputs for the test, and if /// successful, return a [CompilerTest], ready for evaluation. - pub fn build(self) -> CompilerTest { + pub fn build(mut self) -> CompilerTest { // Set up the command used to compile the test inputs (typically Rust -> Wasm) let mut command = match self.source { CompilerTestInputType::CargoMiden(_) => { @@ -398,31 +397,26 @@ impl CompilerTestBuilder { // Build test match self.source { - CompilerTestInputType::CargoMiden(config) => { - let expected_wasm_artifact_path = config.wasm_artifact_path(); - let skip_rust_compilation = - std::env::var("SKIP_RUST").is_ok() && expected_wasm_artifact_path.exists(); - let wasm_artifact_path = if !skip_rust_compilation { - let mut args = vec![command.get_program().to_str().unwrap().to_string()]; - let cmd_args: Vec = command - .get_args() - .collect::>() - .iter() - .map(|s| s.to_str().unwrap().to_string()) - .collect(); - args.extend(cmd_args); - let wasm_artifacts = - cargo_miden::run(args.into_iter(), cargo_miden::OutputType::Wasm).unwrap(); - assert_eq!( - wasm_artifacts.len(), - 1, - "expected one Wasm artifact, got {:?}", - wasm_artifacts - ); - wasm_artifacts.first().unwrap().clone() - } else { - drop(command); - expected_wasm_artifact_path + CompilerTestInputType::CargoMiden(..) => { + let mut args = vec![command.get_program().to_str().unwrap().to_string()]; + let cmd_args: Vec = command + .get_args() + .collect::>() + .iter() + .map(|s| s.to_str().unwrap().to_string()) + .collect(); + args.extend(cmd_args); + let build_output = + cargo_miden::run(args.into_iter(), cargo_miden::OutputType::Wasm) + .unwrap() + .expect("'cargo miden build' should return Some(CommandOutput)") + .unwrap_build_output(); // Use the new method + let (wasm_artifact_path, dependencies) = match build_output { + cargo_miden::BuildOutput::Wasm { + artifact_path, + dependencies, + } => (artifact_path, dependencies), + other => panic!("Expected Wasm output, got {:?}", other), }; let artifact_name = wasm_artifact_path.file_stem().unwrap().to_str().unwrap().to_string(); @@ -440,6 +434,10 @@ impl CompilerTestBuilder { })); // dbg!(&inputs); + for dep in &dependencies { + self.midenc_flags.push("--link-library".to_string()); + self.midenc_flags.push(dep.to_str().unwrap().to_string()); + } let context = setup::default_context(inputs, &self.midenc_flags); let session = context.session_rc(); CompilerTest { @@ -448,6 +446,7 @@ impl CompilerTestBuilder { context, artifact_name: artifact_name.into(), entrypoint: self.entrypoint, + dependencies, ..Default::default() } } @@ -874,6 +873,8 @@ pub struct CompilerTest { ir_masm_program: Option, String>>, /// The compiled package containing a program executable by the VM package: Option, String>>, + /// Miden packages for dependencies + pub dependencies: Vec, } impl fmt::Debug for CompilerTest { @@ -907,6 +908,7 @@ impl Default for CompilerTest { masm_src: None, ir_masm_program: None, package: None, + dependencies: Vec::new(), } } } diff --git a/tests/integration/src/rust_masm_tests/rust_sdk.rs b/tests/integration/src/rust_masm_tests/rust_sdk.rs index 80fcb7290..5efd25add 100644 --- a/tests/integration/src/rust_masm_tests/rust_sdk.rs +++ b/tests/integration/src/rust_masm_tests/rust_sdk.rs @@ -90,9 +90,14 @@ fn rust_sdk_p2id_note_script() { .map(|s| s.to_string()) .collect(); dbg!(env::current_dir().unwrap().display()); - let outputs = cargo_miden::run(args.into_iter(), cargo_miden::OutputType::Masm) - .expect("Failed to compile the basic-wallet package"); - let masp_path: PathBuf = outputs.first().unwrap().clone(); + let build_output = cargo_miden::run(args.into_iter(), cargo_miden::OutputType::Masm) + .expect("Failed to compile the basic-wallet package") + .expect("'cargo miden build' for basic-wallet should return Some(CommandOutput)") + .unwrap_build_output(); // Use the new method + let masp_path = match build_output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output for basic-wallet, got {:?}", other), + }; dbg!(&masp_path); // @@ -163,31 +168,6 @@ fn rust_sdk_cross_ctx_account() { #[test] fn rust_sdk_cross_ctx_note() { - // Build cross-ctx-account package - let args: Vec = [ - "cargo", - "miden", - "build", - "--manifest-path", - "../rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.toml", - "--lib", - "--release", - // Use the target dir of this test's cargo project to avoid issues running tests in parallel - // i.e. avoid using the same target dir as the basic-wallet test (see above) - "--target-dir", - "../rust-apps-wasm/rust-sdk/cross-ctx-note/target", - ] - .iter() - .map(|s| s.to_string()) - .collect(); - dbg!(env::current_dir().unwrap().display()); - - let outputs = cargo_miden::run(args.into_iter(), cargo_miden::OutputType::Masm) - .expect("Failed to compile the cross-ctx-account package for cross-ctx-note"); - let masp_path: PathBuf = outputs.first().unwrap().clone(); - - dbg!(&masp_path); - let _ = env_logger::builder().is_test(true).try_init(); let config = WasmTranslationConfig::default(); @@ -195,14 +175,7 @@ fn rust_sdk_cross_ctx_note() { let mut builder = CompilerTestBuilder::rust_source_cargo_miden( "../rust-apps-wasm/rust-sdk/cross-ctx-note", config, - [ - "-l".into(), - "std".into(), - "-l".into(), - "base".into(), - "--link-library".into(), - masp_path.clone().into_os_string().into_string().unwrap().into(), - ], + ["-l".into(), "std".into(), "-l".into(), "base".into()], ); builder.with_entrypoint(FunctionIdent { module: Ident::new(Symbol::intern("miden:base/note-script@1.0.0"), SourceSpan::default()), @@ -215,10 +188,12 @@ fn rust_sdk_cross_ctx_note() { test.expect_masm(expect_file![format!("../../expected/rust_sdk/{artifact_name}.masm")]); let package = test.compiled_package(); let mut exec = Executor::new(vec![]); - let account_package = - Arc::new(Package::read_from_bytes(&std::fs::read(masp_path).unwrap()).unwrap()); - exec.dependency_resolver_mut() - .add(account_package.digest(), account_package.into()); + for dep_path in test.dependencies { + let account_package = + Arc::new(Package::read_from_bytes(&std::fs::read(dep_path).unwrap()).unwrap()); + exec.dependency_resolver_mut() + .add(account_package.digest(), account_package.into()); + } exec.with_dependencies(&package.manifest.dependencies).unwrap(); let trace = exec.execute(&package.unwrap_program(), &test.session); } diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml index 7e2b751f5..a51c536cb 100644 --- a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml @@ -39,6 +39,10 @@ overflow-checks = false debug = true trim-paths = ["diagnostics", "object"] +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:cross-ctx-account" = { path = "../cross-ctx-account" } + # TODO: switch to miden table [package.metadata.component] package = "miden:cross-ctx-note" diff --git a/tools/cargo-miden/Cargo.toml b/tools/cargo-miden/Cargo.toml index d29117730..a4582d9f7 100644 --- a/tools/cargo-miden/Cargo.toml +++ b/tools/cargo-miden/Cargo.toml @@ -35,6 +35,9 @@ cargo-generate = "0.23" path-absolutize = "3.1.1" tokio.workspace = true cargo-config2 = "0.1.24" +serde.workspace = true +toml.workspace = true +serde_json = "1.0" [dev-dependencies] miden-mast-package.workspace = true diff --git a/tools/cargo-miden/src/compile_masm.rs b/tools/cargo-miden/src/compile_masm.rs index ce8f315ec..00c4f64fe 100644 --- a/tools/cargo-miden/src/compile_masm.rs +++ b/tools/cargo-miden/src/compile_masm.rs @@ -13,6 +13,7 @@ pub fn wasm_to_masm( wasm_file_path: &Path, output_folder: &Path, is_bin: bool, + dependency_paths: &[PathBuf], // New parameter ) -> Result { if !output_folder.exists() { return Err(Report::msg(format!( @@ -52,6 +53,13 @@ pub fn wasm_to_masm( args.push(entrypoint_opt.as_ref()); } + // Add dependency linker arguments (Step 3.3) + for dep_path in dependency_paths { + args.push("--link-library".as_ref()); + // Ensure the path is valid OsStr + args.push(dep_path.as_os_str()); + } + let session = Rc::new(Compiler::new_session([input], None, args)); let context = Rc::new(Context::new(session)); midenc_compile::compile(context.clone())?; diff --git a/tools/cargo-miden/src/dependencies.rs b/tools/cargo-miden/src/dependencies.rs new file mode 100644 index 000000000..9a6de5782 --- /dev/null +++ b/tools/cargo-miden/src/dependencies.rs @@ -0,0 +1,225 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + path::PathBuf, +}; + +use anyhow::{anyhow, bail, Context, Result}; +use cargo_component::config::CargoArguments; +use cargo_metadata::{camino, Package}; +use serde::Deserialize; + +use crate::{BuildOutput, OutputType}; + +/// Defines dependency (the rhs of the dependency `"ns:package" = { path = "..." }` pair) +#[derive(Deserialize, Debug, Clone)] +#[serde(deny_unknown_fields)] +struct MidenDependencyInfo { + /// Local path to the cargo-miden project that produces Miden package or Miden package `.masp` file + path: PathBuf, +} + +/// Representation for [package.metadata.miden] +#[derive(Deserialize, Debug, Default)] +struct MidenMetadata { + #[serde(default)] + dependencies: HashMap, +} + +/// Processes Miden dependencies defined in `[package.metadata.miden.dependencies]` +/// for the given package. +/// +/// This involves finding dependency projects, recursively building them if necessary, +/// and collecting the paths to the resulting `.masp` package artifacts. +pub fn process_miden_dependencies( + package: &Package, + cargo_args: &CargoArguments, +) -> Result> { + let mut dependency_packages_paths: Vec = Vec::new(); + // Avoid redundant builds/checks + let mut processed_dep_paths: HashSet = HashSet::new(); + + log::debug!("Processing Miden dependencies for package '{}'...", package.name); + + // Get the manifest directory from the package + let manifest_path = &package.manifest_path; + let manifest_dir = manifest_path.parent().with_context(|| { + format!("Failed to get parent directory for manifest: {}", manifest_path) + })?; + + // Extract Miden metadata using serde_json + let miden_metadata: MidenMetadata = package + .metadata + .get("miden") + .cloned() + .map(serde_json::from_value) + .transpose() + .context("Failed to deserialize [package.metadata.miden]")? + .unwrap_or_default(); + + let dependencies = miden_metadata.dependencies; + + if !dependencies.is_empty() { + log::debug!(" Found dependencies defined in {}", manifest_path); + + for (dep_name, dep_info) in &dependencies { + let relative_path = &dep_info.path; + // Resolve relative to the *dependency declaring* manifest's directory + let utf8_relative_path = match camino::Utf8PathBuf::from_path_buf(relative_path.clone()) + { + Ok(p) => p, + Err(e) => { + bail!( + "Dependency path for '{}' is not valid UTF-8 ({}): {}", + dep_name, + relative_path.display(), + e.to_path_buf().display() + ); + } + }; + let dep_path = manifest_dir.join(&utf8_relative_path); + + let absolute_dep_path = + fs::canonicalize(dep_path.as_std_path()).with_context(|| { + format!("resolving dependency path for '{}' ({})", dep_name, dep_path) + })?; + + // Skip if we've already processed this exact path + if processed_dep_paths.contains(&absolute_dep_path) { + // Check if the artifact path is already collected, add if not + if dependency_packages_paths.contains(&absolute_dep_path) { + // Already in the list, nothing to do. + } else { + // If it was processed but is a valid .masp file, ensure it's in the final list + if absolute_dep_path.is_file() + && absolute_dep_path.extension().is_some_and(|ext| ext == "masp") + { + dependency_packages_paths.push(absolute_dep_path.clone()); + } + } + continue; + } + + if absolute_dep_path.is_file() { + // Look for a Miden package .masp file + if absolute_dep_path.extension().is_some_and(|ext| ext == "masp") { + log::debug!( + " - Found pre-compiled dependency '{}': {}", + dep_name, + absolute_dep_path.display() + ); + if !dependency_packages_paths.iter().any(|p| p == &absolute_dep_path) { + dependency_packages_paths.push(absolute_dep_path.clone()); + } + // Mark as processed + processed_dep_paths.insert(absolute_dep_path); + } else { + bail!( + "Dependency path for '{}' points to a file, but it's not a .masp file: {}", + dep_name, + absolute_dep_path.display() + ); + } + } else if absolute_dep_path.is_dir() { + // Build a cargo project + let dep_manifest_path = absolute_dep_path.join("Cargo.toml"); + if dep_manifest_path.is_file() { + log::debug!( + " - Building Miden library dependency project '{}' at {}", + dep_name, + absolute_dep_path.display() + ); + + let mut dep_build_args = vec![ + "cargo".to_string(), + "miden".to_string(), + "build".to_string(), + "--manifest-path".to_string(), + dep_manifest_path.to_string_lossy().to_string(), + ]; + // Inherit release/debug profile from parent build + if cargo_args.release { + dep_build_args.push("--release".to_string()); + } + // Dependencies should always be built as libraries + dep_build_args.push("--lib".to_string()); + + // We expect dependencies to *always* produce Masm libraries (.masp) + let command_output = crate::run(dep_build_args.into_iter(), OutputType::Masm) + .with_context(|| { + format!( + "building dependency '{}' at {}", + dep_name, + absolute_dep_path.display() + ) + })? + .ok_or(anyhow!("`cargo miden build` does not produced any output"))?; + + let build_output = command_output.unwrap_build_output(); + + let artifact_path = match build_output { + BuildOutput::Masm { artifact_path } => artifact_path, + // We specifically requested Masm, so Wasm output would be an error. + BuildOutput::Wasm { artifact_path, .. } => { + bail!( + "Dependency build for '{}' unexpectedly produced WASM output at \ + {}. Expected MASM (.masp)", + dep_name, + artifact_path.display() + ); + } + }; + log::debug!( + " - Dependency '{}' built successfully. Output: {}", + dep_name, + artifact_path.display() + ); + // Ensure it's a .masp file and add if unique + if artifact_path.extension().is_some_and(|ext| ext == "masp") { + if !dependency_packages_paths.iter().any(|p| p == &artifact_path) { + dependency_packages_paths.push(artifact_path); + } else { + bail!( + "Dependency build for '{}' produced a duplicate artifact: {}", + dep_name, + artifact_path.display() + ); + } + } else { + bail!( + "Build output for dependency '{}' is not a .masp file: {}.", + dep_name, + artifact_path.display() + ); + } + // Mark the *directory* as processed + processed_dep_paths.insert(absolute_dep_path); + } else { + bail!( + "Dependency path for '{}' points to a directory, but it does not contain \ + a Cargo.toml file: {}", + dep_name, + absolute_dep_path.display() + ); + } + } else { + bail!( + "Dependency path for '{}' does not exist or is not a file/directory: {}", + dep_name, + absolute_dep_path.display() + ); + } + } + } else { + log::debug!(" No Miden dependencies found for package '{}'.", package.name); + } + log::debug!( + "Finished processing Miden dependencies. Packages to link: [{}]", + dependency_packages_paths + .iter() + .map(|p| p.display().to_string()) + .collect::>() + .join(", ") + ); + Ok(dependency_packages_paths) +} diff --git a/tools/cargo-miden/src/lib.rs b/tools/cargo-miden/src/lib.rs index bc4c0877c..0680b009f 100644 --- a/tools/cargo-miden/src/lib.rs +++ b/tools/cargo-miden/src/lib.rs @@ -1,8 +1,6 @@ #![deny(warnings)] -use std::path::PathBuf; - -use anyhow::bail; +use anyhow::{bail, Context, Result}; use cargo_component::{ config::{CargoArguments, Config}, load_component_metadata, load_metadata, run_cargo_command, @@ -11,13 +9,18 @@ use cargo_component_core::terminal::{Color, Terminal, Verbosity}; use clap::{CommandFactory, Parser}; use commands::NewCommand; use compile_masm::wasm_to_masm; +use dependencies::process_miden_dependencies; use non_component::run_cargo_command_for_non_component; mod commands; mod compile_masm; +mod dependencies; mod non_component; +mod outputs; mod target; +pub use outputs::{BuildOutput, CommandOutput}; + fn version() -> &'static str { option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) } @@ -98,8 +101,10 @@ pub enum OutputType { } /// Runs the cargo-miden command -/// The arguments are expected to start with `["cargo", "miden", ...]` followed by a subcommand with options -pub fn run(args: T, build_output_type: OutputType) -> anyhow::Result> +/// The arguments are expected to start with `["cargo", "miden", ...]` followed by a subcommand +/// with options +/// Returns the outputs of the command. +pub fn run(args: T, build_output_type: OutputType) -> Result> where T: Iterator, { @@ -107,12 +112,15 @@ where let args = args.skip_while(|arg| arg != "miden").collect::>(); let subcommand = detect_subcommand(args.clone()); - let outputs = match subcommand.as_deref() { + match subcommand.as_deref() { // Check for built-in command or no command (shows help) 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()?], + Command::New(cmd) => { + let project_path = cmd.exec()?; + Ok(Some(CommandOutput::NewCommandOutput { project_path })) + } }, } } @@ -133,7 +141,7 @@ where // If somehow the CLI parsed correctly despite no subcommand, // print the help instead CargoMiden::command().print_long_help()?; - Vec::new() + Ok(None) } _ => { @@ -155,6 +163,12 @@ where ); } + // Get the root package being built + let root_package = + metadata.root_package().context("Metadata is missing a root package")?; + + let dependency_packages_paths = process_miden_dependencies(root_package, &cargo_args)?; + for package in packages.iter_mut() { package.metadata.section.bindings.with = [ ("miden:base/core-types@1.0.0/felt", "miden::Felt"), @@ -243,8 +257,15 @@ where &spawn_args, )?; } + assert_eq!(wasm_outputs.len(), 1, "expected only one Wasm artifact"); + let wasm_output = wasm_outputs.first().unwrap(); match build_output_type { - OutputType::Wasm => wasm_outputs, + OutputType::Wasm => Ok(Some(CommandOutput::BuildCommandOutput { + output: BuildOutput::Wasm { + artifact_path: wasm_output.clone(), + dependencies: dependency_packages_paths, + }, + })), OutputType::Masm => { let miden_out_dir = metadata.target_directory.join("miden").join(if cargo_args.release { @@ -256,19 +277,24 @@ where std::fs::create_dir_all(&miden_out_dir)?; } - let mut outputs = Vec::new(); - for wasm in wasm_outputs { - // so far, we only support the Miden VM programs, unless `--lib` is - // specified (in our integration tests) - let is_bin = !args.contains(&"--lib".to_string()); - let output = wasm_to_masm(&wasm, miden_out_dir.as_std_path(), is_bin) - .map_err(|e| anyhow::anyhow!("{e}"))?; - outputs.push(output); - } - outputs + // so far, we only support the Miden VM programs, unless `--lib` is + // specified (in our integration tests) + let is_bin = !args.contains(&"--lib".to_string()); + let output = wasm_to_masm( + wasm_output, + miden_out_dir.as_std_path(), + is_bin, + &dependency_packages_paths, + ) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + Ok(Some(CommandOutput::BuildCommandOutput { + output: BuildOutput::Masm { + artifact_path: output, + }, + })) } } } - }; - Ok(outputs) + } } diff --git a/tools/cargo-miden/src/outputs.rs b/tools/cargo-miden/src/outputs.rs new file mode 100644 index 000000000..89f662df8 --- /dev/null +++ b/tools/cargo-miden/src/outputs.rs @@ -0,0 +1,45 @@ +use std::path::PathBuf; + +/// Represents the structured output of a successful `cargo miden` command. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CommandOutput { + /// Output from the `new` command. + NewCommandOutput { + /// The path to the newly created project directory. + project_path: PathBuf, + }, + /// Output from the `build` command. + BuildCommandOutput { + /// The type and path of the artifact produced by the build. + output: BuildOutput, + }, + // Add other variants here if other commands need structured output later. +} + +impl CommandOutput { + /// Panics if the output is not `BuildCommandOutput`, otherwise returns the inner `BuildOutput`. + pub fn unwrap_build_output(self) -> BuildOutput { + match self { + CommandOutput::BuildCommandOutput { output } => output, + _ => panic!("called `unwrap_build_output()` on a non-BuildCommandOutput value"), + } + } +} + +/// Represents the specific artifact produced by the `build` command. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BuildOutput { + /// Miden Assembly (.masm) output. + Masm { + /// Path to the compiled MASM file or directory containing artifacts. + artifact_path: PathBuf, + // Potentially add other relevant info like package name, component type etc. + }, + /// WebAssembly (.wasm) output. + Wasm { + /// Path to the compiled WASM file. + artifact_path: PathBuf, + /// Miden package for each dependency + dependencies: Vec, + }, +} diff --git a/tools/cargo-miden/tests/build.rs b/tools/cargo-miden/tests/build.rs index 4f26f8a30..78a9ebd00 100644 --- a/tools/cargo-miden/tests/build.rs +++ b/tools/cargo-miden/tests/build.rs @@ -62,8 +62,15 @@ fn build_new_project_from_template(template: &str) -> Package { let args = new_project_args(project_name, template); - let outputs = run(args.into_iter(), OutputType::Masm).expect("Failed to create new project"); - let new_project_path = outputs.first().unwrap().canonicalize().unwrap(); + let output = run(args.into_iter(), OutputType::Masm) + .expect("Failed to create new project") + .expect("'cargo miden new' should return Some(CommandOutput)"); + let new_project_path = match output { + cargo_miden::CommandOutput::NewCommandOutput { project_path } => { + project_path.canonicalize().unwrap() + } + other => panic!("Expected NewCommandOutput, got {:?}", other), + }; dbg!(&new_project_path); assert!(new_project_path.exists()); assert_eq!(new_project_path, expected_new_project_dir.canonicalize().unwrap()); @@ -71,9 +78,16 @@ fn build_new_project_from_template(template: &str) -> Package { // build with the dev profile let args = ["cargo", "miden", "build"].iter().map(|s| s.to_string()); - let outputs = run(args, OutputType::Masm).expect("Failed to compile with the dev profile"); - assert_eq!(outputs.len(), 1); - let expected_masm_path = outputs.first().unwrap(); + let output = run(args, OutputType::Masm) + .expect("Failed to compile with the dev profile") + .expect("'cargo miden build' should return Some(CommandOutput)"); + let expected_masm_path = match output { + cargo_miden::CommandOutput::BuildCommandOutput { output } => match output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output, got {:?}", other), + }, + other => panic!("Expected BuildCommandOutput, got {:?}", other), + }; dbg!(&expected_masm_path); assert!(expected_masm_path.exists()); assert!(expected_masm_path.to_str().unwrap().contains("/debug/")); @@ -83,9 +97,16 @@ fn build_new_project_from_template(template: &str) -> Package { // build with the release profile let args = ["cargo", "miden", "build", "--release"].iter().map(|s| s.to_string()); - let outputs = run(args, OutputType::Masm).expect("Failed to compile with the release profile"); - assert_eq!(outputs.len(), 1); - let expected_masm_path = outputs.first().unwrap(); + let output = run(args, OutputType::Masm) + .expect("Failed to compile with the release profile") + .expect("'cargo miden build --release' should return Some(CommandOutput)"); + let expected_masm_path = match output { + cargo_miden::CommandOutput::BuildCommandOutput { output } => match output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output, got {:?}", other), + }, + other => panic!("Expected BuildCommandOutput, got {:?}", other), + }; dbg!(&expected_masm_path); assert!(expected_masm_path.exists()); assert_eq!(expected_masm_path.extension().unwrap(), "masp");