diff --git a/src/main.rs b/src/main.rs index a9ecf5a..cf6d0af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ pub struct Prepare { /// When --bin is specified, `cargo-chef` will ignore all members of the workspace /// that are not necessary to successfully compile the specific binary. #[arg(long)] - bin: Option, + bin: Vec, } #[derive(Parser)] @@ -305,7 +305,7 @@ fn _main() -> Result<(), anyhow::Error> { } Command::Prepare(Prepare { recipe_path, bin }) => { let recipe = - Recipe::prepare(current_directory, bin).context("Failed to compute recipe")?; + Recipe::prepare(current_directory, &bin).context("Failed to compute recipe")?; let serialized = serde_json::to_string(&recipe).context("Failed to serialize recipe.")?; fs::write(recipe_path, serialized).context("Failed to save recipe to 'recipe.json'")?; diff --git a/src/recipe.rs b/src/recipe.rs index 1755ceb..53bcc5b 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -51,8 +51,8 @@ pub struct CookArgs { } impl Recipe { - pub fn prepare(base_path: PathBuf, member: Option) -> Result { - let skeleton = Skeleton::derive(base_path, member)?; + pub fn prepare(base_path: PathBuf, members: &[String]) -> Result { + let skeleton = Skeleton::derive(base_path, members)?; Ok(Recipe { skeleton }) } diff --git a/src/skeleton/mod.rs b/src/skeleton/mod.rs index d3d2e81..fc43afb 100644 --- a/src/skeleton/mod.rs +++ b/src/skeleton/mod.rs @@ -44,22 +44,19 @@ pub(in crate::skeleton) struct ParsedManifest { impl Skeleton { /// Find all Cargo.toml files in `base_path` by traversing sub-directories recursively. - pub fn derive>( - base_path: P, - member: Option, - ) -> Result { + pub fn derive>(base_path: P, members: &[String]) -> Result { // Use `--no-deps` to skip dependency resolution when we don't need the full graph // (i.e. no `--bin` filtering). We still need full resolution when there's no existing // Cargo.lock, since `cargo metadata` generates it as a side effect. - let no_deps = member.is_none() && base_path.as_ref().join("Cargo.lock").exists(); + let no_deps = members.is_empty() && base_path.as_ref().join("Cargo.lock").exists(); let graph = extract_package_graph(base_path.as_ref(), no_deps)?; // Read relevant files from the filesystem let config_file = read::config(&base_path)?; let mut manifests = read::manifests(&base_path, &graph)?; let mut lock_file = read::lockfile(&base_path)?; - if let Some(member) = &member { - filter_to_member_closure(&mut manifests, &mut lock_file, &graph, member)?; + if !members.is_empty() { + filter_to_member_closure(&mut manifests, &mut lock_file, &graph, members)?; } let rust_toolchain_file = read::rust_toolchain(&base_path)?; @@ -337,27 +334,34 @@ fn filter_to_member_closure( manifests: &mut Vec, lock_file: &mut Option, graph: &PackageGraph, - member: &str, + members: &[String], ) -> Result<(), anyhow::Error> { let ws = graph.workspace(); - // `member` may be a package name or a binary target name (from --bin). - // Try package name first, then search for a binary target. - let pkg = match ws.member_by_name(member) { - Ok(pkg) => pkg, - Err(_) => ws - .iter() - .find(|pkg| { - pkg.build_targets().any(|t| { - matches!(t.id(), guppy::graph::BuildTargetId::Binary(name) if name == member) + + let mut pkgs = Vec::with_capacity(members.len()); + + for member in members { + // `member` may be a package name or a binary target name (from --bin). + // Try package name first, then search for a binary target. + let pkg = match ws.member_by_name(member) { + Ok(pkg) => pkg, + Err(_) => ws + .iter() + .find(|pkg| { + pkg.build_targets().any(|t| { + matches!(t.id(), guppy::graph::BuildTargetId::Binary(name) if name == member) + }) }) - }) - .ok_or_else(|| { - anyhow::anyhow!("No workspace package or binary target named '{member}'") - })?, - }; + .ok_or_else(|| { + anyhow::anyhow!("No workspace package or binary target named '{member}'") + })?, + }; + + pkgs.push(pkg.id().clone()); + } // Get transitive closure of all dependencies - let resolved = graph.query_forward(std::iter::once(pkg.id()))?.resolve(); + let resolved = graph.query_forward(pkgs.iter())?.resolve(); // Collect workspace member names in the closure let closure_members: HashSet = ws diff --git a/tests/recipe.rs b/tests/recipe.rs index fcb99bc..72c23ca 100644 --- a/tests/recipe.rs +++ b/tests/recipe.rs @@ -14,7 +14,7 @@ fn quick_recipe(content: &str) -> Recipe { bin_dir.child(filename).touch().unwrap(); test_dir.child(filename).touch().unwrap(); } - Recipe::prepare(recipe_directory.path().canonicalize().unwrap(), None).unwrap() + Recipe::prepare(recipe_directory.path().canonicalize().unwrap(), &[]).unwrap() } #[test] diff --git a/tests/skeletons/tests/core.rs b/tests/skeletons/tests/core.rs index 7d93b16..ce678b8 100644 --- a/tests/skeletons/tests/core.rs +++ b/tests/skeletons/tests/core.rs @@ -24,7 +24,7 @@ path = "src/main.rs" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -43,7 +43,7 @@ path = "src/main.rs" .assert(predicate::path::exists()); // Act (no_std) - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), true) @@ -116,7 +116,7 @@ uuid = { version = "=0.8.0", features = ["v4"] } .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -138,7 +138,7 @@ uuid = { version = "=0.8.0", features = ["v4"] } .assert(""); // Act (no_std) - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), true) @@ -192,7 +192,7 @@ harness = false .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -230,7 +230,7 @@ name = "foo" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -243,7 +243,7 @@ name = "foo" cook_directory.child("tests").child("foo.rs").assert(""); // Act (no_std) - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), true) @@ -293,7 +293,7 @@ harness = false .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -325,7 +325,7 @@ name = "foo" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -341,7 +341,7 @@ name = "foo" .assert("fn main() {}"); // Act (no_std) - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), true) @@ -387,14 +387,14 @@ edition = "2018" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); // What we're testing is that auto-directories come back in the same order. // Since it's possible that the directories just happen to come back in the // same order randomly, we'll run this a few times to increase the // likelihood of triggering the problem if it exists. for _ in 0..5 { - let skeleton2 = Skeleton::derive(project.path(), None).unwrap(); + let skeleton2 = Skeleton::derive(project.path(), &[]).unwrap(); assert_eq!( skeleton, skeleton2, "Skeletons of equal directories are not equal. Check [[bin]] ordering in manifest?" @@ -436,7 +436,7 @@ workspace = true .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); // Assert - lints should be stripped from all manifests for manifest in &skeleton.manifests { @@ -489,7 +489,7 @@ reqwest = { .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); // Assert assert_eq!(1, skeleton.manifests.len()); diff --git a/tests/skeletons/tests/masking.rs b/tests/skeletons/tests/masking.rs index aadfb32..c15840b 100644 --- a/tests/skeletons/tests/masking.rs +++ b/tests/skeletons/tests/masking.rs @@ -19,7 +19,7 @@ edition = "2018" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -57,7 +57,7 @@ edition = "2018" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -100,7 +100,7 @@ version = "1.2.3" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -203,7 +203,7 @@ dependencies = [ .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -346,7 +346,7 @@ crate-type = ["cdylib"] .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -479,7 +479,7 @@ edition = "2021" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) diff --git a/tests/skeletons/tests/toolchain.rs b/tests/skeletons/tests/toolchain.rs index d56bdd7..5cabda7 100644 --- a/tests/skeletons/tests/toolchain.rs +++ b/tests/skeletons/tests/toolchain.rs @@ -21,7 +21,7 @@ edition = "2021" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -68,7 +68,7 @@ channel = "1.75.0" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -202,7 +202,7 @@ uuid = { version = "=0.8.0", features = ["v4"] } // Act let path = project.path(); - let all = Skeleton::derive(&path, None).unwrap(); + let all = Skeleton::derive(&path, &[]).unwrap(); assert_eq!( manifest_content_dirs(&all), vec![ @@ -214,37 +214,37 @@ uuid = { version = "=0.8.0", features = ["v4"] } ] ); - let project_a = Skeleton::derive(&path, Some("project_a".into())).unwrap(); + let project_a = Skeleton::derive(&path, &["project_a".into()]).unwrap(); assert_eq!( manifest_content_dirs(&project_a), vec!["crates/client/project_a"] ); - let project_b = Skeleton::derive(&path, Some("project_b".into())).unwrap(); + let project_b = Skeleton::derive(&path, &["project_b".into()]).unwrap(); assert_eq!( manifest_content_dirs(&project_b), vec!["crates/client/project_b"] ); - let project_c = Skeleton::derive(&path, Some("project_c".into())).unwrap(); + let project_c = Skeleton::derive(&path, &["project_c".into()]).unwrap(); assert_eq!( manifest_content_dirs(&project_c), vec!["crates/server/project_c"] ); - let project_d = Skeleton::derive(&path, Some("project_d".into())).unwrap(); + let project_d = Skeleton::derive(&path, &["project_d".into()]).unwrap(); assert_eq!( manifest_content_dirs(&project_d), vec!["crates/server/project_d"] ); - let project_e = Skeleton::derive(&path, Some("project_e".into())).unwrap(); + let project_e = Skeleton::derive(&path, &["project_e".into()]).unwrap(); assert_eq!( manifest_content_dirs(&project_e), vec!["vendored/project_e"] ); - let project_f = Skeleton::derive(&path, Some("project_f".into())).unwrap(); + let project_f = Skeleton::derive(&path, &["project_f".into()]).unwrap(); assert_eq!(manifest_content_dirs(&project_f), vec!["project_f"]); // TODO: If multiple binaries are valid in `cargo chef prepare`, then testing diff --git a/tests/skeletons/tests/workspace.rs b/tests/skeletons/tests/workspace.rs index 6eb4c7e..e969cf6 100644 --- a/tests/skeletons/tests/workspace.rs +++ b/tests/skeletons/tests/workspace.rs @@ -77,7 +77,7 @@ description = "sample package representing all of rocket's dependencies" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); // Assert assert_eq!(1, skeleton.manifests.len()); @@ -118,7 +118,7 @@ edition = "2018" .build(); // Act - let skeleton = Skeleton::derive(project.path(), "backend".to_string().into()).unwrap(); + let skeleton = Skeleton::derive(project.path(), &["backend".to_string()]).unwrap(); // Assert: // - that "ci" is *still* in the list of `skeleton`'s manifests @@ -198,7 +198,7 @@ anyhow = { workspace = true } .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); let cook_directory = TempDir::new().unwrap(); skeleton .build_minimum_project(cook_directory.path(), false) @@ -320,7 +320,7 @@ version = "0.0.1" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); // Assert assert_eq!(skeleton.manifests.len(), 3); @@ -358,7 +358,7 @@ edition = "2021" .build(); // Act - let skeleton = Skeleton::derive(project.path(), Some("bin_a".to_string())).unwrap(); + let skeleton = Skeleton::derive(project.path(), &["bin_a".to_string()]).unwrap(); // Assert — only root workspace manifest + bin_a assert_eq!(skeleton.manifests.len(), 2); @@ -415,7 +415,7 @@ edition = "2021" .build(); // Act - let skeleton = Skeleton::derive(project.path(), Some("bin_a".to_string())).unwrap(); + let skeleton = Skeleton::derive(project.path(), &["bin_a".into()]).unwrap(); // Assert — root workspace + bin_a + lib_a (bin_b excluded) assert_eq!(skeleton.manifests.len(), 3); @@ -469,7 +469,7 @@ my_ryu = { workspace = true } ) .build(); - let skeleton = Skeleton::derive(project.path(), Some("bin_a".to_string())).unwrap(); + let skeleton = Skeleton::derive(project.path(), &["bin_a".into()]).unwrap(); // Root manifest should keep my_itoa but not my_ryu let root = skeleton @@ -516,7 +516,7 @@ version = "0.2.1" .build(); // Act - let skeleton = Skeleton::derive(project.path(), None).unwrap(); + let skeleton = Skeleton::derive(project.path(), &[]).unwrap(); check( &skeleton.manifests[1].contents,