diff --git a/crates/cargo-wdk/README.md b/crates/cargo-wdk/README.md index ad2d0a106..a42c46767 100644 --- a/crates/cargo-wdk/README.md +++ b/crates/cargo-wdk/README.md @@ -82,7 +82,10 @@ Driver Signing: --verify-signature Verify the signatures of the driver binary and catalog file after signing Inf2Cat Options: - --inf2cat-args Custom arguments to pass to `inf2cat` when generating the catalog file, e.g. `--inf2cat-args '/os:10_x64,10_GE_X64 /uselocaltime'` + --inf2cat-args Custom arguments to pass to `inf2cat` when generating the catalog file, e.g. `--inf2cat-args '/os:10_x64,10_GE_X64 /uselocaltime'` + +Stampinf Options: + --stampinf-args Custom arguments to pass to `stampinf` when generating the INF file, e.g. `--stampinf-args '-d 01/01/2026 -v 1.2.3.4 -p "Contoso Ltd"'` InfVerif Options: --infverif-args Custom arguments to pass to `infverif` when validating the INF, e.g. `--infverif-args '/rulever 10.0.22621 /info'` @@ -111,6 +114,10 @@ Building a sample driver requires the `--sample` flag. If it is not specified, t If you have a workspace with a mix of sample and non-sample driver projects, the build will fail as that scenario is not supported yet. In the future `build` will be able to automatically detect sample projects. That will remove the need for the `--sample` flag and enable support for this scenario. +#### Customizing `stampinf` arguments + +To customize the behaviour of `stampinf`, pass `--stampinf-args` with arguments to forward to `stampinf`. Args `-f`, `-a`, `-c`, `-k` and `-u` are not allowed because they are always supplied by `cargo-wdk` itself. + #### Customizing `inf2cat` arguments To target a specific set of Windows versions or to customize the behaviour of `inf2cat` in any other way, pass `--inf2cat-args` with a string of the arguments to forward to `inf2cat`. `cargo-wdk` itself provides the `/driver` argument so do not include it or its alias `/drv`. diff --git a/crates/cargo-wdk/src/actions/build/mod.rs b/crates/cargo-wdk/src/actions/build/mod.rs index e02a3311e..1e776dffb 100644 --- a/crates/cargo-wdk/src/actions/build/mod.rs +++ b/crates/cargo-wdk/src/actions/build/mod.rs @@ -80,6 +80,7 @@ pub struct BuildActionParams<'a> { pub target_arch: Option, pub sign_mode: SignMode, pub inf2cat_args: Option>, + pub stampinf_args: Option>, pub infverif_args: Option>, pub is_sample_class: bool, pub locked: bool, @@ -96,6 +97,7 @@ pub struct BuildAction<'a> { target_arch: Option, sign_mode: SignMode, inf2cat_args: Option>, + stampinf_args: Option>, infverif_args: Option>, is_sample_class: bool, locked: bool, @@ -146,6 +148,7 @@ impl<'a> BuildAction<'a> { target_arch: params.target_arch, sign_mode: params.sign_mode.clone(), inf2cat_args: params.inf2cat_args.clone(), + stampinf_args: params.stampinf_args.clone(), infverif_args: params.infverif_args.clone(), is_sample_class: params.is_sample_class, locked: params.locked, @@ -456,6 +459,7 @@ impl<'a> BuildAction<'a> { target_arch: &target_arch, sign_mode: self.sign_mode.clone(), inf2cat_args: self.inf2cat_args.clone(), + stampinf_args: self.stampinf_args.clone(), infverif_args: self.infverif_args.clone(), sample_class: self.is_sample_class, driver_model, diff --git a/crates/cargo-wdk/src/actions/build/package_task.rs b/crates/cargo-wdk/src/actions/build/package_task.rs index 28cf2b1ea..02998f746 100644 --- a/crates/cargo-wdk/src/actions/build/package_task.rs +++ b/crates/cargo-wdk/src/actions/build/package_task.rs @@ -85,6 +85,7 @@ pub struct PackageTaskParams<'a> { pub target_arch: &'a CpuArchitecture, pub sign_mode: SignMode, pub inf2cat_args: Option>, + pub stampinf_args: Option>, pub infverif_args: Option>, pub sample_class: bool, pub driver_model: DriverConfig, @@ -96,6 +97,7 @@ pub struct PackageTask<'a> { package_name: String, sign_mode: SignMode, inf2cat_args: Option>, + stampinf_args: Option>, infverif_args: Option>, sample_class: bool, @@ -202,6 +204,7 @@ impl<'a> PackageTask<'a> { package_name, sign_mode: params.sign_mode, inf2cat_args: params.inf2cat_args, + stampinf_args: params.stampinf_args, infverif_args: params.infverif_args, sample_class: params.sample_class, src_inx_file_path, @@ -377,35 +380,35 @@ impl<'a> PackageTask<'a> { let cat_file_path = format!("{}.cat", self.package_name); let dest_inf_file_path = self.dest_inf_file_path.to_string_lossy(); let arch = self.arch.to_string(); - let mut args: Vec<&str> = vec![ - "-f", - &dest_inf_file_path, - "-d", - "*", - "-a", - &arch, - "-c", - &cat_file_path, - ]; + let mut args: Vec<&str> = vec!["-f", &dest_inf_file_path]; - match std::env::var(STAMPINF_VERSION_ENV_VAR) { - Ok(version) if !version.trim().is_empty() => { - // When STAMPINF_VERSION is set to a non-empty, non-whitespace - // value, we intentionally omit -v so stampinf - // reads it and populates DriverVer. - // (Whitespace-only values are ignored.) - debug!( - DriverVer = version, - "Using {STAMPINF_VERSION_ENV_VAR} env var to set DriverVer" - ); - } - _ => { - args.extend(["-v", "*"]); + if !self.stampinf_args_contains("d") { + args.extend(["-d", "*"]); + } + args.extend(["-a", &arch, "-c", &cat_file_path]); + if self.stampinf_args_contains("v") { + debug!("Using -v from --stampinf-args to set DriverVer"); + } else { + match std::env::var(STAMPINF_VERSION_ENV_VAR) { + Ok(version) if !version.trim().is_empty() => { + // When STAMPINF_VERSION is set to a non-empty, + // non-whitespace value, we intentionally omit -v so + // stampinf reads it and populates DriverVer. + // (Whitespace-only values are ignored.) + debug!( + DriverVer = version, + "Using {STAMPINF_VERSION_ENV_VAR} env var to set DriverVer" + ); + } + _ => { + args.extend(["-v", "*"]); + } } } - if !wdf_version_flags.is_empty() { - args.append(&mut wdf_version_flags.iter().map(String::as_str).collect()); + args.extend(wdf_version_flags.iter().map(String::as_str)); + if let Some(stampinf_args) = &self.stampinf_args { + args.extend(stampinf_args.iter().map(String::as_str)); } if let Err(e) = self.command_exec.run("stampinf", &args, None, None) { return Err(PackageTaskError::StampinfCommand(e)); @@ -413,6 +416,13 @@ impl<'a> PackageTask<'a> { Ok(()) } + fn stampinf_args_contains(&self, arg_name: &str) -> bool { + self.stampinf_args.iter().flatten().any(|arg| { + arg.strip_prefix(['-', '/']) + .is_some_and(|arg| arg.eq_ignore_ascii_case(arg_name)) + }) + } + fn run_inf2cat(&self) -> Result<(), PackageTaskError> { info!("Running inf2cat"); let driver_arg = format!( @@ -733,6 +743,7 @@ mod tests { signtool_args: Vec::new(), }, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -803,6 +814,7 @@ mod tests { signtool_args: Vec::new(), }, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -835,6 +847,7 @@ mod tests { signtool_args: Vec::new(), }, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -876,6 +889,7 @@ mod tests { signtool_args: Vec::new(), }, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -917,6 +931,132 @@ mod tests { } } + fn assert_stampinf_args(env_version: Option<&str>, stampinf_args: &[&str], expected: &[&str]) { + let working_dir = PathBuf::from("C:/abs/driver"); + let target_dir = PathBuf::from("C:/abs/driver/target/debug"); + let arch = CpuArchitecture::Amd64; + + let params = PackageTaskParams { + package_name: "driver", + working_dir: &working_dir, + target_dir: &target_dir, + target_arch: &arch, + driver_model: DriverConfig::Kmdf(KmdfConfig::default()), + sample_class: false, + sign_mode: SignMode::Off, + inf2cat_args: None, + stampinf_args: Some(stampinf_args.iter().map(ToString::to_string).collect()), + infverif_args: None, + target_platform: TargetPlatform::Universal, + }; + + let wdk_build = WdkBuild::default(); + let fs = Fs::default(); + let mut command_exec = CommandExec::default(); + let expected_inf_file_path = target_dir + .join("driver_package") + .join("driver.inf") + .to_string_lossy() + .into_owned(); + let expected: Vec = expected.iter().map(ToString::to_string).collect(); + command_exec + .expect_run() + .withf(move |cmd: &str, args: &[&str], _, _| { + cmd == "stampinf" + && args.len() >= 2 + && args[0] == "-f" + && args[1] == expected_inf_file_path + && args[2..] == expected[..] + }) + .once() + .return_once(|_, _, _, _| { + Ok(Output { + status: ExitStatus::default(), + stdout: vec![], + stderr: vec![], + }) + }); + + let result = + crate::test_utils::with_env(&[(STAMPINF_VERSION_ENV_VAR, env_version)], || { + let task = PackageTask::new(params, &wdk_build, &command_exec, &fs); + task.run_stampinf() + }); + assert!(result.is_ok()); + } + + /// The `-k` value cargo-wdk derives from the default KMDF metadata. + fn default_kmdf_version() -> String { + let kmdf = KmdfConfig::default(); + format!( + "{}.{}", + kmdf.kmdf_version_major, kmdf.target_kmdf_version_minor + ) + } + + #[test] + fn run_stampinf_appends_custom_args_after_the_defaults() { + assert_stampinf_args( + None, + &["/p", "Contoso Ltd", "-n"], + &[ + "-d", + "*", + "-a", + "amd64", + "-c", + "driver.cat", + "-v", + "*", + "-k", + &default_kmdf_version(), + "/p", + "Contoso Ltd", + "-n", + ], + ); + } + + #[test] + fn run_stampinf_drops_default_date_and_version_when_caller_supplies_them() { + assert_stampinf_args( + None, + &["-d", "01/01/2026", "/V", "1.2.3.4"], + &[ + "-a", + "amd64", + "-c", + "driver.cat", + "-k", + &default_kmdf_version(), + "-d", + "01/01/2026", + "/V", + "1.2.3.4", + ], + ); + } + + #[test] + fn run_stampinf_caller_version_wins_over_env_var() { + assert_stampinf_args( + Some("9.9.9.9"), + &["/v", "1.2.3.4"], + &[ + "-d", + "*", + "-a", + "amd64", + "-c", + "driver.cat", + "-k", + &default_kmdf_version(), + "/v", + "1.2.3.4", + ], + ); + } + #[test] fn run_inf2cat_with_no_args_uses_arch_os_and_uselocaltime() { let working_dir = PathBuf::from("C:/abs/driver"); @@ -935,6 +1075,7 @@ mod tests { signtool_args: Vec::new(), }, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -981,6 +1122,7 @@ mod tests { signtool_args: Vec::new(), }, inf2cat_args: Some(Vec::new()), + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -1027,6 +1169,7 @@ mod tests { "/os:10_x64,10_CO_X64".to_string(), "/verbose".to_string(), ]), + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -1086,6 +1229,7 @@ mod tests { sample_class: false, sign_mode: SignMode::Off, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -1284,6 +1428,7 @@ mod tests { ], }, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform: TargetPlatform::Universal, }; @@ -1312,6 +1457,7 @@ mod tests { sample_class: false, sign_mode: SignMode::Off, inf2cat_args: None, + stampinf_args: None, infverif_args: None, target_platform, }; @@ -1390,6 +1536,7 @@ mod tests { sample_class: true, sign_mode: SignMode::Off, inf2cat_args: None, + stampinf_args: None, infverif_args: Some(vec![ "/rulever".to_string(), "10.0.22621".to_string(), diff --git a/crates/cargo-wdk/src/actions/build/tests.rs b/crates/cargo-wdk/src/actions/build/tests.rs index 2c23610bb..add1a19e5 100644 --- a/crates/cargo-wdk/src/actions/build/tests.rs +++ b/crates/cargo-wdk/src/actions/build/tests.rs @@ -1745,6 +1745,7 @@ fn initialize_build_action<'a>( target_arch, sign_mode, inf2cat_args: None, + stampinf_args: None, infverif_args: None, is_sample_class: sample_class, locked: test_build_action.locked, diff --git a/crates/cargo-wdk/src/cli.rs b/crates/cargo-wdk/src/cli.rs index 1ed472984..08097963f 100644 --- a/crates/cargo-wdk/src/cli.rs +++ b/crates/cargo-wdk/src/cli.rs @@ -164,6 +164,18 @@ pub struct BuildArgs { )] pub inf2cat_args: Option, + /// Custom arguments to pass to `stampinf` when generating the INF file, + /// e.g. `--stampinf-args '-d 01/01/2026 -v 1.2.3.4 -p "Contoso Ltd"'`. + #[arg( + long, + value_name = "ARGS", + // `stampinf` args can be `-` prefixed. + allow_hyphen_values = true, + value_parser = parse_passthrough_args, + help_heading = "Stampinf Options" + )] + pub stampinf_args: Option, + /// Custom arguments to pass to `infverif` when validating the INF, /// e.g. `--infverif-args '/rulever 10.0.22621 /info'`. #[arg( @@ -241,6 +253,41 @@ impl BuildArgs { Ok(Some(args)) } + /// Resolves the arguments to forward to `stampinf`. Rejects + /// the args cargo-wdk derives from the build itself: `-f`, `-a`, `-c`, + /// `-k` and `-u`. + /// Returns a `clap::Error` if the caller-supplied arguments are invalid. + fn stampinf_args(&self) -> Result>, clap::Error> { + const RESERVED_ARGS: [&str; 5] = ["f", "a", "c", "k", "u"]; + let Some(args) = self.stampinf_args.clone().map(|parsed| parsed.0) else { + return Ok(None); + }; + for arg in &args { + // `stampinf` accepts both `-x` and `/x`, case-insensitively. + let Some(arg_name) = arg.strip_prefix(['-', '/']) else { + continue; + }; + if RESERVED_ARGS + .iter() + .any(|reserved| arg_name.eq_ignore_ascii_case(reserved)) + { + let reserved_args = RESERVED_ARGS + .iter() + .map(|arg| format!("`-{arg}`")) + .collect::>() + .join(", "); + return Err(Cli::command().error( + ErrorKind::ArgumentConflict, + format!( + "`--stampinf-args` must not contain `{arg}`; cargo-wdk supplies the \ + {reserved_args} args itself" + ), + )); + } + } + Ok(Some(args)) + } + /// Resolves the arguments to forward to `infverif`. Rejects the arguments /// cargo-wdk supplies itself: the mode flags derived from the /// `--target-platform` option and the INF file path. @@ -398,6 +445,7 @@ impl Cli { Subcmd::Build(cli_args) => { let sign_mode = cli_args.sign_mode()?; let inf2cat_args = cli_args.inf2cat_args()?; + let stampinf_args = cli_args.stampinf_args()?; let infverif_args = cli_args.infverif_args()?; BuildAction::new( &BuildActionParams { @@ -406,6 +454,7 @@ impl Cli { target_arch: cli_args.target_arch, sign_mode, inf2cat_args, + stampinf_args, infverif_args, is_sample_class: cli_args.sample, locked: cli_args.locked, @@ -602,6 +651,31 @@ mod tests { ); } + #[test] + fn stampinf_args_rejects_args_reserved_by_cargo_wdk() { + for value in [ + "-f other.inf", + "-a arm64", + "-c other.cat", + "-k 1.15", + "-u 2.33.0", + "/c other.cat", + "-C other.cat", + "-d 01/01/2026 /A arm64", + ] { + let args = + parse_build_args(&["--stampinf-args", value]).expect("args should parse"); + let err = args + .stampinf_args() + .expect_err("reserved arg should be rejected"); + assert!( + err.to_string() + .contains("cargo-wdk supplies the `-f`, `-a`, `-c`, `-k`, `-u` args"), + "unexpected error for {value:?}: {err}" + ); + } + } + fn assert_infverif_args_rejected(value: &str, expected_reason: &str) { let args = parse_build_args(&["--infverif-args", value]).expect("args should parse"); let err = args @@ -633,6 +707,26 @@ mod tests { } } + #[test] + fn stampinf_args_allows_args_not_reserved_by_cargo_wdk() { + let args = parse_build_args(&[ + "--stampinf-args", + "-d 01/01/2026 /v 1.2.3.4 -p \"Contoso Ltd\"", + ]) + .expect("args should parse"); + assert_eq!( + args.stampinf_args().expect("should resolve"), + Some(vec![ + "-d".to_string(), + "01/01/2026".to_string(), + "/v".to_string(), + "1.2.3.4".to_string(), + "-p".to_string(), + "Contoso Ltd".to_string(), + ]) + ); + } + #[test] fn infverif_args_rejects_inf_paths() { for value in [ diff --git a/crates/cargo-wdk/tests/build_command_test.rs b/crates/cargo-wdk/tests/build_command_test.rs index 7d4e26524..ff2a157d8 100644 --- a/crates/cargo-wdk/tests/build_command_test.rs +++ b/crates/cargo-wdk/tests/build_command_test.rs @@ -745,6 +745,44 @@ fn kmdf_driver_with_custom_inf2cat_args_builds_successfully() { ); } +/// Functional tests for the `--stampinf-args` passthrough. +mod stampinf_args { + use super::*; + + #[test] + fn kmdf_driver_with_custom_date_and_version_builds_successfully() { + let driver = "kmdf-driver"; + clean_build_and_verify_project( + "kmdf", + driver, + None, + Some("01/01/2026,4.3.2.1"), + None, + None, + None, + None, + Some(&["--stampinf-args", "-d 01/01/2026 /v 4.3.2.1"]), + ); + } + + #[test] + fn custom_version_wins_over_stampinf_version_env_var() { + let driver = "kmdf-driver"; + let env = [(STAMPINF_VERSION_ENV_VAR, Some("9.9.9.9".to_string()))]; + clean_build_and_verify_project( + "kmdf", + driver, + None, + Some("4.3.2.1"), + None, + None, + Some(&env), + None, + Some(&["--stampinf-args", "/v 4.3.2.1"]), + ); + } +} + #[test] fn kmdf_driver_with_custom_infverif_args_builds_successfully() { let stderr = clean_build_and_verify_project( @@ -975,10 +1013,21 @@ fn assert_driver_ver(package_path: &str, driver_name: &str, driver_version: Opti }; // Example: DriverVer = 09/13/2023,1.0.0.0 + let (driver_date, driver_version) = match driver_version { + Some(val) if val.contains(',') => { + let (d, v) = val.split_once(',').unwrap(); + let d = (!d.is_empty()).then_some(d); + let v = (!v.is_empty()).then_some(v); + (d, v) + } + _ => (None, driver_version), + }; + + let driver_date_regex = driver_date.map_or_else(|| r"\d+/\d+/\d+".to_string(), regex::escape); let driver_version_regex = driver_version.map_or_else(|| r"\d+\.\d+\.\d+\.\d+".to_string(), regex::escape); let re = regex::Regex::new(&format!( - r"^DriverVer\s+=\s+\d+/\d+/\d+,{driver_version_regex}$" + r"^DriverVer\s+=\s+{driver_date_regex},{driver_version_regex}$" )) .unwrap();