diff --git a/Cargo.lock b/Cargo.lock index 88bec64a..2bcaedf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,7 +219,7 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.1", + "polling 3.7.2", "rustix 0.38.34", "slab", "tracing", @@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.5.0-beta.5" +version = "0.5.0-beta.6" dependencies = [ "anstyle", "anyhow", @@ -2655,6 +2655,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -3031,7 +3037,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -3042,7 +3048,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.52.0", ] @@ -3444,9 +3450,9 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -3679,7 +3685,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -3872,7 +3878,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -4397,13 +4403,13 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.1" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite", "rustix 0.38.34", "tracing", @@ -4619,9 +4625,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index cd727bdd..3e82a11b 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.5.0-beta.5" +version = "0.5.0-beta.6" readme = "README.md" description = "Build tool for neat yellow console." keywords = ["playdate", "build", "cargo", "plugin", "cargo-subcommand"] diff --git a/cargo/src/build/mod.rs b/cargo/src/build/mod.rs index 88ff0c5c..a5bf89b1 100644 --- a/cargo/src/build/mod.rs +++ b/cargo/src/build/mod.rs @@ -5,12 +5,16 @@ use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::HashMap; +use std::collections::HashSet; use anyhow::Context; use anyhow::anyhow; use anyhow::ensure; +use cargo::core::PackageId; use cargo::CargoResult; -use cargo::core::Package; use cargo::core::Target; use cargo::core::Verbosity; use cargo::core::compiler::CompileKind; @@ -34,177 +38,482 @@ use crate::proc::args_line_for_proc; use crate::proc::cargo_proxy_cmd; use crate::proc::reader::CargoJsonReader; use crate::proc::reader::format::ArtifactProfile; +use crate::proc::reader::format::Artifact; +use crate::proc::reader::format::CargoMessage; +use crate::utils::cargo::meta_deps::MetaDeps; +use crate::utils::cargo::meta_deps::RootNode; use crate::utils::cargo::CompileKindExt; use crate::utils::path::AsRelativeTo; -use crate::utils::workspace::PossibleTargets; pub mod rustflags; -pub fn build<'cfg>(config: &'cfg Config<'cfg>) -> CargoResult>> { - if config.dry_run { +pub fn build(cfg: &Config, tree: &MetaDeps) -> CargoResult> { + if cfg.dry_run { return Ok(Default::default()); } - // get plan _before_ cargo invocation: - let plan = config.build_plan()?; - // call cargo: - let mut cargo = cargo_proxy_cmd(config, &Cmd::Build)?; + let mut cargo = cargo_proxy_cmd(cfg, &Cmd::Build)?; // add build-std: // https://github.com/rust-lang/cargo/issues/8733 // https://stackoverflow.com/a/76614919/829264 // We cannot specify build-std for one target, so for all if needed for one: - if !has_only_nopd_targets(config)? { + if !has_only_nopd_targets(cfg)? { cargo.args(["-Zbuild-std=core,alloc", "-Zunstable-options"]); - if config.prevent_unwinding { + if cfg.prevent_unwinding { cargo.arg("-Zbuild-std-features=panic_immediate_abort"); } } - config.log().verbose(|mut log| { - log.status("Cargo", args_line_for_proc(&cargo)); - }); - + cfg.log().verbose(|mut log| { + log.status("Cargo", args_line_for_proc(&cargo)); + }); // read cargo output: - let artifacts = read_cargo_output(config, cargo)?; + let mut artifacts = read_cargo_output(cfg, cargo, tree)?; + let artifacts_len = artifacts.len(); + log::trace!("artifacts by cargo: {}", artifacts.len()); + + + // Now we have many artifacts associated with multiple roots which have various `CompileKind`s. + // We should reduce number of associated to one guessing the target for artifact, each of them. + + + // Prepare targets associated with roots: + let root_targets = cfg.possible_targets()? + .flat_map(|(p, targets)| { + let package_id = p.package_id(); + tree.roots() + .iter() + .filter(move |r| r.node().unit().package_id == package_id) + .filter_map(move |r| { + let unit = r.node().unit(); + targets.iter() + .find(|t| { + &unit.target.kind() == t.kind() && + (unit.target.name == t.name() || + unit.target.name == t.crate_name()) && + unit.target.crate_types == t.rustc_crate_types() + }) + .map(|t| (r, *t)) + }) + }) + .collect::>(); + + let target_for_root = |root: &RootNode| { + root_targets.iter() + .find_map(|(r, t)| (*r == root).then_some(*t).cloned()) + .unwrap_or_else(|| root.node().target().cargo_target()) + }; - let mut products = Vec::new(); - for (package, target, art) in artifacts { + struct ArtifactGraph<'t, 'cfg> { + /// Targets of root+artifact + trg: Vec, + /// Artifacts of root+target + art: Vec, + /// [`CrateType`]s of `artifact.filename`s + cty: BTreeMap, + /// Value: (target index, artifact index) + index: HashMap<&'t RootNode<'cfg>, (usize, usize)>, + } + + + let mut graph = ArtifactGraph { trg: Vec::with_capacity(tree.roots().len()), + art: Vec::with_capacity(artifacts.len()), + cty: Default::default(), + index: Default::default() }; + + + fn add_to_graph<'t, 'cfg>(root: &'t RootNode<'cfg>, + target: Target, + art: Artifact, + arts: &mut Vec, + targs: &mut Vec, + index: &mut HashMap<&'t RootNode<'cfg>, (usize, usize)>) { + let art_index = arts.len(); + let trg_index = arts.len(); + arts.push(art); + targs.push(target); + index.insert(root, (trg_index, art_index)); + log::debug!( - "cargo: rustc: artifact: {}:{} ({:?}) {:#?}", - art.package_id.name(), - art.target.name, - art.target.kind(), - art.filenames.iter().map(|p| p.as_relative_to_root(config)) + "add to build: {}::{} {:?} for {}", + root.package_id().name(), + root.node().target().name, + root.node().target().kind, + match root.node().unit().platform { + CompileKind::Host => "host", + CompileKind::Target(ref kind) => kind.short_name(), + } ); + } + + + fn determine_crate_types<'t, 'a: 'trg, 'trg, 'cfg>( + cfg: &'t Config<'cfg>, + art: &'a Artifact, + target: &'trg Target, + tk: cargo::core::TargetKind, + ck: CompileKind) + -> impl Iterator)> + 'trg { + cfg.rustc_outputs(CompileMode::Build, &tk, &ck) + .log_err_cargo(cfg) + .into_iter() + .map(|(fts, _)| fts) + .flat_map(move |fts| { + art.filenames.iter().map(move |f| { + // compare with expected filename that should be: + let name = f.file_name().expect("artifact filename").to_string_lossy(); + let ct = fts.iter() + .find(|ft| name == ft.uplift_filename(target)) + .and_then(|ft| ft.crate_type.clone()); + (f, ct) + }) + }) + } + + + // Reduce a number of associated root to one, determining CrateType and CompileKind for each file of artifact: + const CYCLE_MAX: u8 = 10; // Usually no more then 1-2 times + let mut reserved_roots = HashSet::<&RootNode>::new(); + let mut anti_recursion = CYCLE_MAX; + let mut something_changed = false; + while anti_recursion == CYCLE_MAX || + (something_changed && anti_recursion > 0 && artifacts.iter().any(|(_, roots)| roots.len() > 1)) + { + anti_recursion -= 1; + something_changed = false; + + let mut targets_cache = HashMap::new(); + + artifacts.extract_if(|(art, roots)| { + let executable = art.executable.is_some(); + let need_remove_roots = roots.len() > 1; + + + log::trace!( + "Choosing one root of {} for artifact {}::{} {:?}{}", + roots.len(), + art.package_id.name(), + art.target.name, + art.target.kind(), + executable.then_some(", executable").unwrap_or_default() + ); + cfg.log_extra_verbose(|_| { + let f = art.filenames.iter().map(|p| p.as_relative_to_root(cfg)); + log::trace!(" with files: {f:?}",); + }); + + let mut crate_types_cache = BTreeMap::new(); + + let removed = roots.extract_if(|root| { + let tk = root.node().target().kind(); + let ck = root.node().unit().platform; + + log::trace!(" check {} {}", tk.description(), match ck { + CompileKind::Host => "host", + CompileKind::Target(ref kind) => kind.short_name(), + }); + + if reserved_roots.contains(root) { + log::trace!(" excluding, reserved root"); + return true; + } + + { + use cargo::core::TargetKind as Tk; + if executable && !matches!(tk, Tk::Bin | Tk::ExampleBin) { + log::trace!(" excluding, is not executable"); + return true; + } + } + + let target = targets_cache.entry(*root) + .or_insert_with(|| target_for_root(root)); + + let ct = determine_crate_types(cfg, art, target, tk.clone(), ck).collect::>(); + + let is_good = art.filenames.len() == ct.len() && + !ct.iter().any(|(_, ct)| ct.is_none()); + if is_good { + // save resolved crate-types: + let ct = ct.into_iter() + .filter_map(|(p, ct)| ct.map(|ct| (p.to_owned(), ct))); + crate_types_cache.extend(ct); + } + + need_remove_roots && !is_good + }) + .inspect(|r| { + let p = r.package_id().name(); + let t = r.node().target().name.as_str(); + log::trace!(" excluded: {p}::{t} {:?}", match r.node().unit().platform + { + CompileKind::Host => "host", + CompileKind::Target(ref kind) => kind.short_name(), + }) + }) + .count(); + + if removed > 0 { + log::trace!(" excluded: {removed}, now roots: {}", roots.len()); + } + + if removed > 0 || roots.len() == 1 { + something_changed = true; + } + + + if roots.len() == 1 { + reserved_roots.insert(roots[0]); + graph.cty.append(&mut crate_types_cache); + true + } else { + false + } + }) + .for_each(|(art, roots)| { + let r = roots[0]; + let trg = target_for_root(r); + add_to_graph(r, trg, art, &mut graph.art, &mut graph.trg, &mut graph.index); + }); + } + + + // Now we have artifacts with associated roots and all of them are kinda ok for thus artifacts. + // We only have to pick one root, so using fallback method now determining by path. + if !artifacts.is_empty() { + log::trace!("using fallback by path 😱"); + let export_dir = cfg.compile_options + .build_config + .export_dir + .clone() + .unwrap_or_else(|| cfg.workspace.target_dir().into_path_unlocked()); + artifacts.extract_if(|(art, roots)| { + let various_ck: Vec<_> = { + let mut ck: Vec<_> = roots.iter().map(|r| &r.node().unit().platform).collect(); + ck.sort(); + ck.dedup(); + ck + }; + let is_various_ck = various_ck.len() > 1; + let has_host = roots.iter() + .map(|r| &r.node().unit().platform) + .any(|ck| matches!(ck, CompileKind::Host)); + log::trace!("has host: {has_host}, various ck: {is_various_ck}"); + + + // num of path components for target/profile/this with filename: + let _num_comps = match art.target.kind() { + cargo::core::TargetKind::ExampleBin => 4, + cargo::core::TargetKind::ExampleLib(_) => 4, + _ => 3, + }; + + // possible variants: + let ck_name: Vec<_> = various_ck.iter() + .filter_map(|ck| { + match ck { + CompileKind::Host => None, + CompileKind::Target(ct) => Some(ct.short_name()), + } + }) + .collect(); + log::trace!(" possible ck: {ck_name:#?}"); + + let comps = art.filenames + .iter() + .filter_map(|f| f.strip_prefix(&export_dir).log_err_cargo(cfg).ok()) + .filter(|f| !f.as_os_str().is_empty()) + .map(|f| { + let comps = f.components().count(); + let first = f.components().next().map(|c| c.as_os_str()); + (comps, first) + }) + .collect::>(); + + let ck_exact = ck_name.iter().copied().find(|ck| { + comps.iter() + .all(|(_num, first)| matches!(first, Some(s) if *s == *ck)) + }); + + let removed = if let Some(ck) = ck_exact { + roots.extract_if(|root| { + match root.node().unit().platform { + CompileKind::Host => false, + CompileKind::Target(ct) => ct.short_name() != ck, + } + }) + .count() + } else if has_host { + roots.extract_if(|root| !matches!(root.node().unit().platform, CompileKind::Host)) + .count() + } else { + 0 + }; + + if removed > 0 { + log::trace!(" excluded: {removed}, now roots: {}", roots.len()); + } + + roots.len() == 1 + }) + .for_each(|(art, roots)| { + let r = roots[0]; + let trg = target_for_root(r); + let tk = r.node().target().kind(); + let ck = r.node().unit().platform; + let ct = determine_crate_types(cfg, &art, &trg, tk, ck).filter_map(|(p, ct)| { + ct.map(|ct| (p.to_owned(), ct)) + }); + graph.cty.extend(ct); + add_to_graph(r, trg, art, &mut graph.art, &mut graph.trg, &mut graph.index); + }); + } - // That's easier and faster then build bcx and inspecting unit-graph - let invocations = plan.build_package_invocations(&package.package_id()) - .collect::>(); - - let mapping = art.filenames - .iter() - .flat_map(|path| { - let invocation = - invocations.iter() - .find(|inv| inv.links.contains_key(path) || inv.outputs.contains(path)); - debug_assert!(invocation.is_some()); - log::trace!("{:?} <== {}", invocation.map(|i| i.kind), path.display()); - invocation.map(|i| (path, i)) - }) - .map(|(p, i)| { - let ct = if art.executable.as_deref() == Some(p.as_path()) { - CrateType::Bin - } else { - let ct = config.rustc_outputs(CompileMode::Build, &art.target.kind(), &i.kind) - .log_err_cargo(config) - .into_iter() - .flat_map(|out| out.0.into_iter()) - .find(|ft| { - // compare with final filename that should be: - let name = ft.uplift_filename(target); - p.file_name() == Some(OsStr::new(&name)) - }) - .and_then(|fty| fty.crate_type) - .unwrap_or_else(|| { - // We're probably never goes to this fallback, but it should be there anyway: - guess_crate_type(config, p, art.target.kind(), i.kind) - }); - log::debug!( - "crate type: {ct} - {:?}", - p.file_name().map(|s| s.to_string_lossy()) + + // log missed-mapped artifacts: + if artifacts_len != graph.art.len() { + cfg.log().verbose(|mut log| { + log.status_err(format_args!("Incomprehensible roots for {} artifacts:", artifacts.len())); + + for (art, roots) in &artifacts { + let files = art.filenames + .iter() + .map(|p| p.as_relative_to_root(cfg)) + .map(Path::to_string_lossy) + .intersperse(", ".into()) + .collect::(); + let p = art.package_id.name(); + let t = art.target.name; + let tk = art.target.kind().description(); + log.status_with_color("Artifact", format_args!("{p}::{t} {tk} with {files}"), Color::Red); + log.status("", format_args!(" potential roots: {}", roots.len())); + + for root in roots { + let tk = root.node().target().kind(); + let ck = root.node().unit().platform; + let p = root.package_id().name(); + let t = root.node().target().name.as_str(); + log.status("", format_args!(" {p}::{t} {tk:?}, {ck:?}")); + } + } + }); + } else { + log::trace!("Nothing is missed or incomprehensible 🎉"); + } + + debug_assert!(artifacts.is_empty(), "Missed-mapped artifacts: {artifacts:#?}"); + drop(artifacts); + + + // Finally, build artifacts: + let mut products = Vec::new(); + let mapped = graph.index + .iter() + .map(|(r, (t, a))| (*r, &graph.trg[*t], &graph.art[*a])); + + for (root, target, artifact) in mapped { + let tk = root.node().target().kind(); + let ck = root.node().unit().platform; + let package_id = root.package_id(); + let layout = cfg.layout_for(ck)?.lock(cfg.workspace.gctx())?; + log::debug!("Building {} {}", package_id.name(), target.description_named(),); + + let printable_target_name = || { + let package_crate_name = package_id.name().replace('-', "_"); + if package_crate_name == *artifact.target.name { + Cow::Borrowed(package_id.name().as_str()) + } else { + format!("{}, crate {}", package_id.name(), artifact.target.name).into() + } + }; + + for file in artifact.filenames.iter() { + let ct = if let Some(ct) = graph.cty.get(file) { + ct.clone() + } else if let Some(ct) = guess_cty(cfg, file, tk.clone(), ck) { + // We're probably never goes to this fallback, but it should be there anyway + ct + } else { + let name = printable_target_name(); + let trg = target.description_named(); + cfg.log().status_with_color( + "Skip", + format_args!("drop {name} {trg}, unable to determine crate-type"), + Color::Red, ); - ct - }; - (p, ct, i) - }); - - - for (path, ct, inv) in mapping { - let layout = config.layout_for(inv.kind)?.lock(config.workspace.gctx())?; - let artifact = CargoArtifact { package, - path, - name: art.target.name, - ct: ct.clone(), - ck: inv.kind, - profile: art.profile.to_owned(), - example: target.is_example() }; + continue; + }; + let layout = layout.as_inner(); + + let temp = CargoArtifact { package_id: package_id.to_owned(), + path: file, + name: artifact.target.name, + ct: ct.to_owned(), + ck, + profile: artifact.profile.to_owned(), + example: target.is_example() }; + let product = match ct { - CrateType::Bin if artifact.ck.is_playdate() => build_binary(config, layout, artifact), - CrateType::Dylib | CrateType::Cdylib if artifact.ck.is_simulator() => { - build_library(config, layout, artifact) - }, - CrateType::Staticlib if artifact.ck.is_playdate() => build_library(config, layout, artifact), + CrateType::Bin if ck.is_playdate() => build_binary(cfg, layout, temp), + CrateType::Dylib | CrateType::Cdylib if ck.is_simulator() => build_library(cfg, layout, temp), + CrateType::Staticlib if ck.is_playdate() => build_library(cfg, layout, temp), _ => { - let package_crate_name = package.name().replace('-', "_"); - let name = if package_crate_name == *art.target.name { - Cow::Borrowed(art.target.name.as_str()) - } else { - format!("{}, crate {}", package.name(), art.target.name).into() - }; - let target = if artifact.ck.is_playdate() { - "playdate" - } else { - "simulator" - }; - config.log().status_with_color( - "Skip", - format!( - "drop {name}, can't build {} targeting {target}", - artifact.ct - ), - Color::Yellow, + let name = printable_target_name(); + let target = if ck.is_playdate() { "playdate" } else { "simulator" }; + cfg.log().status_with_color( + "Skip", + format_args!("drop {name}, can't build {ct} targeting {target}"), + Color::Yellow, ); continue; }, }; - - if !config.compile_options.build_config.keep_going { + if !cfg.compile_options.build_config.keep_going { products.push(product?); - } else if let Ok(product) = product.log_err_cargo(config) { + } else if let Ok(product) = product.log_err_cargo(cfg) { products.push(product); } - config.log() - .status("Finished", format!("{} of {}", ct, inv.package_name)); + cfg.log().status( + "Finished", + format_args!("{} of {} {}", ct, package_id.name(), target.description_named()), + ); } } - - config.log().verbose(|mut log| { - log.status("Finished", "cargo execution, got products:"); - for (i, p) in products.iter().enumerate() { - let (head, msg) = match p { - BuildProduct::Success { name, src_ct, dst_ct, .. } => { - let ct = if dst_ct == src_ct { - format!("{dst_ct}") - } else { - format!("{src_ct} -> {dst_ct}") - }; - ("Build", format!("[{i}] {name} ({ct})")) - }, - BuildProduct::Skip { reason, ct, .. } => { - ("Skip", format!("[{i}] ({ct}) with reason: {reason}")) - }, - }; - log.status_with_color(head, msg, Color::White); - } - }); + cfg.log().verbose(|mut log| { + log.status("Finished", "cargo execution, got products:"); + for (i, p) in products.iter().enumerate() { + let (head, msg) = match p { + BuildProduct::Success { name, src_ct, dst_ct, .. } => { + let ct = if dst_ct == src_ct { + format!("{dst_ct}") + } else { + format!("{src_ct} -> {dst_ct}") + }; + ("Build", format!("[{i}] {name} ({ct})")) + }, + BuildProduct::Skip { reason, ct, .. } => { + ("Skip", format!("[{i}] ({ct}) with reason: {reason}")) + }, + }; + log.status_with_color(head, msg, Color::White); + } + }); Ok(products) } -fn read_cargo_output<'cc>( - config: &'cc Config, - cargo: Command) - -> CargoResult> { - use crate::proc::reader::format::CargoMessage; - +fn read_cargo_output<'cfg, 't>(config: &'cfg Config, + cargo: Command, + tree: &'t MetaDeps<'cfg>) + -> CargoResult>)>> { let mut reader = CargoJsonReader::new(cargo)?; let mut build_finished_success = None; @@ -249,26 +558,23 @@ fn read_cargo_output<'cc>( }; - let possible_targets = config.possible_targets()?; - // Add completion to iterator with asking & logging process status. // It's looks a little bit ugly with map to `Option` then `flat_map`, but // after optimization there is no these perturbations. // Also we don't need to fail entire process if one target fails and so status will not ok. - let artifacts = - map_artifacts(possible_targets, artifacts).map(Some) - .chain([reader].into_iter() - .flat_map(|mut r| { - r.status() - .log_err_cargo(config) - .ok() - .and_then(|status| { - status.exit_ok().log_err_cargo(config).ok() - }) - }) - .map(|_| None)) - .flatten() - .collect::>(); + let artifacts = map_artifacts(tree, artifacts).map(Some) + .chain([reader].into_iter() + .flat_map(|mut r| { + r.status() + .log_err_cargo(config) + .ok() + .and_then(|status| { + status.exit_ok().log_err_cargo(config).ok() + }) + }) + .map(|_| None)) + .flatten() + .collect::>(); let success = build_finished_success.filter(|v| *v) .ok_or_else(|| anyhow!("build not successful")); if !config.compile_options.build_config.keep_going { @@ -281,31 +587,34 @@ fn read_cargo_output<'cc>( } -fn map_artifacts<'cargo, 'cc>( - targets: impl Iterator>, - artifacts: impl Iterator + 'cargo) - -> impl Iterator { - let targets = targets.collect::>(); - artifacts.filter_map(move |art| { - targets.iter() - .find(|(p, ..)| p.package_id() == art.package_id) - .and_then(|(package, targets, ..)| { - targets.iter() - .find(|t| { - let crate_name = t.crate_name(); - (crate_name == *art.target.name || - crate_name == art.target.name.replace('-', "_")) && - t.kind() == &art.target.kind() - }) - .map(|target| (*package, *target, art)) - }) +fn map_artifacts<'cargo, 'cfg, 't>(tree: &'t MetaDeps<'cfg>, + artifacts: impl Iterator + 'cargo) + -> impl Iterator>)> { + use crate::utils::cargo::format::TargetKind as TK; + + artifacts.filter(|art| matches!(art.target.kind, TK::Lib(_) | TK::Bin | TK::Example)) + .filter_map(move |art| { + let findings = tree.roots() + .iter() + .filter(|r| { + let unit = r.node().unit(); + unit.package_id == art.package_id && + unit.target.name.as_str() == art.target.name.as_str() && + unit.target.kind() == art.target.kind() && + unit.target.crate_types == art.target.crate_types && + Some(unit.target.src_path.as_str().into()) == art.target.src_path + }) + .inspect(|r| log::trace!("root for artifact found: {:?} {r}", art.target.crate_types)) + .collect::>(); + (!findings.is_empty()).then_some((art, findings)) }) + .filter(|(_, roots)| !roots.is_empty()) } #[derive(Debug)] -struct CargoArtifact<'cfg, 'cr, Name: AsRef + Debug> { - package: &'cfg Package, +struct CargoArtifact<'cr, Name: AsRef + Debug> { + package_id: PackageId, path: &'cr Path, /// Crate name name: Name, @@ -319,9 +628,9 @@ struct CargoArtifact<'cfg, 'cr, Name: AsRef + Debug> { #[derive(Debug)] -pub enum BuildProduct<'cfg> { +pub enum BuildProduct { Success { - package: &'cfg Package, + package_id: PackageId, /// Crate-target ID name: String, @@ -339,17 +648,17 @@ pub enum BuildProduct<'cfg> { Skip { reason: String, - package: &'cfg Package, + package_id: PackageId, ct: CrateType, ck: CompileKind, }, } -impl<'cfg> BuildProduct<'cfg> { - fn skip_as_unsupported + Debug>(artifact: CargoArtifact<'cfg, '_, S>) -> Self { +impl BuildProduct { + fn skip_as_unsupported + Debug>(artifact: CargoArtifact<'_, S>) -> Self { let reason = format!( "{} ({}) {:?} unsupported target {:?}", - artifact.package.name(), + artifact.package_id.name(), artifact.name.as_ref(), artifact.ct, artifact.ck @@ -357,7 +666,7 @@ impl<'cfg> BuildProduct<'cfg> { Self::Skip { reason, ct: artifact.ct, ck: artifact.ck, - package: artifact.package } + package_id: artifact.package_id } } } @@ -376,10 +685,10 @@ impl<'t> Config<'t> { } -fn build_binary<'cfg, Layout, S>(config: &'cfg Config, - layout: Layout, - artifact: CargoArtifact<'cfg, '_, S>) - -> anyhow::Result> +fn build_binary(config: &Config, + layout: Layout, + artifact: CargoArtifact<'_, S>) + -> anyhow::Result where Layout: AsRef, S: AsRef + Debug { @@ -390,7 +699,7 @@ fn build_binary<'cfg, Layout, S>(config: &'cfg Config, artifact.path.display() ); - let package_crate_name = artifact.package.name().replace('-', "_"); + let package_crate_name = artifact.package_id.name().replace('-', "_"); let mut pdl = ForTargetLayout::new( layout.as_ref(), package_crate_name, @@ -420,7 +729,7 @@ fn build_binary<'cfg, Layout, S>(config: &'cfg Config, profile: artifact.profile, layout: pdl_ref.to_owned(), path: product.to_path_buf(), - package: artifact.package, + package_id: artifact.package_id, name: artifact.name.as_ref().to_owned(), example: artifact.example } } else { @@ -432,10 +741,10 @@ fn build_binary<'cfg, Layout, S>(config: &'cfg Config, } -fn build_library<'cfg, Layout, S>(config: &'cfg Config, - layout: Layout, - artifact: CargoArtifact<'cfg, '_, S>) - -> anyhow::Result> +fn build_library(config: &Config, + layout: Layout, + artifact: CargoArtifact<'_, S>) + -> anyhow::Result where Layout: AsRef, S: AsRef + Debug { @@ -444,7 +753,7 @@ fn build_library<'cfg, Layout, S>(config: &'cfg Config, format!( "{} of {}{}", artifact.ct, - artifact.package, + artifact.package_id, if config.workspace.gctx().extra_verbose() { format!(" from {}", artifact.path.as_relative_to_root(config).display()) } else { @@ -459,7 +768,7 @@ fn build_library<'cfg, Layout, S>(config: &'cfg Config, artifact.path.display() ); - let package_crate_name = artifact.package.name().replace('-', "_"); + let package_crate_name = artifact.package_id.name().replace('-', "_"); let mut pdl = ForTargetLayout::new( layout.as_ref(), package_crate_name, @@ -526,7 +835,7 @@ fn build_library<'cfg, Layout, S>(config: &'cfg Config, profile: artifact.profile, layout: pdl_ref.to_owned(), path: product.to_path_buf(), - package: artifact.package, + package_id: artifact.package_id, name: artifact.name.as_ref().to_owned(), example: artifact.example } } else if artifact.ck.is_simulator() { @@ -549,7 +858,7 @@ fn build_library<'cfg, Layout, S>(config: &'cfg Config, profile: artifact.profile, layout: pdl.to_owned(), path: product, - package: artifact.package, + package_id: artifact.package_id, name: artifact.name.as_ref().to_owned(), example: artifact.example } } else { @@ -564,37 +873,36 @@ fn build_library<'cfg, Layout, S>(config: &'cfg Config, /// Guesses a `CrateType` by `path`.ext and artifact crate types (target kind). /// /// `crate_types` should not be empty, otherwise `None` will be returned. -fn guess_crate_type(config: &Config, path: &Path, tk: cargo::core::TargetKind, ck: CompileKind) -> CrateType { +fn guess_cty(cfg: &Config, path: &Path, tk: cargo::core::TargetKind, ck: CompileKind) -> Option { let mut crate_types = tk.rustc_crate_types(); - let unknown = || CrateType::Other("UNKNOWN".into()); if crate_types.len() == 1 { - return crate_types.pop().unwrap_or_else(unknown); + return crate_types.pop(); } let ext = path.extension(); if ext.is_none() && crate_types.contains(&CrateType::Bin) { - return CrateType::Bin; + return Some(CrateType::Bin); } if let Some(ext) = ext { if ext == static_lib_suffix() && crate_types.contains(&CrateType::Staticlib) { - CrateType::Staticlib + Some(CrateType::Staticlib) } else { - let dylib_suffix = dylib_suffix_for_target(config, ck); + let dylib_suffix = dylib_suffix_for_target(cfg, ck); if Some(ext) == dylib_suffix.as_deref().map(OsStr::new) && (crate_types.contains(&CrateType::Dylib) || crate_types.contains(&CrateType::Cdylib)) { // For us it doesn't matter if it's dylib or cdylib - CrateType::Dylib + Some(CrateType::Dylib) } else if ext == OsStr::new("rlib") { - CrateType::Rlib + Some(CrateType::Rlib) } else { - unknown() + None } } } else { - unknown() + None } } diff --git a/cargo/src/layout/cargo.rs b/cargo/src/layout/cargo.rs index 47219fe5..35a8c3de 100644 --- a/cargo/src/layout/cargo.rs +++ b/cargo/src/layout/cargo.rs @@ -41,6 +41,10 @@ pub struct CargoLayout { tmp: PathBuf, } +impl AsRef for &'_ CargoLayout { + fn as_ref(&self) -> &CargoLayout { self } +} + impl CargoLayout { /// Calculate the paths for build output and return as a Layout. /// diff --git a/cargo/src/main.rs b/cargo/src/main.rs index 8768bd53..3993d7cb 100644 --- a/cargo/src/main.rs +++ b/cargo/src/main.rs @@ -1,5 +1,6 @@ -#![feature(extract_if)] #![feature(never_type)] +#![feature(extract_if)] +#![feature(iter_intersperse)] #![feature(exit_status_error)] #![feature(btree_extract_if)] #![feature(const_trait_impl)] @@ -77,13 +78,14 @@ fn execute(config: &Config) -> CargoResult<()> { return Err(anyhow::anyhow!("build-plan in not implemented yet")); } - build::build(config)?; + let deps_tree = crate::utils::cargo::meta_deps::meta_deps(config)?; + build::build(config, &deps_tree)?; }, cli::cmd::Cmd::Package => { let deps_tree = crate::utils::cargo::meta_deps::meta_deps(config)?; let assets = assets::build_all(config, &deps_tree)?; - let products = build::build(config)?; + let products = build::build(config, &deps_tree)?; log::debug!("assets artifacts: {}", assets.len()); log::debug!("build artifacts: {}", products.len()); @@ -150,17 +152,17 @@ fn execute(config: &Config) -> CargoResult<()> { // build requested package(s): let assets = assets::build_all(config, &deps_tree)?; - let mut products = build::build(config)?; + let mut products = build::build(config, &deps_tree)?; // filter products with expected: products.extract_if(|product| { match product { - build::BuildProduct::Success { package, + build::BuildProduct::Success { package_id, name, src_ct, .. } => { !expected.iter().any(|(p, targets)| { - p == package && + p.package_id() == *package_id && targets.iter().any(|t| { let crate_name = t.crate_name(); (name == &crate_name || diff --git a/cargo/src/package/mod.rs b/cargo/src/package/mod.rs index 0a5964bc..939bff8c 100644 --- a/cargo/src/package/mod.rs +++ b/cargo/src/package/mod.rs @@ -53,13 +53,13 @@ pub struct Product { pub fn build_all(config: &'_ Config, assets: AssetsArtifacts<'_, '_>, - products: Vec>) + products: Vec) -> CargoResult> { let products: Vec = products.into_iter().flat_map(TryInto::try_into).collect(); let mut targets = HashMap::<_, Vec<_>>::new(); for product in products { - let key = (product.package, product.name.to_owned()); + let key = (product.package_id, product.name.to_owned()); if let Some(products) = targets.get_mut(&key) { products.push(product); } else { @@ -69,9 +69,7 @@ pub fn build_all(config: &'_ Config, let mut results = Vec::new(); - for ((package, _), mut products) in targets { - let package_id = package.package_id(); - + for ((package_id, _), mut products) in targets { log::debug!( "Looking for assets artifacts for ({}) {}::{} for {}:", &products[0].src_ct, @@ -126,7 +124,7 @@ pub fn build_all(config: &'_ Config, results.push(result); }, _ => { - let result = package_multi_target(config, package, products, root, assets)?; + let result = package_multi_target(config, package_id, products, root, assets)?; results.push(result); }, } @@ -136,11 +134,11 @@ pub fn build_all(config: &'_ Config, } -fn package_single_target<'p, 'art>(config: &Config, - product: SuccessfulBuildProduct<'p>, - root: &RootNode<'_>, - assets: Option>) - -> CargoResult { +fn package_single_target<'art>(config: &Config, + product: SuccessfulBuildProduct, + root: &RootNode<'_>, + assets: Option>) + -> CargoResult { let presentable_name = product.presentable_name(); config.log().status( "Packaging", @@ -170,7 +168,7 @@ fn package_single_target<'p, 'art>(config: &Config, build_manifest( config, &product.layout, - product.package, + &product.package_id, root.as_source(), cargo_target, product.example, @@ -186,7 +184,7 @@ fn package_single_target<'p, 'art>(config: &Config, } let result = Product { name: product.name, - package_id: product.package.package_id(), + package_id: product.package_id, crate_types: vec![product.src_ct.clone()], targets: vec![product.ck], path: artifact.to_path_buf() }; @@ -204,12 +202,12 @@ fn package_single_target<'p, 'art>(config: &Config, /// So one executable and one or two dylibs. /// /// __Can't mix macos dylib with linux dylib in a one package.__ -fn package_multi_target<'p, 'art>(config: &Config, - package: &'p Package, - products: Vec, - root: &RootNode<'_>, - assets: Option>) - -> CargoResult { +fn package_multi_target<'art>(config: &Config, + package_id: PackageId, + products: Vec, + root: &RootNode<'_>, + assets: Option>) + -> CargoResult { let src_cts = products.iter() .map(|p| format!("{}", p.src_ct)) .collect::>() @@ -285,10 +283,9 @@ fn package_multi_target<'p, 'art>(config: &Config, } // cross-target layout: - let layout_target_name = Name::with_names(package.name().as_str(), products.first().map(|p| &p.name)); + let layout_target_name = Name::with_names(package_id.name().as_str(), products.first().map(|p| &p.name)); let mut layout = - CrossTargetLayout::new(config, package.package_id(), Some(layout_target_name))?.lock(config.workspace - .gctx())?; + CrossTargetLayout::new(config, package_id, Some(layout_target_name))?.lock(config.workspace.gctx())?; crate::layout::Layout::prepare(&mut layout.as_mut())?; @@ -300,7 +297,7 @@ fn package_multi_target<'p, 'art>(config: &Config, let mut dev = Default::default(); for product in &products { log::debug!("Preparing binaries for packaging {}", product.presentable_name()); - assert_eq!(package, product.package, "package must be same"); + assert_eq!(package_id, product.package_id, "package must be same"); let dst = layout.build().join(product.path.file_name().expect("file_name")); soft_link_checked(&product.path, &dst, true, layout.as_inner().target())?; @@ -340,7 +337,7 @@ fn package_multi_target<'p, 'art>(config: &Config, build_manifest( config, &layout, - package, + &package_id, root.as_source(), cargo_target, products[0].example, @@ -360,7 +357,7 @@ fn package_multi_target<'p, 'art>(config: &Config, soft_link_checked(&artifact, &link, true, product.layout.root())?; } - let result = Product { package_id: package.package_id(), + let result = Product { package_id, name: products[0].name.clone(), crate_types: products.iter().map(|p| p.src_ct.clone()).collect(), targets: products.iter().map(|p| p.ck).collect(), @@ -371,13 +368,13 @@ fn package_multi_target<'p, 'art>(config: &Config, fn build_manifest(config: &Config, layout: &Layout, - package: &Package, + package_id: &PackageId, source: impl PackageSource, cargo_target: Option>, dev: bool) -> CargoResult<()> { config.log().verbose(|mut log| { - let msg = format!("building package manifest for {}", package.package_id()); + let msg = format!("building package manifest for {}", package_id); log.status("Manifest", msg); }); @@ -522,8 +519,8 @@ fn prepare_assets>(config: &Config, #[allow(dead_code)] -struct SuccessfulBuildProduct<'cfg> { - package: &'cfg Package, +struct SuccessfulBuildProduct { + package_id: PackageId, /// Crate-target ID name: String, @@ -539,22 +536,22 @@ struct SuccessfulBuildProduct<'cfg> { example: bool, } -impl SuccessfulBuildProduct<'_> { +impl SuccessfulBuildProduct { pub fn presentable_name(&self) -> Cow<'_, str> { - if self.package.name().as_str() == self.name.as_str() { + if self.package_id.name().as_str() == self.name.as_str() { Cow::from(self.name.as_str()) } else { - Cow::from(format!("{}:{}", self.package.name(), self.name)) + Cow::from(format!("{}:{}", self.package_id.name(), self.name)) } } } -impl<'cfg> TryFrom> for SuccessfulBuildProduct<'cfg> { +impl TryFrom for SuccessfulBuildProduct { type Error = (); - fn try_from(product: BuildProduct<'cfg>) -> Result { + fn try_from(product: BuildProduct) -> Result { match product { - BuildProduct::Success { package, + BuildProduct::Success { package_id: package, name, src_ct, dst_ct, @@ -563,7 +560,7 @@ impl<'cfg> TryFrom> for SuccessfulBuildProduct<'cfg> { path, layout, example, } => { - Ok(Self { package, + Ok(Self { package_id: package, name, src_ct, dst_ct, diff --git a/cargo/src/proc/reader.rs b/cargo/src/proc/reader.rs index 07371005..9f3d8bae 100644 --- a/cargo/src/proc/reader.rs +++ b/cargo/src/proc/reader.rs @@ -166,7 +166,7 @@ pub mod format { pub manifest_path: PathBuf, pub target: SerializedTarget, pub profile: ArtifactProfile, - pub features: Vec, + pub features: Vec, pub filenames: Vec, pub executable: Option, pub fresh: bool, @@ -190,7 +190,7 @@ pub mod format { pub name: InternedString, pub src_path: Option, pub edition: InternedString, - pub required_features: Option>, + pub required_features: Option>, /// Whether docs should be built for the target via `cargo doc` /// See pub doc: bool, diff --git a/cargo/src/utils/cargo/format.rs b/cargo/src/utils/cargo/format.rs index 80df6c81..676d16be 100644 --- a/cargo/src/utils/cargo/format.rs +++ b/cargo/src/utils/cargo/format.rs @@ -6,8 +6,8 @@ use cargo::core::compiler::CompileTarget; use cargo::core::compiler::CrateType; use cargo::core::PackageId; use cargo::core::SourceId; -use serde::{Serialize, Deserialize}; -use serde::{Serializer, Deserializer}; +pub use serde::{Serialize, Deserialize}; +pub use serde::{Serializer, Deserializer}; #[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] diff --git a/cargo/src/utils/cargo/meta_deps.rs b/cargo/src/utils/cargo/meta_deps.rs index 394d54bb..855c90a6 100644 --- a/cargo/src/utils/cargo/meta_deps.rs +++ b/cargo/src/utils/cargo/meta_deps.rs @@ -49,6 +49,21 @@ pub struct RootNode<'cfg> { ws: Option<&'cfg WorkspaceMetadata>, } +impl Eq for RootNode<'_> {} +impl PartialEq for RootNode<'_> { + fn eq(&self, other: &Self) -> bool { + self.ws.is_some() == other.ws.is_some() && self.node == other.node && self.deps == other.deps + } +} + +impl std::hash::Hash for RootNode<'_> { + fn hash(&self, state: &mut H) { + self.node.hash(state); + self.deps.hash(state); + self.ws.is_some().hash(state); + } +} + impl<'t> RootNode<'t> { pub fn package_id(&self) -> &'t PackageId { self.node.package_id() } @@ -82,10 +97,23 @@ impl std::fmt::Display for RootNode<'_> { #[derive(Debug, Clone, Copy)] pub struct Node<'cfg> { - meta: Option<&'cfg Package>>, unit: &'cfg Unit, + meta: Option<&'cfg Package>>, +} + +impl Eq for Node<'_> {} +impl PartialEq for Node<'_> { + fn eq(&self, other: &Self) -> bool { self.meta.is_some() == other.meta.is_some() && self.unit == other.unit } } +impl std::hash::Hash for Node<'_> { + fn hash(&self, state: &mut H) { + self.unit.hash(state); + self.meta.is_some().hash(state); + } +} + + impl<'t> Node<'t> { pub fn package_id(&self) -> &'t PackageId { &self.unit.package_id } diff --git a/cargo/src/utils/cargo/unit_graph.rs b/cargo/src/utils/cargo/unit_graph.rs index dc505484..9a1f0f53 100644 --- a/cargo/src/utils/cargo/unit_graph.rs +++ b/cargo/src/utils/cargo/unit_graph.rs @@ -33,7 +33,7 @@ impl format::UnitTarget { use cargo::core::compiler::CrateType as CT; match self.kind { - format::TargetKind::Lib(ref ct) => TK::Lib(ct.clone()), + format::TargetKind::Lib(ref ct) => TK::Lib(ct.to_owned()), format::TargetKind::Bin => TK::Bin, format::TargetKind::Test => TK::Test, format::TargetKind::Bench => TK::Bench, @@ -41,7 +41,7 @@ impl format::UnitTarget { if &self.crate_types == &[CT::Bin] { TK::ExampleBin } else { - TK::ExampleLib(self.crate_types.clone()) + TK::ExampleLib(self.crate_types.to_owned()) } }, format::TargetKind::CustomBuild => TK::CustomBuild, @@ -67,22 +67,61 @@ impl format::UnitTarget { format::TargetKind::CustomBuild => TargetKindWild::CustomBuild, } } + + + pub fn cargo_target(&self) -> cargo::core::manifest::Target { + use cargo::core::manifest::Target; + + match self.kind { + format::TargetKind::Lib(_) => { + Target::lib_target( + &self.name, + self.crate_types.to_owned(), + self.src_path.as_str().into(), + self.edition, + ) + }, + format::TargetKind::Bin => { + Target::bin_target( + &self.name, + None, + self.src_path.as_str().into(), + None, + self.edition, + ) + }, + format::TargetKind::Example => { + Target::example_target( + &self.name, + self.crate_types.to_owned(), + self.src_path.as_str().into(), + None, + self.edition, + ) + }, + format::TargetKind::Test => unimplemented!("test cargo-target"), + format::TargetKind::Bench => unimplemented!("bench cargo-target"), + format::TargetKind::CustomBuild => unimplemented!("custom-build cargo-target"), + } + } } pub mod format { - #![allow(dead_code)] + use std::str::FromStr; + + use cargo::core::Edition; use cargo::core::PackageId; use cargo::util::command_prelude::CompileMode; use cargo::core::compiler::CompileKind; use cargo::core::compiler::CrateType; - use serde::Deserialize; pub use super::super::format::*; #[derive(Debug, Deserialize)] pub struct UnitGraph { + #[allow(dead_code)] pub version: usize, pub units: Vec, pub roots: Vec, @@ -112,7 +151,9 @@ pub mod format { pub crate_types: Vec, pub name: String, pub src_path: String, - // ... + #[serde(deserialize_with = "de_edition")] + pub edition: Edition, + // ... doc, doctest, test } #[derive(Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -123,4 +164,11 @@ pub mod format { #[serde(alias = "noprelude")] pub no_prelude: bool, } + + + pub fn de_edition<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + let s = <&str>::deserialize(deserializer)?; + Edition::from_str(s).map_err(serde::de::Error::custom) + } } diff --git a/cargo/src/utils/workspace.rs b/cargo/src/utils/workspace.rs index 61098889..8b0282fe 100644 --- a/cargo/src/utils/workspace.rs +++ b/cargo/src/utils/workspace.rs @@ -24,7 +24,6 @@ impl<'t> Config<'t> { Ok(members) } - #[deprecated(since = "0.5", note = "TODO: use unit_graph instead")] /// Returns a list of targets that are requested by the user. /// Resolved by requested spec initially, with fallback to all possible targets. pub fn possible_targets_ext(&'t self) -> CargoResult>> { @@ -38,7 +37,6 @@ impl<'t> Config<'t> { Ok(possible) } - #[deprecated(since = "0.5", note = "TODO: use unit_graph instead")] /// Returns a list of potential targets that are requested by the user. pub fn possible_targets(&'t self) -> CargoResult>> { let packages = self.members_with_features()?.into_iter().map(|(p, _)| p); @@ -46,7 +44,6 @@ impl<'t> Config<'t> { Ok(members) } - #[deprecated(since = "0.5", note = "TODO: use unit_graph instead")] pub fn possible_targets_with(&'t self, members: impl IntoIterator) -> impl Iterator> { @@ -60,7 +57,6 @@ impl<'t> Config<'t> { }) } - #[deprecated(since = "0.5", note = "TODO: determine from unit_graph instead")] pub fn possible_compile_kinds(&'t self) -> CargoResult> { let member_kinds = self.members_with_features()?.into_iter().flat_map(|(p, _)| { p.manifest()