Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
88d3948
vhf library linker directive redesign, pull out static library linker…
Jun 18, 2026
ed27cc2
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Jun 18, 2026
0cce966
moved mechanism and data logic into wdk-build, wdk-sys now only selec…
Jun 18, 2026
e8756f1
Merge branch 'vhf_lib_linker_args-v2' of https://github.com/Alan632/w…
Jun 18, 2026
a3b51b8
utilize the test-stubs feature in wdk-sys to gate the new link attrib…
Jun 19, 2026
453f9db
-redesign to closely match Config::headers pattern (apisubset selecti…
Jun 19, 2026
f8f26b7
- add a cfg() conditional compilation guard on not(test)) to the link…
Jun 22, 2026
20cced8
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Jun 30, 2026
f0d20e6
Addressing PR comments
Jul 2, 2026
f179fb7
Merge branch 'vhf_lib_linker_args-v2' of https://github.com/Alan632/w…
Jul 2, 2026
9e6f1c8
formatting
Jul 2, 2026
2967d71
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Jul 2, 2026
8348a3f
- add new line after unsafe block in link directive return string
Jul 6, 2026
11453ac
Merge branch 'vhf_lib_linker_args-v2' of https://github.com/Alan632/w…
Jul 6, 2026
082282c
forgot to update unit tests with change to render function
Jul 6, 2026
0b5deed
toml formatting
Jul 6, 2026
d828928
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Jul 20, 2026
ac529c7
- fix: naming of a couple tests for clarity
Jul 20, 2026
4f5e9a4
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Jul 21, 2026
06cbdd3
Audited and refactored comments making them more succinct and removin…
Jul 21, 2026
ec66202
make a TODO comment more clear
Jul 21, 2026
b7c4f20
adding comment back in
Jul 21, 2026
219b1e3
NOT_TEST_CFG is private so shouldn't be linked in the comment
Jul 21, 2026
683ec71
also removing link in comment to `None`
Jul 21, 2026
3b15dc4
refine comments across changed files
Jul 23, 2026
6a143ac
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Jul 23, 2026
217e72b
- move implementation comment into function and refine the doc commen…
Jul 27, 2026
61c6bf9
Comment refinement
Alan632 Jul 27, 2026
d54c71d
- add a new feature to wdk-sys "no-link", where enabling "no-link" pr…
Jul 28, 2026
779e687
- remove `lto = true` from dev profiles across the repo. The dev prof…
Jul 29, 2026
8de321b
- add doc comments to test_stubs.rs
Jul 29, 2026
2b529ff
- rename the new wdk-sys feature from "no-link" to "omit-wdk-libs" du…
Jul 29, 2026
3aeb28c
- enable test-stubs in mixed-package-kmdf-workspace's driver crate
Jul 30, 2026
ad9c371
- add a step in test.yaml to run the tests for each of the sample dri…
Jul 30, 2026
71c9719
- refactor doc comments in wdk-build/lib.rs
Jul 30, 2026
b08e80b
- add WdfDriverGlobals stub to test_stubs
Jul 31, 2026
3378625
- formatting
Jul 31, 2026
aed6759
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Aug 4, 2026
d77203f
- change the new wdk-sys feature name "omit-wdk-libs" to "suppress-wd…
Aug 5, 2026
716bd64
comment formatting
Aug 6, 2026
f26ffc6
Merge branch 'main' into vhf_lib_linker_args-v2
Alan632 Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 213 additions & 32 deletions crates/wdk-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::{
sync::LazyLock,
};

use ::bindgen::Builder;
pub use bindgen::BuilderExt;
use metadata::TryFromCargoMetadataError;
use tracing::debug;
Expand Down Expand Up @@ -1057,6 +1058,97 @@ impl Config {
.collect())
}

/// Returns the native libraries that must be linked for a given
/// [`ApiSubset`], based off this [`Config`].
///
/// This is the link-directive analogue of [`Config::headers`]: it owns the
/// mapping from [`ApiSubset`] (and the active [`DriverConfig`] and
/// [`CpuArchitecture`]) to the set of [`LinkDirective`]s that should be
/// emitted into the generated bindings for that subset. Subsets that
/// contribute no extra libraries return an empty [`Vec`].
///
/// Unlike header selection, link subsets must be selected
/// *non-cumulatively*: each generated bindings file should request only
/// its own subset's libraries (e.g. `hid.rs` requests only
/// [`ApiSubset::Hid`], not [`ApiSubset::Base`]), since the base
/// libraries are already emitted into the base bindings file. This
/// avoids duplicate `#[link]` directives across generated files.
#[must_use]
pub fn link_directives(&self, api_subset: ApiSubset) -> Vec<LinkDirective<'static>> {
match api_subset {
ApiSubset::Base => self.base_link_directives(),
ApiSubset::Hid => self.hid_link_directives(),
ApiSubset::Wdf
| ApiSubset::Gpio
| ApiSubset::ParallelPorts
| ApiSubset::Spb
| ApiSubset::Storage
| ApiSubset::Usb => Vec::new(),
}
}

/// Builds the static library link directives for the base bindings.
///
/// TODO: Once [link-arg-attribute](https://doc.rust-lang.org/unstable-book/language-features/link-arg-attribute.html)
/// stabilizes, the `cargo::rustc-cdylib-link-arg=*` directives from
/// [`wdk_build::Config::configure_binary_build`] will be added
#[tracing::instrument(level = "trace")]
Comment thread
Alan632 marked this conversation as resolved.
Outdated
Comment thread
leon-xd marked this conversation as resolved.
Outdated
fn base_link_directives(&self) -> Vec<LinkDirective<'static>> {
const fn static_lib(name: &'static str) -> LinkDirective<'static> {
LinkDirective::new(name, "static").with_modifiers(&["-bundle"])
}

let mut directives = Vec::new();
match &self.driver_config {
DriverConfig::Wdm => {
directives.extend([
static_lib("BufferOverflowFastFailK"),
static_lib("ntoskrnl"),
static_lib("hal"),
static_lib("wmilib"),
]);
}
DriverConfig::Kmdf(_) => {
directives.extend([
static_lib("BufferOverflowFastFailK"),
static_lib("ntoskrnl"),
static_lib("hal"),
static_lib("wmilib"),
static_lib("WdfLdr"),
static_lib("WdfDriverEntry"),
]);
}
DriverConfig::Umdf(umdf_config) => {
if umdf_config.umdf_version_major >= 2 {
directives.extend([static_lib("WdfDriverStubUm"), static_lib("ntdll")]);
}
directives.push(static_lib("OneCoreUAP"));
}
}

// ARM64-specific libraries to link to derived from
Comment thread
leon-xd marked this conversation as resolved.
Outdated
// WindowsDriver.arm64.props.
if matches!(
self.driver_config,
Comment thread
leon-xd marked this conversation as resolved.
Outdated
DriverConfig::Wdm | DriverConfig::Kmdf(_)
) && self.cpu_architecture == CpuArchitecture::Arm64
{
directives.push(static_lib("arm64rt"));
}

directives
}

#[tracing::instrument(level = "trace")]
fn hid_link_directives(&self) -> Vec<LinkDirective<'static>> {
match self.driver_config {
DriverConfig::Wdm | DriverConfig::Kmdf(_) => {
vec![LinkDirective::new("VhfKm", "static").with_modifiers(&["-bundle"])]
}
DriverConfig::Umdf(_) => vec![LinkDirective::new("VhfUm", "dylib")],
}
}

/// Configure a Cargo build of a library that depends on the WDK. This
/// emits specially formatted prints to Cargo based on this [`Config`].
///
Expand Down Expand Up @@ -1125,20 +1217,12 @@ impl Config {
println!("cargo::rustc-link-search={}", path.display());
}

// TODO: Once [link-arg-attribute](https://doc.rust-lang.org/unstable-book/language-features/link-arg-attribute.html)
// stabilizes, the `cargo::rustc-cdylib-link-arg=*` directives will be moved to
// the wdk-sys build script
match &self.driver_config {
DriverConfig::Wdm => {
// Emit WDM-specific libraries to link to
println!("cargo::rustc-link-lib=static=BufferOverflowFastFailK");
println!("cargo::rustc-link-lib=static=ntoskrnl");
println!("cargo::rustc-link-lib=static=hal");
println!("cargo::rustc-link-lib=static=wmilib");

// Emit ARM64-specific libraries to link to derived from
// WindowsDriver.arm64.props
if self.cpu_architecture == CpuArchitecture::Arm64 {
println!("cargo::rustc-link-lib=static=arm64rt");
}

// Linker arguments derived from WindowsDriver.KernelMode.props in Ni(22H2) WDK
Comment thread
Alan632 marked this conversation as resolved.
println!("cargo::rustc-cdylib-link-arg=/DRIVER");
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB");
Expand All @@ -1160,19 +1244,6 @@ impl Config {
}
DriverConfig::Kmdf(_) => {
// Emit KMDF-specific libraries to link to
println!("cargo::rustc-link-lib=static=BufferOverflowFastFailK");
println!("cargo::rustc-link-lib=static=ntoskrnl");
println!("cargo::rustc-link-lib=static=hal");
println!("cargo::rustc-link-lib=static=wmilib");
println!("cargo::rustc-link-lib=static=WdfLdr");
println!("cargo::rustc-link-lib=static=WdfDriverEntry");

// Emit ARM64-specific libraries to link to derived from
// WindowsDriver.arm64.props
if self.cpu_architecture == CpuArchitecture::Arm64 {
println!("cargo::rustc-link-lib=static=arm64rt");
}

// Linker arguments derived from WindowsDriver.KernelMode.props in Ni(22H2) WDK
println!("cargo::rustc-cdylib-link-arg=/DRIVER");
Comment thread
Alan632 marked this conversation as resolved.
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB");
Expand All @@ -1187,16 +1258,9 @@ impl Config {
// might not run` since `rustc` has no support for `/KERNEL`
println!("cargo::rustc-cdylib-link-arg=/IGNORE:4257");
}
DriverConfig::Umdf(umdf_config) => {
// Emit UMDF-specific libraries to link to
if umdf_config.umdf_version_major >= 2 {
println!("cargo::rustc-link-lib=static=WdfDriverStubUm");
println!("cargo::rustc-link-lib=static=ntdll");
}

DriverConfig::Umdf(_) => {
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB:kernel32.lib");
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB:user32.lib");
println!("cargo::rustc-link-lib=static=OneCoreUAP");

// Linker arguments derived from WindowsDriver.UserMode.props in Ni(22H2) WDK
println!("cargo::rustc-cdylib-link-arg=/SUBSYSTEM:WINDOWS");
Expand Down Expand Up @@ -1495,6 +1559,123 @@ pub fn configure_wdk_binary_build() -> Result<(), ConfigError> {
Config::from_env_auto()?.configure_binary_build()
}

/// A native library to link into the generated bindings.
///
/// Rendered as a `#[link(...)]` attribute on an (empty) `extern` block and
/// emitted into the bindgen output via [`Builder::raw_line`]. An optional
/// `cfg` predicate gates the directive (e.g. by driver type).
#[derive(Debug, Clone)]
pub struct LinkDirective<'a> {
Comment thread
wmmc88 marked this conversation as resolved.
Outdated
/// `#[link(name = "...")]` — the library to link.
name: &'a str,
/// `kind = "..."` — one of `static`, `dylib`, `raw-dylib`, `framework`.
/// `#[link(name = "VhfKm", kind = "...")]`
kind: &'a str,
Comment thread
wmmc88 marked this conversation as resolved.
Outdated
/// `modifiers = "..."` values (e.g. `-bundle`, `+whole-archive`). Joined
/// with commas; an empty slice omits the `modifiers` key entirely.
/// `#[link(name = "VhfKm", kind = "static", modifiers = "...")]`
modifiers: &'a [&'a str],
/// ABI of the emitted `extern` block (e.g. `C`, `system`).
abi: &'a str,
/// Inner predicate of `#[cfg(...)]` gating the directive. `None` emits no
/// cfg.
cfg: Option<&'a str>,
Comment thread
wmmc88 marked this conversation as resolved.
Outdated
}

impl<'a> LinkDirective<'a> {
/// Creates a directive with the default `"C"` ABI, no modifiers, and no cfg
/// gate.
#[must_use]
pub const fn new(name: &'a str, kind: &'a str) -> Self {
Self {
name,
kind,
modifiers: &[],
abi: "C",
cfg: None,
}
}

/// Sets the linking modifiers (e.g. `["-bundle"]`). Each modifier must be
/// prefixed with `+` or `-`.
#[must_use]
pub const fn with_modifiers(mut self, modifiers: &'a [&'a str]) -> Self {
self.modifiers = modifiers;
self
}

/// Sets the ABI of the emitted `extern` block (defaults to `"C"`).
#[must_use]
pub const fn with_abi(mut self, abi: &'a str) -> Self {
self.abi = abi;
self
}

/// Gates the directive behind a `#[cfg(...)]` predicate.
#[must_use]
pub const fn with_cfg(mut self, cfg: &'a str) -> Self {
self.cfg = Some(cfg);
self
}

/// Renders this directive into a self-contained block of Rust source.
fn render(&self) -> String {
const VALID_KINDS: [&str; 4] = ["static", "dylib", "raw-dylib", "framework"];
Comment thread
wmmc88 marked this conversation as resolved.
Outdated

assert!(
!self.name.is_empty(),
"link directive `name` must not be empty"
);
assert!(
!self.abi.is_empty(),
"link directive `abi` must not be empty"
);
assert!(
VALID_KINDS.contains(&self.kind),
"unsupported link kind {:?}; expected one of {VALID_KINDS:?}",
self.kind
);
assert!(
self.modifiers
.iter()
.all(|m| m.starts_with('+') || m.starts_with('-')),
"each link modifier must start with `+` or `-`: {:?}",
self.modifiers
);

let modifiers = if self.modifiers.is_empty() {
String::new()
} else {
format!(r#", modifiers = "{}""#, self.modifiers.join(","))
};
let link_attr = format!(
r#"#[link(name = "{}", kind = "{}"{modifiers})]"#,
self.name, self.kind
);
let cfg_attr = self
.cfg
.map(|cfg| format!("#[cfg({cfg})]"))
.unwrap_or_default();

// Emitted as one block so the attributes always stay attached to the
// following `extern` block, regardless of where bindgen places raw
// lines.
format!(
"\n{cfg_attr}\n{link_attr}\nunsafe extern \"{}\" {{}}",
self.abi
)
Comment thread
wmmc88 marked this conversation as resolved.
Outdated
}
Comment thread
Alan632 marked this conversation as resolved.
}
Comment thread
Alan632 marked this conversation as resolved.

/// Appends each [`LinkDirective`] to `builder` so rustc links the corresponding
/// libraries when compiling the generated bindings.
#[must_use]
pub fn add_link_directives(builder: Builder, directives: &[LinkDirective]) -> Builder {
directives.iter().fold(builder, |builder, directive| {
builder.raw_line(directive.render())
})
}

/// This currently only exports the driver type, but may export more metadata in
/// the future. `EXPORTED_CFG_SETTINGS` is a mapping of cfg key to allowed cfg
/// values
Expand Down
14 changes: 10 additions & 4 deletions crates/wdk-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use wdk_build::{
IoError,
KmdfConfig,
UmdfConfig,
add_link_directives,
configure_wdk_library_build_and_then,
};

Expand Down Expand Up @@ -269,9 +270,13 @@ fn generate_base(out_path: &Path, config: &Config) -> Result<(), ConfigError> {
let header_contents = config.bindgen_header_contents([ApiSubset::Base])?;
trace!(header_contents = ?header_contents);

let bindgen_builder = bindgen::Builder::wdk_default(config)?
.with_codegen_config((CodegenConfig::TYPES | CodegenConfig::VARS).complement())
.header_contents(&format!("{outfile_name}-input.h"), &header_contents);
let bindgen_builder = {
let builder = bindgen::Builder::wdk_default(config)?
.with_codegen_config((CodegenConfig::TYPES | CodegenConfig::VARS).complement())
.header_contents(&format!("{outfile_name}-input.h"), &header_contents);

add_link_directives(builder, &config.link_directives(ApiSubset::Base))
Comment thread
wmmc88 marked this conversation as resolved.
Outdated
};
trace!(bindgen_builder = ?bindgen_builder);

let output_file_path = out_path.join(format!("{outfile_name}.rs"));
Expand Down Expand Up @@ -360,7 +365,8 @@ fn generate_hid(out_path: &Path, config: &Config) -> Result<(), ConfigError> {
for header_file in config.headers(ApiSubset::Hid)? {
builder = builder.allowlist_file(format!("(?i).*{header_file}.*"));
}
builder

add_link_directives(builder, &config.link_directives(ApiSubset::Hid))
};
trace!(bindgen_builder = ?bindgen_builder);

Expand Down
Loading