feat: add PE resource version + metadata generation - #661
Conversation
… var as the same as an absent env var, update documentation, update and add unit tests
There was a problem hiding this comment.
Pull request overview
This PR adds automatic generation and compilation of Windows PE VERSIONINFO resources for driver binaries built with wdk-build, so that file version and descriptive metadata are embedded directly into the produced .sys / .dll artifacts (not just the .inf).
Changes:
- Introduces
resource_compilemodule to generate a.rc, compile it viarc.exeto a.res, and emit linker args to embed it into the driver binary. - Extends
ConfigErrorandConfig::configure_binary_buildto run the version-resource compilation step during build script configuration. - Updates WDK metadata parsing so
[package.metadata.wdk.*]sections (e.g.,version-resource) can coexist while still strictly validating thedriver-modelconfiguration, plus adds unit tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/wdk-build/src/resource_compile.rs | New module implementing version/metadata resolution, .rc generation, rc.exe invocation, and link-arg emission, with unit tests. |
| crates/wdk-build/src/metadata/mod.rs | Narrows WDK configuration deserialization to metadata.wdk.driver-model while permitting other metadata.wdk.* sections; adds tests. |
| crates/wdk-build/src/lib.rs | Exposes the new module, adds a ConfigError variant, and invokes resource compilation from configure_binary_build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #661 +/- ##
==========================================
+ Coverage 80.47% 82.25% +1.77%
==========================================
Files 26 27 +1
Lines 5720 6693 +973
Branches 5720 6693 +973
==========================================
+ Hits 4603 5505 +902
- Misses 989 1037 +48
- Partials 128 151 +23 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…tness for different dev environment setups
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Alan632 <aln.noda7@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Alan632 <aln.noda7@gmail.com>
… typos.toml used in rc.exe args
…se crate only), fix lint issues
…ary rerun directive (CI builds are fresh), fix formatting in .typos.toml
| DriverConfig::Umdf(_) => ("VFT_DLL", "VFT2_UNKNOWN"), | ||
| }; | ||
|
|
||
| let mut rc = String::with_capacity(1024); |
There was a problem hiding this comment.
Since you're writing into a string anyways, I think this chunk would be a lot more readable and easier to grok the RC file contents if it did a template with string replacement similar to
.|
|
||
| let rc = generate_rc_content(version, &metadata, &config); | ||
|
|
||
| assert!(rc.contains("#pragma code_page(65001)")); |
There was a problem hiding this comment.
Not necessarily required for this PR to merge, but I think some snapshot testing here would be better since the output is fairly complex. It would be worth looking into insta or snapbox
| @@ -0,0 +1,1645 @@ | |||
| // Copyright (c) Microsoft Corporation | |||
There was a problem hiding this comment.
I think there should be an integration test asserting the version derived here matches the version in the inf. You can probably add that test coverage by piggybacking on some of the existing integration tests in cargo-wdk
| fn resolve_version() -> Result<DriverVersion, ResourceCompileError> { | ||
| let version_str = env_var_non_empty(VERSION_ENV_VAR).map_or_else( | ||
| || { | ||
| env::var("CARGO_PKG_VERSION").map_err(|_| ResourceCompileError::MetadataError { |
There was a problem hiding this comment.
what is the current behavior here if there is not version defined (since cargo made version field optional)? Does it error out or does it default to 0.0.0?
| let metadata = cargo_metadata::MetadataCommand::new() | ||
| .manifest_path(&manifest_path) | ||
| .no_deps() | ||
| .exec() | ||
| .map_err(|e| ResourceCompileError::MetadataError { | ||
| detail: format!("cargo metadata failed: {e}"), | ||
| })?; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
crates/wdk-build/src/resource_compile.rs:303
cargo_metadata::MetadataCommandhere is executed without settingcurrent_dir. Elsewhere in wdk-build, metadata commands setcurrent_dirto the manifest’s parent directory to ensure.cargo/config.tomloverrides are honored. Without this, resource metadata extraction can differ from the rest of the build (e.g., custom registries/config), depending on the build script’s working directory.
let manifest_path = Path::new(&manifest_dir).join("Cargo.toml");
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&manifest_path)
.no_deps()
.exec()
| /// Reads an environment variable, returning `None` for both unset and empty | ||
| /// values. eWDK/vcvars occasionally export variables with empty defaults that | ||
| /// should be treated as unset. | ||
| pub fn env_var_non_empty(key: &str) -> Option<String> { | ||
| env::var(key).ok().filter(|value| !value.is_empty()) | ||
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
crates/wdk-build/src/resource_compile.rs:304
read_version_resource_metadatarunscargo_metadatawithout settingcurrent_dir. Elsewhere inwdk-buildyou explicitly setcurrent_dirto the manifest directory to ensure.cargo/config.tomloverrides are respected (e.g.,Config::from_env_autoincrates/wdk-build/src/lib.rs:446-453). Setting it here makes metadata resolution more robust when the build script’s working directory isn’t the manifest dir.
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&manifest_path)
.no_deps()
.exec()
.map_err(|e| ResourceCompileError::MetadataError {
| [type.resource_compile] | ||
| extend-glob = ["resource_compile.rs"] | ||
|
|
||
| [type.resource_compile.extend-words] | ||
| fo = "fo" # rc.exe /fo (output file) switch |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.typos.toml:10
typosfile globs are matched against the file’s relative path (as evidenced by existing patterns like**/Cargo.lock).extend-glob = ["resource_compile.rs"]likely won’t matchcrates/wdk-build/src/resource_compile.rs, so thefoallowlist won’t take effect and the typos check can still fail.
[type.resource_compile]
extend-glob = ["resource_compile.rs"]
[type.resource_compile.extend-words]
fo = "fo" # rc.exe /fo (output file) switch
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
crates/wdk-build/src/resource_compile.rs:703
compile_version_resource_innerreadsSTAMPINF_VERSION(viaresolve_version) but doesn’t emit a correspondingcargo:rerun-if-env-changeddirective. SinceConfig::from_env_auto()already emitscargo:rerun-if-changeddirectives, Cargo will not rerun the build script when onlySTAMPINF_VERSIONchanges, leaving a stale embedded VERSIONINFO on incremental builds.
fn compile_version_resource_inner(config: &Config) -> Result<(), ResourceCompileError> {
let version = resolve_version()?;
let metadata = read_version_resource_metadata()?;
…nv var set by `cargo` now, update documentation and unit tests to match
…indows-drivers-rs into driver_file_versioning
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (5)
.typos.toml:7
extend-glob = ["resource_compile.rs"]likely won’t matchcrates/wdk-build/src/resource_compile.rs, so the allowlisted wordfomay still triptyposin CI. Use a path/glob that matches the file undercrates/.
extend-glob = ["resource_compile.rs"]
crates/wdk-build/src/resource_compile.rs:248
resolve_version()ignoresSTAMPINF_VERSION, but the PR description/validation claims the embedded PE version is sourced fromSTAMPINF_VERSION(with fallback to Cargo). As-is, settingSTAMPINF_VERSIONwon’t affect the generated VERSIONINFO resource.
/// Determine the driver version to embed in the binary from the env var set by
/// `cargo`.
fn resolve_version() -> Result<DriverVersion, ResourceCompileError> {
let version_str =
env::var("CARGO_PKG_VERSION").map_err(|_| ResourceCompileError::MetadataError {
detail: "CARGO_PKG_VERSION environment variable not set. This function must be called \
from a Cargo build script."
.to_string(),
})?;
parse_version(&version_str)
}
crates/wdk-build/src/resource_compile.rs:943
- If
STAMPINF_VERSIONis intended to drive the embedded VERSIONINFO, the unit tests should cover precedence and fallback (currently onlyCARGO_PKG_VERSIONis exercised). This also helps prevent regressions whereSTAMPINF_VERSIONis accidentally ignored.
#[test]
fn resolve_version_uses_cargo_pkg_version() {
let version =
with_env(&[("CARGO_PKG_VERSION", Some("1.2.3"))], resolve_version).unwrap();
assert_eq!(
version,
DriverVersion {
major: 1,
minor: 2,
patch: 3,
revision: 0
}
);
}
crates/wdk-build/src/resource_compile.rs:29
- The module docs say the version always comes from
CARGO_PKG_VERSION, but the PR description statesSTAMPINF_VERSIONshould take precedence (falling back to Cargo). Either update the docs or the implementation so they’re consistent.
This issue also appears in the following locations of the same file:
- line 237
- line 928
//! The version is read from `CARGO_PKG_VERSION`, which Cargo sets from the
//! package's `[package]` version.
crates/wdk-build/src/resource_compile.rs:1343
- The
create_include_subdirectoriestest helper treats entries like"km\\crt"as a single directory name on non-Windows platforms, which makes these tests OS-dependent (it won’t createkm/crt, soresource_include_pathswon’t find kernel include dirs). Split subdirectory strings on both\\and/so the tests behave consistently across host OSes.
fn create_include_subdirectories(include_directory: &Path, subdirectories: &[&str]) {
for subdirectory in subdirectories {
fs::create_dir_all(include_directory.join(subdirectory)).unwrap();
}
Summary
Currently driver PE resource metadata and versioning do not get populated (the version in the *.inf does) and makes issue and post-mortem troubleshooting/tracing difficult.
This PR adds the functionality to populate the PE resource metadata automatically.
wdk-build/src/lib.rsconfigure_binary_buildto initiate *.rc generation, compilation to *.res, and linker directive emissionwdk-build/src/resource_compile.rscompile_version_resourcecalled fromconfigure_binary_buildSTAMPINF_VERSION, falling back toCARGO_PKG_VERSIONset by Cargorc.exeto compile to a *.res and emits linker directiveswdk-build/src/metadata/mod.rs[package.metadata.wdk.version-resource]), keeps strict serde validation of Wdk/DriverConfig (eg.[package.metadata.wdk.driver-model])Validation
Local testing with in production driver in the following scenarios: missing version-resource metadata, populated version-resource metadata, and
STAMPINF_VERSIONset.Tested with sample drivers kmdf/wdm/umdf.
WDM sample driver with
[package.metadata.wdk.version-resource]info filled in:UMDF sample driver without
[package.metadata.wdk.version-resource]info: