Skip to content

Commit ddaf262

Browse files
Add optional member flag to prepare (#126)
* Add optional member If specified, and if the top-level cargo.toml has a members list, replace the list with only the specified member * Add workspace prefix * Nest fields * Fix tests Add none member to existing tests * Only write out contents if we have something to change * Add test * Add test from other implementation, making sure that we serialize properly and that no manifests reference excluded member * Rewrite member to bin and add bin option to cook * Update src/main.rs Per feedback Co-authored-by: Luca Palmieri <lucapalmieri1993@hotmail.com> * Change the language per feedback * Refactor members rewriting to operate in memory and be side-effect free * Remove part of test that relies on side effect * Update documentation for prepare * Add error message alternatively, this could return nothing, but maybe having an error for when someone tries to specify a bin and it doesn't work is worth having. * Remove unused result return since we bulldoze whatever was there, not worrying about if it was an array or not * Add bin options to cook Co-authored-by: Luca Palmieri <lucapalmieri1993@hotmail.com>
1 parent 0dae8d2 commit ddaf262

File tree

5 files changed

+120
-23
lines changed

5 files changed

+120
-23
lines changed

src/main.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ pub struct Prepare {
5252
/// It defaults to "recipe.json".
5353
#[clap(long, default_value = "recipe.json")]
5454
recipe_path: PathBuf,
55+
56+
/// When --bin is specified, `cargo-chef` will ignore all members of the workspace
57+
/// that are not necessary to successfully compile the specific binary.
58+
#[clap(long)]
59+
bin: Option<String>,
5560
}
5661

5762
#[derive(Parser)]
@@ -113,6 +118,10 @@ pub struct Cook {
113118
/// Cook using `#[no_std]` configuration (does not affect `proc-macro` crates)
114119
#[clap(long)]
115120
no_std: bool,
121+
/// When --bin is specified, `cargo-chef` will ignore all members of the workspace
122+
/// that are not necessary to successfully compile the specific binary.
123+
#[clap(long)]
124+
bin: Option<String>,
116125
}
117126

118127
fn _main() -> Result<(), anyhow::Error> {
@@ -144,6 +153,7 @@ fn _main() -> Result<(), anyhow::Error> {
144153
workspace,
145154
offline,
146155
no_std,
156+
bin,
147157
}) => {
148158
if atty::is(atty::Stream::Stdout) {
149159
eprintln!("WARNING stdout appears to be a terminal.");
@@ -220,11 +230,13 @@ fn _main() -> Result<(), anyhow::Error> {
220230
workspace,
221231
offline,
222232
no_std,
233+
bin,
223234
})
224235
.context("Failed to cook recipe.")?;
225236
}
226-
Command::Prepare(Prepare { recipe_path }) => {
227-
let recipe = Recipe::prepare(current_directory).context("Failed to compute recipe")?;
237+
Command::Prepare(Prepare { recipe_path, bin }) => {
238+
let recipe =
239+
Recipe::prepare(current_directory, bin).context("Failed to compute recipe")?;
228240
let serialized =
229241
serde_json::to_string(&recipe).context("Failed to serialize recipe.")?;
230242
fs::write(recipe_path, serialized).context("Failed to save recipe to 'recipe.json'")?;

src/recipe.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ pub struct CookArgs {
3131
pub workspace: bool,
3232
pub offline: bool,
3333
pub no_std: bool,
34+
pub bin: Option<String>,
3435
}
3536

3637
impl Recipe {
37-
pub fn prepare(base_path: PathBuf) -> Result<Self, anyhow::Error> {
38-
let skeleton = Skeleton::derive(&base_path)?;
38+
pub fn prepare(base_path: PathBuf, member: Option<String>) -> Result<Self, anyhow::Error> {
39+
let skeleton = Skeleton::derive(&base_path, member)?;
3940
Ok(Recipe { skeleton })
4041
}
4142

@@ -83,7 +84,8 @@ fn build_dependencies(args: &CookArgs) {
8384
package,
8485
workspace,
8586
offline,
86-
..
87+
bin,
88+
no_std: _no_std,
8789
} = args;
8890
let cargo_path = std::env::var("CARGO").expect("The `CARGO` environment variable was not set. This is unexpected: it should always be provided by `cargo` when invoking a custom sub-command, allowing `cargo-chef` to correctly detect which toolchain should be used. Please file a bug.");
8991
let mut command = Command::new(cargo_path);
@@ -133,6 +135,9 @@ fn build_dependencies(args: &CookArgs) {
133135
if let Some(package) = package {
134136
command_with_args.arg("--package").arg(package);
135137
}
138+
if let Some(binary_target) = bin {
139+
command_with_args.arg("--bin").arg(binary_target);
140+
}
136141
if *workspace {
137142
command_with_args.arg("--workspace");
138143
}

src/skeleton/mod.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,17 @@ pub(in crate::skeleton) struct ParsedManifest {
2929

3030
impl Skeleton {
3131
/// Find all Cargo.toml files in `base_path` by traversing sub-directories recursively.
32-
pub fn derive<P: AsRef<Path>>(base_path: P) -> Result<Self, anyhow::Error> {
32+
pub fn derive<P: AsRef<Path>>(
33+
base_path: P,
34+
member: Option<String>,
35+
) -> Result<Self, anyhow::Error> {
3336
// Read relevant files from the filesystem
3437
let config_file = read::config(&base_path)?;
3538
let mut manifests = read::manifests(&base_path, config_file.as_deref())?;
39+
if let Some(member) = member {
40+
ignore_all_members_except(&mut manifests, member);
41+
}
42+
3643
let mut lock_file = read::lockfile(&base_path)?;
3744

3845
version_masking::mask_local_crate_versions(&mut manifests, &mut lock_file);
@@ -307,3 +314,18 @@ fn serialize_manifests(manifests: Vec<ParsedManifest>) -> Result<Vec<Manifest>,
307314
}
308315
Ok(serialised_manifests)
309316
}
317+
318+
/// If the top-level `Cargo.toml` has a `members` field, replace it with
319+
/// a list consisting of just the specified member.
320+
fn ignore_all_members_except(manifests: &mut [ParsedManifest], member: String) {
321+
let workspace_toml = manifests
322+
.iter_mut()
323+
.find(|manifest| manifest.relative_path == std::path::PathBuf::from("Cargo.toml"));
324+
325+
if let Some(members) = workspace_toml
326+
.and_then(|toml| toml.contents.get_mut("workspace"))
327+
.and_then(|workspace| workspace.get_mut("members"))
328+
{
329+
*members = toml::Value::Array(vec![toml::Value::String(member)]);
330+
}
331+
}

tests/recipe.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fn quick_recipe(content: &str) -> Recipe {
1414
bin_dir.child(filename).touch().unwrap();
1515
test_dir.child(filename).touch().unwrap();
1616
}
17-
Recipe::prepare(recipe_directory.path().into()).unwrap()
17+
Recipe::prepare(recipe_directory.path().into(), None).unwrap()
1818
}
1919

2020
#[test]

tests/skeletons.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ path = "src/main.rs"
3434
.unwrap();
3535

3636
// Act
37-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
37+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
3838
let cook_directory = TempDir::new().unwrap();
3939
skeleton
4040
.build_minimum_project(cook_directory.path(), false)
@@ -53,7 +53,7 @@ path = "src/main.rs"
5353
.assert(predicate::path::exists());
5454

5555
// Act (no_std)
56-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
56+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
5757
let cook_directory = TempDir::new().unwrap();
5858
skeleton
5959
.build_minimum_project(cook_directory.path(), true)
@@ -140,7 +140,7 @@ uuid = { version = "=0.8.0", features = ["v4"] }
140140
project_b.child("src").child("lib.rs").touch().unwrap();
141141

142142
// Act
143-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
143+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
144144
let cook_directory = TempDir::new().unwrap();
145145
skeleton
146146
.build_minimum_project(cook_directory.path(), false)
@@ -162,7 +162,7 @@ uuid = { version = "=0.8.0", features = ["v4"] }
162162
.assert("");
163163

164164
// Act (no_std)
165-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
165+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
166166
let cook_directory = TempDir::new().unwrap();
167167
skeleton
168168
.build_minimum_project(cook_directory.path(), true)
@@ -229,7 +229,7 @@ harness = false
229229
.unwrap();
230230

231231
// Act
232-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
232+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
233233
let cook_directory = TempDir::new().unwrap();
234234
skeleton
235235
.build_minimum_project(cook_directory.path(), false)
@@ -277,7 +277,7 @@ name = "foo"
277277
.unwrap();
278278

279279
// Act
280-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
280+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
281281
let cook_directory = TempDir::new().unwrap();
282282
skeleton
283283
.build_minimum_project(cook_directory.path(), false)
@@ -290,7 +290,7 @@ name = "foo"
290290
cook_directory.child("tests").child("foo.rs").assert("");
291291

292292
// Act (no_std)
293-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
293+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
294294
let cook_directory = TempDir::new().unwrap();
295295
skeleton
296296
.build_minimum_project(cook_directory.path(), true)
@@ -349,7 +349,7 @@ name = "foo"
349349
.unwrap();
350350

351351
// Act
352-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
352+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
353353
let cook_directory = TempDir::new().unwrap();
354354
skeleton
355355
.build_minimum_project(cook_directory.path(), false)
@@ -365,7 +365,7 @@ name = "foo"
365365
.assert("fn main() {}");
366366

367367
// Act (no_std)
368-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
368+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
369369
let cook_directory = TempDir::new().unwrap();
370370
skeleton
371371
.build_minimum_project(cook_directory.path(), true)
@@ -409,14 +409,14 @@ edition = "2018"
409409
bin_dir.child("f.rs").touch().unwrap();
410410

411411
// Act
412-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
412+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
413413

414414
// What we're testing is that auto-directories come back in the same order.
415415
// Since it's possible that the directories just happen to come back in the
416416
// same order randomly, we'll run this a few times to increase the
417417
// likelihood of triggering the problem if it exists.
418418
for _ in 0..5 {
419-
let skeleton2 = Skeleton::derive(recipe_directory.path()).unwrap();
419+
let skeleton2 = Skeleton::derive(recipe_directory.path(), None).unwrap();
420420
assert_eq!(
421421
skeleton, skeleton2,
422422
"Skeletons of equal directories are not equal. Check [[bin]] ordering in manifest?"
@@ -453,7 +453,7 @@ pub fn config_toml() {
453453
.unwrap();
454454

455455
// Act
456-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
456+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
457457
let cook_directory = TempDir::new().unwrap();
458458
skeleton
459459
.build_minimum_project(cook_directory.path(), false)
@@ -502,7 +502,7 @@ pub fn version() {
502502
.unwrap();
503503

504504
// Act
505-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
505+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
506506
let cook_directory = TempDir::new().unwrap();
507507
skeleton
508508
.build_minimum_project(cook_directory.path(), false)
@@ -555,7 +555,7 @@ version = "1.2.3"
555555
.unwrap();
556556

557557
// Act
558-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
558+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
559559
let cook_directory = TempDir::new().unwrap();
560560
skeleton
561561
.build_minimum_project(cook_directory.path(), false)
@@ -671,7 +671,7 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
671671
project_b.child("src").child("lib.rs").touch().unwrap();
672672

673673
// Act
674-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
674+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
675675
let cook_directory = TempDir::new().unwrap();
676676
skeleton
677677
.build_minimum_project(cook_directory.path(), false)
@@ -886,12 +886,70 @@ description = "sample package representing all of rocket's dependencies""#;
886886
.unwrap();
887887

888888
// Act
889-
let skeleton = Skeleton::derive(recipe_directory.path()).unwrap();
889+
let skeleton = Skeleton::derive(recipe_directory.path(), None).unwrap();
890890

891891
// Assert
892892
assert_eq!(1, skeleton.manifests.len());
893893
}
894894

895+
#[test]
896+
pub fn specify_member_in_workspace() {
897+
// Arrange
898+
let workspace_content = r#"
899+
[workspace]
900+
901+
members = [
902+
"backend",
903+
"ci",
904+
]
905+
"#;
906+
907+
let first_content = r#"
908+
[package]
909+
name = "backend"
910+
version = "0.1.0"
911+
edition = "2018"
912+
"#;
913+
914+
let recipe_directory = TempDir::new().unwrap();
915+
let manifest = recipe_directory.child("Cargo.toml");
916+
manifest.write_str(workspace_content).unwrap();
917+
let backend = recipe_directory.child("backend");
918+
backend.create_dir_all().unwrap();
919+
920+
backend
921+
.child("Cargo.toml")
922+
.write_str(first_content)
923+
.unwrap();
924+
backend.child("src").create_dir_all().unwrap();
925+
backend.child("src").child("main.rs").touch().unwrap();
926+
927+
// Act
928+
let skeleton = Skeleton::derive(recipe_directory.path(), "backend".to_string().into()).unwrap();
929+
930+
let gold = r#"[workspace]
931+
members = ["backend"]
932+
"#;
933+
934+
// Assert:
935+
// - that "ci" is not in `skeleton`'s manifests
936+
assert!(skeleton
937+
.manifests
938+
.iter()
939+
.all(|manifest| !manifest.contents.contains("ci")));
940+
941+
// - that the root manifest matches the file contents
942+
assert!(
943+
skeleton
944+
.manifests
945+
.iter()
946+
.find(|manifest| manifest.relative_path == std::path::PathBuf::from("Cargo.toml"))
947+
.unwrap()
948+
.contents
949+
== gold
950+
);
951+
}
952+
895953
fn check(actual: &str, expect: Expect) {
896954
let actual = actual.to_string();
897955
expect.assert_eq(&actual);

0 commit comments

Comments
 (0)