Skip to content

Commit 99ccdcb

Browse files
committed
cargo-rail: fixing the feaure minimization
1 parent 40582e5 commit 99ccdcb

File tree

3 files changed

+66
-21
lines changed

3 files changed

+66
-21
lines changed

src/cargo/unify/minimize.rs

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -427,13 +427,13 @@ fn test_with_features(
427427
// Write modified TOML
428428
std::fs::write(workspace_toml_path, doc.to_string())?;
429429

430-
// OPTIMIZATION: Check only affected members instead of whole workspace
431-
let check_passed = run_cargo_check_targeted(used_by_members, ctx.workspace_root())?;
430+
// OPTIMIZATION: Build + test only affected members instead of whole workspace
431+
let build_and_test_passed = run_cargo_build_and_test_targeted(used_by_members, ctx.workspace_root())?;
432432

433433
// Always restore original content
434434
std::fs::write(workspace_toml_path, original_content)?;
435435

436-
Ok(check_passed)
436+
Ok(build_and_test_passed)
437437
}
438438

439439
/// Set dependency features in workspace.dependencies to exact list
@@ -480,38 +480,72 @@ fn set_dependency_features(doc: &mut toml_edit::DocumentMut, dep_name: &str, fea
480480
Ok(false)
481481
}
482482

483-
/// Run cargo check for SPECIFIC workspace members (graph-aware optimization)
483+
/// Run cargo build + test for SPECIFIC workspace members (graph-aware optimization)
484484
///
485485
/// **HUGE PERFORMANCE WIN:**
486-
/// Instead of `cargo check --workspace` (checks all 50+ crates),
486+
/// Instead of `cargo build --workspace` (checks all 50+ crates),
487487
/// we check only the 2-3 crates that actually use this dependency.
488-
fn run_cargo_check_targeted(members: &[String], workspace_root: &Path) -> RailResult<bool> {
489-
let mut cmd = Command::new("cargo");
490-
cmd.arg("check");
491-
492-
// OPTIMIZATION: Check only affected members
488+
///
489+
/// **Why build instead of check:**
490+
/// - `cargo check` allows some code that fails during actual compilation/linking
491+
/// - Features may affect codegen, proc-macros, or linking behavior
492+
/// - More accurate validation at slight performance cost
493+
///
494+
/// **Why also run tests:**
495+
/// - Some features only affect runtime behavior (e.g., serde's preserve_order)
496+
/// - Tests catch functional regressions that compilation doesn't
497+
fn run_cargo_build_and_test_targeted(members: &[String], workspace_root: &Path) -> RailResult<bool> {
498+
// Step 1: Build with all targets
499+
let mut build_cmd = Command::new("cargo");
500+
build_cmd.arg("build");
501+
502+
// OPTIMIZATION: Build only affected members
493503
if members.len() <= 5 {
494-
// Small number of members - check them explicitly
504+
// Small number of members - build them explicitly
495505
for member in members {
496-
cmd.arg("-p").arg(member);
506+
build_cmd.arg("-p").arg(member);
497507
}
498508
} else {
499-
// Many members use this dep - check whole workspace (rare case)
500-
cmd.arg("--workspace");
509+
// Many members use this dep - build whole workspace (rare case)
510+
build_cmd.arg("--workspace");
501511
}
502512

503-
// CRITICAL FIX: Check all targets (tests, examples, benches)
513+
// CRITICAL: Build all targets (tests, examples, benches)
504514
// This prevents removing features that are only used in tests/examples
505-
cmd.arg("--all-targets");
515+
build_cmd.arg("--all-targets");
506516

507-
cmd.current_dir(workspace_root);
517+
build_cmd.current_dir(workspace_root);
508518

509519
// Suppress output - we only care about exit status
510-
cmd.stdout(std::process::Stdio::null());
511-
cmd.stderr(std::process::Stdio::null());
520+
build_cmd.stdout(std::process::Stdio::null());
521+
build_cmd.stderr(std::process::Stdio::null());
522+
523+
let build_output = build_cmd.output()?;
524+
if !build_output.status.success() {
525+
return Ok(false);
526+
}
527+
528+
// Step 2: Run tests to ensure functionality
529+
let mut test_cmd = Command::new("cargo");
530+
test_cmd.arg("test");
531+
532+
// Test same members
533+
if members.len() <= 5 {
534+
for member in members {
535+
test_cmd.arg("-p").arg(member);
536+
}
537+
} else {
538+
test_cmd.arg("--workspace");
539+
}
540+
541+
test_cmd.current_dir(workspace_root);
542+
543+
// Suppress output
544+
test_cmd.stdout(std::process::Stdio::null());
545+
test_cmd.stderr(std::process::Stdio::null());
512546

513-
let output = cmd.output()?;
514-
Ok(output.status.success())
547+
let test_output = test_cmd.output()?;
548+
Ok(test_output.status.success())
515549
}
516550

517551
#[cfg(test)]

src/commands/init.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ pub fn run_init_standalone(
209209
output: crate::config::UnifyOutputConfig::default(),
210210
backup: crate::config::UnifyBackupConfig::default(),
211211
minimize_features: false,
212+
keep_features: std::collections::HashMap::new(),
212213
},
213214
release: crate::config::ReleaseConfig::default(),
214215
splits: vec![],

src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ pub struct UnifyConfig {
7575
/// This is the "killer feature" - automated feature pruning integrated into unify
7676
#[serde(default)]
7777
pub minimize_features: bool,
78+
79+
/// Features to always keep during minimization (optional user whitelist)
80+
/// Format: dependency_name = ["feature1", "feature2"]
81+
/// Example:
82+
/// [unify.keep_features]
83+
/// serde_json = ["preserve_order"]
84+
/// clap = ["default"]
85+
#[serde(default)]
86+
pub keep_features: std::collections::HashMap<String, Vec<String>>,
7887
}
7988

8089
/// Conflict handling configuration for unification
@@ -373,6 +382,7 @@ impl Default for UnifyConfig {
373382
output: UnifyOutputConfig::default(),
374383
backup: UnifyBackupConfig::default(),
375384
minimize_features: false,
385+
keep_features: std::collections::HashMap::new(),
376386
}
377387
}
378388
}

0 commit comments

Comments
 (0)