feat(windows-drivers): vendor wdk 0.5.1 + add ApiSubset::Iddcx (M1 spike)
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m8s
windows-drivers / driver-build (push) Failing after 43s
ci / rust (push) Successful in 1m31s
ci / web (push) Successful in 1m5s
ci / docs-site (push) Successful in 52s
apple / screenshots (push) Failing after 2m35s
windows-host / package (push) Successful in 5m23s
ci / bench (push) Successful in 4m48s
android / android (push) Successful in 10m1s
decky / build-publish (push) Successful in 26s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
deb / build-publish (push) Successful in 3m29s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m21s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m23s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m18s
docker / deploy-docs (push) Successful in 21s
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m8s
windows-drivers / driver-build (push) Failing after 43s
ci / rust (push) Successful in 1m31s
ci / web (push) Successful in 1m5s
ci / docs-site (push) Successful in 52s
apple / screenshots (push) Failing after 2m35s
windows-host / package (push) Successful in 5m23s
ci / bench (push) Successful in 4m48s
android / android (push) Successful in 10m1s
decky / build-publish (push) Successful in 26s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
deb / build-publish (push) Successful in 3m29s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m21s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m23s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m18s
docker / deploy-docs (push) Successful in 21s
Vendor the published, self-contained windows-drivers-rs 0.5.1 crates (wdk-build, wdk-sys) under vendor/ and add a first-class ApiSubset::Iddcx that bindgens iddcx/1.10/IddCx.h in an extra pass reusing bindgen::Builder::wdk_default (allowlist_file (?i).*iddcx.* — emits only IddCx items; WDF/DXGI types resolve to the shared base/wdf bindings, type-identity by construction). Mirrors the existing gpio/hid/spb subsets exactly: wdk-build gets the enum variant + iddcx_headers() (UMDF-only), wdk-sys gets generate_iddcx + the iddcx feature + pub mod iddcx. [patch.crates-io] redirects all wdk-sys/wdk-build (incl. wdk 0.4.1 transitive) to the patched copies. wdk-probe enables the iddcx feature. MAKE-OR-BREAK: does IddCx.h bindgen in wdk-sys config without a header conflict (issue #515) + does the generated module compile (type-identity)? CI answers it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// License: MIT OR Apache-2.0
|
||||
|
||||
use std::{borrow::Borrow, fmt};
|
||||
|
||||
use bindgen::{
|
||||
Builder,
|
||||
callbacks::{ItemInfo, ItemKind, ParseCallbacks},
|
||||
};
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{Config, ConfigError, DriverConfig, find_top_level_cargo_manifest};
|
||||
|
||||
/// An extension trait that provides a way to create a [`bindgen::Builder`]
|
||||
/// configured for generating bindings to the wdk
|
||||
pub trait BuilderExt {
|
||||
/// Returns a `bindgen::Builder` with the default configuration for
|
||||
/// generation of bindings to the WDK
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Implementation may return `wdk_build::ConfigError` if it fails to create
|
||||
/// a builder
|
||||
fn wdk_default(config: impl Borrow<Config> + fmt::Debug) -> Result<Builder, ConfigError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WdkCallbacks {
|
||||
wdf_function_table_symbol_name: Option<String>,
|
||||
}
|
||||
|
||||
struct BindgenRustEditionWrapper(bindgen::RustEdition);
|
||||
|
||||
impl TryFrom<cargo_metadata::Edition> for BindgenRustEditionWrapper {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn try_from(edition: cargo_metadata::Edition) -> Result<Self, Self::Error> {
|
||||
match edition {
|
||||
cargo_metadata::Edition::E2015 => Err(ConfigError::UnsupportedRustEdition {
|
||||
edition: "2015".to_string(),
|
||||
}),
|
||||
cargo_metadata::Edition::E2018 => Ok(Self(bindgen::RustEdition::Edition2018)),
|
||||
cargo_metadata::Edition::E2021 => Ok(Self(bindgen::RustEdition::Edition2021)),
|
||||
cargo_metadata::Edition::E2024 => Ok(Self(bindgen::RustEdition::Edition2024)),
|
||||
cargo_metadata::Edition::_E2027 => Err(ConfigError::UnsupportedRustEdition {
|
||||
edition: "2027".to_string(),
|
||||
}),
|
||||
cargo_metadata::Edition::_E2030 => Err(ConfigError::UnsupportedRustEdition {
|
||||
edition: "2030".to_string(),
|
||||
}),
|
||||
_ => Err(ConfigError::UnsupportedRustEdition {
|
||||
edition: "unknown".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuilderExt for Builder {
|
||||
/// Returns a `bindgen::Builder` with the default configuration for
|
||||
/// generation of bindings to the WDK
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `wdk_build::ConfigError` if any of the resolved include or
|
||||
/// library paths do not exist
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn wdk_default(config: impl Borrow<Config> + fmt::Debug) -> Result<Self, ConfigError> {
|
||||
let config = config.borrow();
|
||||
|
||||
let mut builder = Self::default()
|
||||
.use_core() // Can't use std for kernel code
|
||||
.derive_default(true) // allows for default initializing structs
|
||||
// CStr types are safer and easier to work with when interacting with string constants
|
||||
// from C
|
||||
.generate_cstr(true)
|
||||
// Building in eWDK can pollute system search path when clang-sys tries to detect
|
||||
// c_search_paths
|
||||
.detect_include_paths(false)
|
||||
.clang_args(config.include_paths()?.map(|include_path| {
|
||||
format!(
|
||||
"--include-directory={}",
|
||||
include_path
|
||||
.to_str()
|
||||
.expect("Non Unicode paths are not supported")
|
||||
)
|
||||
}))
|
||||
.clang_args(
|
||||
config
|
||||
.preprocessor_definitions()
|
||||
.map(|(key, value)| {
|
||||
format!(
|
||||
"--define-macro={key}{}",
|
||||
value.map(|v| format!("={v}")).unwrap_or_default()
|
||||
)
|
||||
})
|
||||
.chain(Config::wdk_bindgen_compiler_flags()),
|
||||
)
|
||||
.blocklist_item("ExAllocatePoolWithTag") // Deprecated
|
||||
.blocklist_item("ExAllocatePoolWithQuotaTag") // Deprecated
|
||||
.blocklist_item("ExAllocatePoolWithTagPriority") // Deprecated
|
||||
.blocklist_item("ExAllocatePool") // Deprecated
|
||||
.blocklist_item("USBD_CalculateUsbBandwidth") // Deprecated
|
||||
.blocklist_item("USBD_CreateConfigurationRequest") // Deprecated
|
||||
.blocklist_item("USBD_Debug_LogEntry") // Deprecated
|
||||
.blocklist_item("USBD_GetUSBDIVersion") // Deprecated
|
||||
.blocklist_item("USBD_ParseConfigurationDescriptor") // Deprecated
|
||||
.blocklist_item("USBD_QueryBusTime") // Deprecated
|
||||
.blocklist_item("USBD_RegisterHcFilter") // Deprecated
|
||||
.blocklist_item("IOCTL_USB_DIAG_IGNORE_HUBS_OFF") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_DIAG_IGNORE_HUBS_ON") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_DIAGNOSTIC_MODE_OFF") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_DIAGNOSTIC_MODE_ON") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_GET_HUB_CAPABILITIES") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_HCD_DISABLE_PORT") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_HCD_ENABLE_PORT") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_HCD_GET_STATS_1") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_HCD_GET_STATS_2") // Deprecated/Internal-Use-Only
|
||||
.blocklist_item("IOCTL_USB_RESET_HUB") // Deprecated/Internal-Use-Only
|
||||
.opaque_type("_KGDTENTRY64") // No definition in WDK
|
||||
.opaque_type("_KIDTENTRY64") // No definition in WDK
|
||||
// FIXME: bitfield generated with non-1byte alignment in _MCG_CAP
|
||||
.blocklist_item(".*MCG_CAP(?:__bindgen.*)?")
|
||||
.blocklist_item(".*WHEA_XPF_MCA_SECTION")
|
||||
.blocklist_item(".*WHEA_ARM_BUS_ERROR(?:__bindgen.*)?")
|
||||
.blocklist_item(".*WHEA_ARM_PROCESSOR_ERROR")
|
||||
.blocklist_item(".*WHEA_ARM_CACHE_ERROR")
|
||||
// FIXME: bindgen unable to generate for anonymous structs
|
||||
// https://github.com/rust-lang/rust-bindgen/issues/3177
|
||||
.blocklist_item(".*ADDRESS0_OWNERSHIP_ACQUIRE")
|
||||
.blocklist_item(".*USBDEVICE_ABORTIO")
|
||||
.blocklist_item(".*USBDEVICE_STARTIO")
|
||||
.blocklist_item(".*USBDEVICE_TREE_PURGEIO")
|
||||
// FIXME: arrays with more than 32 entries currently fail to generate a `Default`` impl: https://github.com/rust-lang/rust-bindgen/issues/2803
|
||||
.no_default(".*tagMONITORINFOEXA")
|
||||
.must_use_type("NTSTATUS")
|
||||
.must_use_type("HRESULT")
|
||||
// Defaults enums to generate as a set of constants contained in a module (default value
|
||||
// is EnumVariation::Consts which generates enums as global constants)
|
||||
.default_enum_style(bindgen::EnumVariation::ModuleConsts)
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.parse_callbacks(Box::new(WdkCallbacks::new(config)))
|
||||
.formatter(bindgen::Formatter::Prettyplease)
|
||||
.rust_target(get_rust_target()?)
|
||||
.rust_edition(get_rust_edition()?);
|
||||
|
||||
// The `_USBPM_CLIENT_CONFIG_EXTRA_INFO` struct only has members when
|
||||
// _KERNEL_MODE flag is defined. We need to mark this type as opaque to avoid
|
||||
// generating an empty struct, since they are not currently supported by
|
||||
// bindgen: https://github.com/rust-lang/rust-bindgen/issues/1683
|
||||
if let DriverConfig::Umdf(_) = config.driver_config {
|
||||
builder = builder.opaque_type("_USBPM_CLIENT_CONFIG_EXTRA_INFO");
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseCallbacks for WdkCallbacks {
|
||||
fn generated_name_override(&self, item_info: ItemInfo) -> Option<String> {
|
||||
// Override the generated name for the WDF function table symbol, since bindgen is unable to currently translate the #define automatically: https://github.com/rust-lang/rust-bindgen/issues/2544
|
||||
if let Some(wdf_function_table_symbol_name) = &self.wdf_function_table_symbol_name {
|
||||
if let ItemInfo {
|
||||
name: item_name,
|
||||
kind: ItemKind::Var,
|
||||
..
|
||||
} = item_info
|
||||
{
|
||||
if item_name == wdf_function_table_symbol_name {
|
||||
return Some("WdfFunctions".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl WdkCallbacks {
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn new(config: &Config) -> Self {
|
||||
Self {
|
||||
wdf_function_table_symbol_name: config.compute_wdffunctions_symbol_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves the Rust version as a `bindgen::RustTarget` for the current build
|
||||
// configuration.
|
||||
//
|
||||
// If the `nightly` feature is enabled and the current toolchain is `nightly`,
|
||||
// returns a value allowing `bindgen` to generate code with supported `nightly`
|
||||
// features. Otherwise, queries the MSRV from the `CARGO_PKG_RUST_VERSION`
|
||||
// environment variable and uses it to create a `bindgen::RustTarget::stable`
|
||||
// value.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Returns `ConfigError::MsrvNotSupportedByBindgen` if the MSRV is not supported
|
||||
// by bindgen, or `ConfigError::SemverError` if the MSRV cannot be parsed as a
|
||||
// semver version.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn get_rust_target() -> Result<bindgen::RustTarget, ConfigError> {
|
||||
let nightly_feature = cfg!(feature = "nightly");
|
||||
let nightly_toolchain = rustversion::cfg!(nightly);
|
||||
|
||||
match (nightly_feature, nightly_toolchain) {
|
||||
(true, true) => Ok(bindgen::RustTarget::nightly()),
|
||||
(false, false) => get_stable_rust_target(),
|
||||
(true, false) => {
|
||||
tracing::warn!(
|
||||
"A non-nightly toolchain has been detected. Nightly bindgen features are only \
|
||||
enabled with both nightly feature enablement and nightly toolchain use. "
|
||||
);
|
||||
get_stable_rust_target()
|
||||
}
|
||||
(false, true) => {
|
||||
tracing::warn!(
|
||||
"The nightly feature for wdk-build is disabled. Nightly bindgen features are only \
|
||||
enabled with both nightly feature enablement and nightly toolchain use. "
|
||||
);
|
||||
get_stable_rust_target()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves the stable Rust target for the current build configuration.
|
||||
// Queries the MSRV from the `CARGO_PKG_RUST_VERSION` environment variable and
|
||||
// uses it to create a `bindgen::RustTarget::stable` value.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn get_stable_rust_target() -> Result<bindgen::RustTarget, ConfigError> {
|
||||
let package_msrv = semver::Version::parse(env!("CARGO_PKG_RUST_VERSION"))
|
||||
.map_err(|e| ConfigError::RustVersionParseError { error_source: e })?;
|
||||
|
||||
let bindgen_msrv = bindgen::RustTarget::stable(package_msrv.minor, package_msrv.patch)
|
||||
.map_err(|e| ConfigError::MsrvNotSupportedByBindgen {
|
||||
msrv: package_msrv.to_string(),
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
Ok(bindgen_msrv)
|
||||
}
|
||||
|
||||
// Retrieves the Rust edition from `cargo metadata` and returns the appropriate
|
||||
// `bindgen::RustEdition` value.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Returns `ConfigError::CargoMetadataPackageNotFound` if the `wdk-build`
|
||||
// package is not found, or `ConfigError::UnsupportedRustEdition` if the edition
|
||||
// is not supported.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn get_rust_edition() -> Result<bindgen::RustEdition, ConfigError> {
|
||||
const WDK_BUILD_PACKAGE_NAME: &str = "wdk-build";
|
||||
// Run `cargo_metadata` in the same working directory as the top level manifest
|
||||
// in order to respect `config.toml` overrides
|
||||
let top_level_cargo_manifest_path = find_top_level_cargo_manifest();
|
||||
debug!(
|
||||
"Top level Cargo manifest path: {:?}",
|
||||
top_level_cargo_manifest_path
|
||||
);
|
||||
let cwd = top_level_cargo_manifest_path
|
||||
.parent()
|
||||
.expect("Cargo manifest should have a valid parent directory");
|
||||
let wdk_sys_cargo_metadata = MetadataCommand::new().current_dir(cwd).exec()?;
|
||||
|
||||
let wdk_sys_package_metadata = wdk_sys_cargo_metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|package| package.name == WDK_BUILD_PACKAGE_NAME)
|
||||
.ok_or_else(|| ConfigError::WdkBuildPackageNotFoundInCargoMetadata)?;
|
||||
|
||||
let rust_edition: BindgenRustEditionWrapper = wdk_sys_package_metadata.edition.try_into()?;
|
||||
Ok(rust_edition.0)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+2232
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// License: MIT OR Apache-2.0
|
||||
|
||||
use serde::ser::{self};
|
||||
use thiserror::Error;
|
||||
|
||||
/// A specialized [`Result`] type for [`metadata`](crate::metadata)
|
||||
/// serialization and deserialization operations.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// This type represents all possible errors that can occur when serializing
|
||||
/// or deserializing [`metadata::Wdk`](crate::metadata::Wdk).
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// catch-all error emitted during serialization, when a more specific
|
||||
/// error type is not available. This type of error is commonly
|
||||
/// generated from [`serde`]'s `derive` feature's generated `Serialize`
|
||||
/// impls.
|
||||
#[error("custom serialization error: {message}")]
|
||||
CustomSerialization {
|
||||
/// Message describing the error
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// error emitted when an empty key name is encountered during
|
||||
/// serialization. Serialization of values always requires a non-empty
|
||||
/// key name
|
||||
#[error("empty key name encountered during serialization of value: {value_being_serialized}")]
|
||||
EmptySerializationKeyName {
|
||||
/// Value being serialized
|
||||
value_being_serialized: String,
|
||||
},
|
||||
|
||||
/// error emitted when duplicate key names are found during
|
||||
/// serialization. Serializing into a
|
||||
/// [`metadata::Map`](crate::metadata::Map) requires unique key names
|
||||
#[error(
|
||||
"duplicate keys found during serialization:\nkey: {key}\nvalue 1: {value_1}\nvalue 2: \
|
||||
{value_2}"
|
||||
)]
|
||||
DuplicateSerializationKeys {
|
||||
/// Key name
|
||||
key: String,
|
||||
/// One of the conflicting values
|
||||
value_1: String,
|
||||
/// One of the conflicting values
|
||||
value_2: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T: std::fmt::Display>(msg: T) -> Self {
|
||||
Self::CustomSerialization {
|
||||
message: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// License: MIT OR Apache-2.0
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, btree_map, hash_map},
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
|
||||
/// Trait for map-like type that is returned by
|
||||
/// [`metadata::to_map`](crate::metadata::to_map)
|
||||
/// and [`metadata::to_map_with_prefix`](crate::metadata::to_map_with_prefix).
|
||||
pub trait Map<K, V>: Default {
|
||||
/// Creates a new, empty map
|
||||
#[must_use]
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Inserts a new key-value pair into the map, or calls a function/closure
|
||||
/// if the key already exists.
|
||||
///
|
||||
/// The function/closure is called with the existing key, the existing
|
||||
/// value, and the new value it tried to insert. The closure can decide
|
||||
/// whether the function will return an `Err` or if it will still return a
|
||||
/// `Ok` despite not inserting the value.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if the key already exists and `f` returns
|
||||
/// an `Err` value
|
||||
fn insert_or_else<F, E>(&mut self, key: K, value: V, f: F) -> Result<(), E>
|
||||
where
|
||||
F: FnMut(&K, &V, V) -> Result<(), E>;
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V, S: BuildHasher + Default> Map<K, V> for HashMap<K, V, S> {
|
||||
fn insert_or_else<F, E>(&mut self, key: K, value: V, mut f: F) -> Result<(), E>
|
||||
where
|
||||
F: FnMut(&K, &V, V) -> Result<(), E>,
|
||||
{
|
||||
match self.entry(key) {
|
||||
hash_map::Entry::Occupied(entry) => f(entry.key(), entry.get(), value),
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(value);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ord, V> Map<K, V> for BTreeMap<K, V> {
|
||||
fn insert_or_else<F, E>(&mut self, key: K, value: V, mut f: F) -> Result<(), E>
|
||||
where
|
||||
F: FnMut(&K, &V, V) -> Result<(), E>,
|
||||
{
|
||||
match self.entry(key) {
|
||||
btree_map::Entry::Occupied(entry) => f(entry.key(), entry.get(), value),
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(value);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// License: MIT OR Apache-2.0
|
||||
|
||||
//! Parsing and serializing metadata about WDK projects
|
||||
//!
|
||||
//! This module provides a [`Wdk`] struct that represents the cargo metadata
|
||||
//! specified in the `metadata.wdk` section any `Cargo.toml`. This corresponds
|
||||
//! with the settings in the `Driver Settings` property pages for WDK projects
|
||||
//! in Visual Studio. This module also also provides [`serde`]-compatible
|
||||
//! serialization and deserialization for the metadata.
|
||||
|
||||
pub use error::{Error, Result};
|
||||
pub use map::Map;
|
||||
pub use ser::{Serializer, to_map, to_map_with_prefix};
|
||||
|
||||
pub(crate) mod ser;
|
||||
|
||||
mod error;
|
||||
mod map;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use camino::Utf8PathBuf;
|
||||
use cargo_metadata::Metadata;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::DriverConfig;
|
||||
|
||||
/// Metadata specified in the `metadata.wdk` section of the `Cargo.toml`
|
||||
/// of a crate that depends on the WDK, or in a cargo workspace.
|
||||
///
|
||||
/// This corresponds with the settings in the `Driver Settings` property pages
|
||||
/// for WDK projects in Visual Studio
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all(serialize = "SCREAMING_SNAKE_CASE", deserialize = "kebab-case")
|
||||
)]
|
||||
pub struct Wdk {
|
||||
/// Metadata corresponding to the `Driver Model` property page in the WDK
|
||||
pub driver_model: DriverConfig,
|
||||
}
|
||||
|
||||
/// Errors that could result from trying to construct a
|
||||
/// [`metadata::Wdk`](crate::metadata::Wdk) from information parsed by `cargo
|
||||
/// metadata`
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TryFromCargoMetadataError {
|
||||
/// Error returned when no WDK configuration metadata is detected in the
|
||||
/// dependency graph
|
||||
#[error(
|
||||
"no WDK configuration metadata is detected in the dependency graph. This could happen \
|
||||
when building WDR itself, or building library crates that depend on the WDK but defer \
|
||||
WDK configuration to their consumers"
|
||||
)]
|
||||
NoWdkConfigurationsDetected,
|
||||
|
||||
/// Error returned when multiple configurations of the WDK are detected
|
||||
/// across the dependency graph
|
||||
#[error(
|
||||
"multiple configurations of the WDK are detected across the dependency graph, but only \
|
||||
one configuration is allowed: {wdk_metadata_configurations:#?}"
|
||||
)]
|
||||
MultipleWdkConfigurationsDetected {
|
||||
/// [`HashSet`] of unique [`metadata::Wdk`](crate::metadata::Wdk)
|
||||
/// derived from detected WDK metadata
|
||||
wdk_metadata_configurations: HashSet<Wdk>,
|
||||
},
|
||||
|
||||
/// Error returned when [`crate::metadata::Wdk`] fails to be deserialized
|
||||
/// from [`cargo_metadata::Metadata`] output
|
||||
#[error("failed to deserialize metadata::Wdk from {metadata_source}")]
|
||||
WdkMetadataDeserialization {
|
||||
/// `String` that describes what part of
|
||||
/// `cargo_metadata::Metadata` was used as the source for
|
||||
/// deserialization
|
||||
metadata_source: String,
|
||||
/// [`serde_json::Error`] that caused the deserialization to fail
|
||||
#[source]
|
||||
error_source: serde_json::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<&Metadata> for Wdk {
|
||||
type Error = TryFromCargoMetadataError;
|
||||
|
||||
fn try_from(metadata: &Metadata) -> std::result::Result<Self, Self::Error> {
|
||||
let wdk_metadata_configurations = {
|
||||
// Parse WDK metadata from workspace and all packages
|
||||
let mut configs = parse_packages_wdk_metadata(&metadata.packages)?;
|
||||
if let Some(workspace_metadata) =
|
||||
parse_workspace_wdk_metadata(&metadata.workspace_metadata)?
|
||||
{
|
||||
configs.insert(workspace_metadata);
|
||||
}
|
||||
configs
|
||||
};
|
||||
|
||||
// Ensure that only one configuration of WDK is allowed per dependency graph
|
||||
match wdk_metadata_configurations.len() {
|
||||
1 => Ok(wdk_metadata_configurations.into_iter().next().expect(
|
||||
"wdk_metadata_configurations should have exactly one element because of the \
|
||||
.len() check above",
|
||||
)),
|
||||
|
||||
0 => Err(TryFromCargoMetadataError::NoWdkConfigurationsDetected),
|
||||
|
||||
_ => Err(
|
||||
TryFromCargoMetadataError::MultipleWdkConfigurationsDetected {
|
||||
wdk_metadata_configurations,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_packages_wdk_metadata(
|
||||
packages: &[cargo_metadata::Package],
|
||||
) -> std::result::Result<HashSet<Wdk>, TryFromCargoMetadataError> {
|
||||
let wdk_metadata_configurations = packages
|
||||
.iter()
|
||||
.filter_map(|package| match &package.metadata["wdk"] {
|
||||
serde_json::Value::Null => None,
|
||||
// When wdk section is empty, treat it as if it wasn't there. This is to allow for using
|
||||
// empty wdk metadata sections to mark the package as a driver (ex. for detection in
|
||||
// `package_driver_flow_condition_script`)
|
||||
serde_json::Value::Object(map) if map.is_empty() => None,
|
||||
wdk_metadata => Some(Wdk::deserialize(wdk_metadata).map_err(|err| {
|
||||
TryFromCargoMetadataError::WdkMetadataDeserialization {
|
||||
metadata_source: format!(
|
||||
"{} for {} package",
|
||||
stringify!(package.metadata["wdk"]),
|
||||
package.name
|
||||
),
|
||||
error_source: err,
|
||||
}
|
||||
})),
|
||||
})
|
||||
.collect::<std::result::Result<HashSet<_>, _>>()?;
|
||||
Ok(wdk_metadata_configurations)
|
||||
}
|
||||
|
||||
fn parse_workspace_wdk_metadata(
|
||||
workspace_metadata: &serde_json::Value,
|
||||
) -> std::result::Result<Option<Wdk>, TryFromCargoMetadataError> {
|
||||
Ok(match &workspace_metadata["wdk"] {
|
||||
serde_json::Value::Null => None,
|
||||
wdk_metadata => Some(Wdk::deserialize(wdk_metadata).map_err(|err| {
|
||||
TryFromCargoMetadataError::WdkMetadataDeserialization {
|
||||
metadata_source: stringify!(workspace_metadata["wdk"]).to_string(),
|
||||
error_source: err,
|
||||
}
|
||||
})?),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn iter_manifest_paths(metadata: Metadata) -> impl IntoIterator<Item = Utf8PathBuf> {
|
||||
let mut cargo_manifest_paths = HashSet::new();
|
||||
|
||||
// Add all package manifest paths
|
||||
for package in metadata.packages {
|
||||
cargo_manifest_paths.insert(package.manifest_path);
|
||||
}
|
||||
|
||||
// Add workspace manifest path
|
||||
let workspace_manifest_path: Utf8PathBuf = {
|
||||
let mut path = metadata.workspace_root;
|
||||
path.push("Cargo.toml");
|
||||
path
|
||||
};
|
||||
cargo_manifest_paths.insert(workspace_manifest_path);
|
||||
|
||||
cargo_manifest_paths
|
||||
}
|
||||
@@ -0,0 +1,746 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// License: MIT OR Apache-2.0
|
||||
|
||||
use serde::{
|
||||
Serialize,
|
||||
ser::{self, Impossible},
|
||||
};
|
||||
|
||||
use super::{
|
||||
error::{Error, Result},
|
||||
map::Map,
|
||||
};
|
||||
|
||||
/// delimiter used to separate the names of the different nodes encoded into a
|
||||
/// key name. Since `-` is not valid in Rust identifiers, it is used
|
||||
/// as a separator between different node names.
|
||||
pub const KEY_NAME_SEPARATOR: char = '-';
|
||||
|
||||
/// Serialize a value into a [`Map`] where the keys represent a
|
||||
/// `KEY_NAME_SEPARATOR`-separated list of field names.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the type being serialized:
|
||||
/// * results in duplicate key names
|
||||
/// * results in an empty key name
|
||||
/// * otherwise fails to be parsed and correctly serialized into a [`Map`]
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use std::collections::BTreeMap;
|
||||
///
|
||||
/// use wdk_build::{
|
||||
/// DriverConfig,
|
||||
/// KmdfConfig,
|
||||
/// metadata::{self, to_map},
|
||||
/// };
|
||||
///
|
||||
/// let wdk_metadata = metadata::Wdk {
|
||||
/// driver_model: DriverConfig::Kmdf(KmdfConfig {
|
||||
/// kmdf_version_major: 1,
|
||||
/// target_kmdf_version_minor: 23,
|
||||
/// minimum_kmdf_version_minor: None,
|
||||
/// }),
|
||||
/// };
|
||||
///
|
||||
/// let output = to_map::<BTreeMap<_, _>>(&wdk_metadata).unwrap();
|
||||
///
|
||||
/// assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "KMDF");
|
||||
/// assert_eq!(output["DRIVER_MODEL-KMDF_VERSION_MAJOR"], "1");
|
||||
/// assert_eq!(output["DRIVER_MODEL-TARGET_KMDF_VERSION_MINOR"], "23");
|
||||
///
|
||||
/// // `None` values are not serialized
|
||||
/// assert_eq!(output.get("DRIVER_MODEL-MINIMUM_KMDF_VERSION_MINOR"), None);
|
||||
/// ```
|
||||
pub fn to_map<M>(value: &impl Serialize) -> Result<M>
|
||||
where
|
||||
M: Map<String, String>,
|
||||
{
|
||||
let mut serialization_buffer: Vec<(String, String)> = Vec::new();
|
||||
value.serialize(&mut Serializer::new(&mut serialization_buffer))?;
|
||||
convert_serialized_output_to_map(serialization_buffer)
|
||||
}
|
||||
|
||||
/// Serialize a value into a [`Map`] where the keys represent a
|
||||
/// `KEY_NAME_SEPARATOR`-separated list of field names prepended with a
|
||||
/// prefix.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the type being serialized:
|
||||
/// * results in duplicate key names
|
||||
/// * results in an empty key name
|
||||
/// * otherwise fails to be parsed and correctly serialized into a [`Map`]
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use std::collections::BTreeMap;
|
||||
///
|
||||
/// use wdk_build::{
|
||||
/// DriverConfig,
|
||||
/// KmdfConfig,
|
||||
/// metadata::{self, to_map_with_prefix},
|
||||
/// };
|
||||
///
|
||||
/// let wdk_metadata = metadata::Wdk {
|
||||
/// driver_model: DriverConfig::Kmdf(KmdfConfig {
|
||||
/// kmdf_version_major: 1,
|
||||
/// target_kmdf_version_minor: 33,
|
||||
/// minimum_kmdf_version_minor: Some(31),
|
||||
/// }),
|
||||
/// };
|
||||
///
|
||||
/// let output = to_map_with_prefix::<BTreeMap<_, _>>("WDK_BUILD_METADATA", &wdk_metadata).unwrap();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// output["WDK_BUILD_METADATA-DRIVER_MODEL-DRIVER_TYPE"],
|
||||
/// "KMDF"
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// output["WDK_BUILD_METADATA-DRIVER_MODEL-KMDF_VERSION_MAJOR"],
|
||||
/// "1"
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// output["WDK_BUILD_METADATA-DRIVER_MODEL-TARGET_KMDF_VERSION_MINOR"],
|
||||
/// "33"
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// output["WDK_BUILD_METADATA-DRIVER_MODEL-MINIMUM_KMDF_VERSION_MINOR"],
|
||||
/// "31"
|
||||
/// );
|
||||
/// ```
|
||||
pub fn to_map_with_prefix<M>(prefix: impl Into<String>, value: &impl Serialize) -> Result<M>
|
||||
where
|
||||
M: Map<String, String>,
|
||||
{
|
||||
let mut serialization_buffer: Vec<(String, String)> = Vec::new();
|
||||
value.serialize(&mut Serializer::with_prefix(
|
||||
prefix.into(),
|
||||
&mut serialization_buffer,
|
||||
))?;
|
||||
convert_serialized_output_to_map(serialization_buffer)
|
||||
}
|
||||
|
||||
fn convert_serialized_output_to_map<M>(serialization_buffer: Vec<(String, String)>) -> Result<M>
|
||||
where
|
||||
M: Map<String, String>,
|
||||
{
|
||||
let mut output_map = M::new();
|
||||
for (key, value) in serialization_buffer {
|
||||
output_map.insert_or_else(key, value, |key, existing_value, new_value| {
|
||||
Err(Error::DuplicateSerializationKeys {
|
||||
key: key.clone(),
|
||||
value_1: existing_value.clone(),
|
||||
value_2: new_value,
|
||||
})
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(output_map)
|
||||
}
|
||||
|
||||
/// [`serde`] serializer that serializes values into a [`Vec`] of key-value
|
||||
/// pairs.
|
||||
///
|
||||
/// This serializer is useful when you want to have more granular control of the
|
||||
/// output of the serializer. Most usecases should already be covered by the
|
||||
/// [`to_map`] and [`to_map_with_prefix`] functions.
|
||||
pub struct Serializer<'a> {
|
||||
root_key_name: Option<String>,
|
||||
dst: &'a mut Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl<'a> ser::Serializer for &'a mut Serializer<'a> {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
type SerializeMap = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeSeq = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeStruct = Self;
|
||||
type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
|
||||
|
||||
unsupported_serde_serialize_method! {
|
||||
// simple types
|
||||
bytes newtype_struct newtype_variant unit_struct unit_variant
|
||||
// complex types (returns SerializeXYZ types)
|
||||
map seq struct_variant tuple tuple_struct tuple_variant
|
||||
}
|
||||
|
||||
fn serialize_str(self, value: &str) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_bool(self, value: bool) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_char(self, value: char) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i8(self, value: i8) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i16(self, value: i16) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i32(self, value: i32) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i64(self, value: i64) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_f32(self, value: f32) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_f64(self, value: f64) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok> {
|
||||
self.serialize_unit()
|
||||
}
|
||||
|
||||
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u8(self, value: u8) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u16(self, value: u16) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u32(self, value: u32) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u64(self, value: u64) -> Result<Self::Ok> {
|
||||
self.dst.push((
|
||||
self.root_key_name
|
||||
.clone()
|
||||
.ok_or_else(|| Error::EmptySerializationKeyName {
|
||||
value_being_serialized: value.to_string(),
|
||||
})?,
|
||||
value.to_string(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ser::SerializeStruct for &'a mut Serializer<'a> {
|
||||
type Error = Error;
|
||||
type Ok = ();
|
||||
|
||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<Self::Ok>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(&mut Serializer::with_prefix(
|
||||
self.root_key_name.as_ref().map_or_else(
|
||||
|| key.to_string(),
|
||||
|root_key_name| format!("{root_key_name}{KEY_NAME_SEPARATOR}{key}"),
|
||||
),
|
||||
self.dst,
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serializer<'a> {
|
||||
/// Create a new instance of the `Serializer` struct
|
||||
pub const fn new(dst: &'a mut Vec<(String, String)>) -> Self {
|
||||
Self {
|
||||
root_key_name: None,
|
||||
dst,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance of the `Serializer` struct with a prefix used as
|
||||
/// the root for all keys
|
||||
pub const fn with_prefix(prefix: String, dst: &'a mut Vec<(String, String)>) -> Self {
|
||||
Self {
|
||||
root_key_name: Some(prefix),
|
||||
dst,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Helper macro when implementing the `Serializer` part of a new data
|
||||
/// format for Serde.
|
||||
///
|
||||
/// Generates [`serde::ser::Serializer`] trait methods for serde data model
|
||||
/// types that aren't supported by this serializer. This generates a
|
||||
/// method that calls [`unimplemented!`].
|
||||
macro_rules! unsupported_serde_serialize_method {
|
||||
($($method_type:ident)*) => {
|
||||
$(unsupported_serde_serialize_method_helper! {$method_type})*
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
pub(crate) use unsupported_serde_serialize_method;
|
||||
|
||||
#[doc(hidden)]
|
||||
macro_rules! unsupported_serde_serialize_method_helper {
|
||||
// methods for simple types (returns Ok)
|
||||
(bytes) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_bytes(_v: &[u8]) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(newtype_struct) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_newtype_struct<T>(_name: &'static str, _value: &T) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(newtype_variant) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_newtype_variant<T>(_name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(none) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_none() -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(some) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_some<T>(_value: &T) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(str) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_str(_v: &str) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(unit) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_unit() -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(unit_struct) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_unit_struct(_name: &'static str) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(unit_variant) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_unit_variant(_name: &'static str, _variant_index: u32, _variant: &'static str) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
// methods for complex types (returns SerializeXYZ types)
|
||||
(map) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_map(_len: Option<usize>) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeMap,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(struct) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_struct(_name: &'static str, _len: usize) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeStruct,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(struct_variant) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_struct_variant(_name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeStructVariant,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(seq) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_seq(_len: Option<usize>) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeSeq,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(tuple) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_tuple(_len: usize) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeTuple,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(tuple_struct) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_tuple_struct(_name: &'static str, _len: usize) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeTupleStruct,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
(tuple_variant) => {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
serialize_tuple_variant(_name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::SerializeTupleVariant,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
};
|
||||
// every other method has no extra arguments and is for simple types
|
||||
($method_type:ident) => {
|
||||
paste::paste! {
|
||||
unsupported_serde_serialize_method_definition! {
|
||||
[<serialize_ $method_type>](_v: $method_type) -> std::result::Result<
|
||||
<Self as serde::ser::Serializer>::Ok,
|
||||
<Self as serde::ser::Serializer>::Error,
|
||||
>
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
pub(crate) use unsupported_serde_serialize_method_helper;
|
||||
|
||||
#[doc(hidden)]
|
||||
macro_rules! unsupported_serde_serialize_method_definition {
|
||||
// methods with generic argument
|
||||
($func:ident <$generic_arg:ident> ($($arg:ident : $ty:ty),*) -> std::result::Result<$ok:ty, $err:ty$(,)?>) => {
|
||||
#[inline]
|
||||
fn $func <$generic_arg> (self, $($arg: $ty,)*) -> std::result::Result<$ok, $err>
|
||||
where
|
||||
$generic_arg: ?Sized + Serialize {
|
||||
unimplemented!(
|
||||
"{} is not implemented for {} since it is currently not needed to serialize the metadata::Wdk struct",
|
||||
stringify!($func),
|
||||
std::any::type_name::<Self>(),
|
||||
)
|
||||
}
|
||||
};
|
||||
// methods without generic argument
|
||||
($func:ident ($($arg:ident : $ty:ty),*) -> std::result::Result<$ok:ty, $err:ty$(,)?>) => {
|
||||
#[inline]
|
||||
fn $func (self, $($arg: $ty,)*) -> std::result::Result<$ok, $err> {
|
||||
unimplemented!(
|
||||
"{} is not implemented for {} since it is currently not needed to serialize the metadata::Wdk struct",
|
||||
stringify!($func),
|
||||
std::any::type_name::<Self>(),
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
pub(crate) use unsupported_serde_serialize_method_definition;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
vec,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::{DriverConfig, KmdfConfig, UmdfConfig, metadata};
|
||||
|
||||
#[test]
|
||||
fn test_kmdf() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Kmdf(KmdfConfig {
|
||||
kmdf_version_major: 1,
|
||||
target_kmdf_version_minor: 23,
|
||||
minimum_kmdf_version_minor: Some(21),
|
||||
}),
|
||||
};
|
||||
|
||||
let output = to_map::<BTreeMap<_, _>>(&wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "KMDF");
|
||||
assert_eq!(output["DRIVER_MODEL-KMDF_VERSION_MAJOR"], "1");
|
||||
assert_eq!(output["DRIVER_MODEL-TARGET_KMDF_VERSION_MINOR"], "23");
|
||||
assert_eq!(output["DRIVER_MODEL-MINIMUM_KMDF_VERSION_MINOR"], "21");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kmdf_no_minimum() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Kmdf(KmdfConfig {
|
||||
kmdf_version_major: 1,
|
||||
target_kmdf_version_minor: 23,
|
||||
minimum_kmdf_version_minor: None,
|
||||
}),
|
||||
};
|
||||
|
||||
let output = to_map::<BTreeMap<_, _>>(&wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "KMDF");
|
||||
assert_eq!(output["DRIVER_MODEL-KMDF_VERSION_MAJOR"], "1");
|
||||
assert_eq!(output["DRIVER_MODEL-TARGET_KMDF_VERSION_MINOR"], "23");
|
||||
|
||||
// `None` values are not serialized
|
||||
assert_eq!(output.get("DRIVER_MODEL-MINIMUM_KMDF_VERSION_MINOR"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kmdf_with_prefix() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Kmdf(KmdfConfig {
|
||||
kmdf_version_major: 1,
|
||||
target_kmdf_version_minor: 33,
|
||||
minimum_kmdf_version_minor: Some(31),
|
||||
}),
|
||||
};
|
||||
|
||||
let output =
|
||||
to_map_with_prefix::<BTreeMap<_, _>>("WDK_BUILD_METADATA", &wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output["WDK_BUILD_METADATA-DRIVER_MODEL-DRIVER_TYPE"],
|
||||
"KMDF"
|
||||
);
|
||||
assert_eq!(
|
||||
output["WDK_BUILD_METADATA-DRIVER_MODEL-KMDF_VERSION_MAJOR"],
|
||||
"1"
|
||||
);
|
||||
assert_eq!(
|
||||
output["WDK_BUILD_METADATA-DRIVER_MODEL-TARGET_KMDF_VERSION_MINOR"],
|
||||
"33"
|
||||
);
|
||||
assert_eq!(
|
||||
output["WDK_BUILD_METADATA-DRIVER_MODEL-MINIMUM_KMDF_VERSION_MINOR"],
|
||||
"31"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kmdf_with_hashmap() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Kmdf(KmdfConfig {
|
||||
kmdf_version_major: 1,
|
||||
target_kmdf_version_minor: 33,
|
||||
minimum_kmdf_version_minor: Some(31),
|
||||
}),
|
||||
};
|
||||
|
||||
let output = to_map::<HashMap<_, _>>(&wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "KMDF");
|
||||
assert_eq!(output["DRIVER_MODEL-KMDF_VERSION_MAJOR"], "1");
|
||||
assert_eq!(output["DRIVER_MODEL-TARGET_KMDF_VERSION_MINOR"], "33");
|
||||
assert_eq!(output["DRIVER_MODEL-MINIMUM_KMDF_VERSION_MINOR"], "31");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_umdf() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Umdf(UmdfConfig {
|
||||
umdf_version_major: 1,
|
||||
target_umdf_version_minor: 23,
|
||||
minimum_umdf_version_minor: Some(21),
|
||||
}),
|
||||
};
|
||||
|
||||
let output = to_map::<BTreeMap<_, _>>(&wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "UMDF");
|
||||
assert_eq!(output["DRIVER_MODEL-UMDF_VERSION_MAJOR"], "1");
|
||||
assert_eq!(output["DRIVER_MODEL-TARGET_UMDF_VERSION_MINOR"], "23");
|
||||
assert_eq!(output["DRIVER_MODEL-MINIMUM_UMDF_VERSION_MINOR"], "21");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_umdf_no_minimum() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Umdf(UmdfConfig {
|
||||
umdf_version_major: 1,
|
||||
target_umdf_version_minor: 23,
|
||||
minimum_umdf_version_minor: None,
|
||||
}),
|
||||
};
|
||||
|
||||
let output = to_map::<BTreeMap<_, _>>(&wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "UMDF");
|
||||
assert_eq!(output["DRIVER_MODEL-UMDF_VERSION_MAJOR"], "1");
|
||||
assert_eq!(output["DRIVER_MODEL-TARGET_UMDF_VERSION_MINOR"], "23");
|
||||
|
||||
// `None` values are not serialized
|
||||
assert_eq!(output.get("DRIVER_MODEL-MINIMUM_UMDF_VERSION_MINOR"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wdm() {
|
||||
let wdk_metadata = metadata::Wdk {
|
||||
driver_model: DriverConfig::Wdm,
|
||||
};
|
||||
|
||||
let output = to_map::<BTreeMap<_, _>>(&wdk_metadata).unwrap();
|
||||
|
||||
assert_eq!(output["DRIVER_MODEL-DRIVER_TYPE"], "WDM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conflicting_keys_in_convert_serialized_output_to_map() {
|
||||
let input = vec![("KEY_NAME", "VALUE_1"), ("KEY_NAME", "VALUE_2")]
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect();
|
||||
|
||||
let err = convert_serialized_output_to_map::<BTreeMap<_, _>>(input).unwrap_err();
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
Error::DuplicateSerializationKeys {
|
||||
key,
|
||||
value_1,
|
||||
value_2,
|
||||
} if key == "KEY_NAME" && value_1 == "VALUE_1" && value_2 == "VALUE_2"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// License: MIT OR Apache-2.0
|
||||
|
||||
//! Private module for utility code related to the cargo-make experience for
|
||||
//! building drivers.
|
||||
|
||||
use std::{
|
||||
env,
|
||||
ffi::{CStr, OsStr},
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use windows::{
|
||||
Win32::System::Registry::{
|
||||
HKEY,
|
||||
HKEY_LOCAL_MACHINE,
|
||||
KEY_READ,
|
||||
RRF_RT_REG_SZ,
|
||||
RegCloseKey,
|
||||
RegGetValueA,
|
||||
RegOpenKeyExA,
|
||||
},
|
||||
core::{PCSTR, s},
|
||||
};
|
||||
|
||||
use crate::{ConfigError, CpuArchitecture, IoError, TwoPartVersion};
|
||||
|
||||
/// Detect `WDKContentRoot` Directory. Logic is based off of Toolset.props in
|
||||
/// NI(22H2) WDK
|
||||
#[must_use]
|
||||
pub fn detect_wdk_content_root() -> Option<PathBuf> {
|
||||
// If WDKContentRoot is present in environment(ex. running in an eWDK prompt),
|
||||
// use it
|
||||
if let Ok(wdk_content_root) = env::var("WDKContentRoot") {
|
||||
let path = Path::new(wdk_content_root.as_str());
|
||||
if path.is_dir() {
|
||||
return Some(path.to_path_buf());
|
||||
}
|
||||
eprintln!(
|
||||
"WDKContentRoot was detected to be {}, but does not exist or is not a valid directory.",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// If MicrosoftKitRoot environment variable is set, use it to set WDKContentRoot
|
||||
if let Ok(microsoft_kit_root) = env::var("MicrosoftKitRoot") {
|
||||
let path = Path::new(microsoft_kit_root.as_str());
|
||||
|
||||
if !path.is_absolute() {
|
||||
eprintln!(
|
||||
"MicrosoftKitRoot({}) was found in environment, but is not an absolute path.",
|
||||
path.display()
|
||||
);
|
||||
} else if !path.is_dir() {
|
||||
eprintln!(
|
||||
"MicrosoftKitRoot({}) was found in environment, but does not exist or is not a \
|
||||
valid directory.",
|
||||
path.display()
|
||||
);
|
||||
} else {
|
||||
let wdk_kit_version = env::var("WDKKitVersion").unwrap_or_else(|_| "10.0".to_string());
|
||||
let path = path.join("Windows Kits").join(wdk_kit_version);
|
||||
if path.is_dir() {
|
||||
return Some(path);
|
||||
}
|
||||
eprintln!(
|
||||
"WDKContentRoot was detected to be {}, but does not exist or is not a valid \
|
||||
directory.",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed
|
||||
// Roots@KitsRoot10 registry key
|
||||
if let Some(path) = read_registry_key_string_value(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
s!(r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"),
|
||||
s!(r"KitsRoot10"),
|
||||
) {
|
||||
return Some(Path::new(path.as_str()).to_path_buf());
|
||||
}
|
||||
|
||||
// Check HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows
|
||||
// Kits\Installed Roots@KitsRoot10 registry key
|
||||
if let Some(path) = read_registry_key_string_value(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
s!(r"SOFTWARE\Wow6432Node\Microsoft\Windows Kits\Installed Roots"),
|
||||
s!(r"KitsRoot10"),
|
||||
) {
|
||||
return Some(Path::new(path.as_str()).to_path_buf());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Searches a directory and determines the latest windows SDK version in that
|
||||
/// directory
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `ConfigError::DirectoryNotFound` error if the directory provided
|
||||
/// does not exist.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the path provided is not valid Unicode.
|
||||
pub fn get_latest_windows_sdk_version(path_to_search: &Path) -> Result<String, ConfigError> {
|
||||
Ok(path_to_search
|
||||
.read_dir()
|
||||
.map_err(|source| IoError::with_path(path_to_search, source))?
|
||||
.filter_map(std::result::Result::ok)
|
||||
.map(|valid_directory_entry| valid_directory_entry.path())
|
||||
.filter(|path| {
|
||||
path.is_dir()
|
||||
&& path.file_name().is_some_and(|directory_name| {
|
||||
directory_name
|
||||
.to_str()
|
||||
.is_some_and(|directory_name| directory_name.starts_with("10."))
|
||||
})
|
||||
})
|
||||
.max() // Get the latest SDK folder in case there are multiple installed
|
||||
.ok_or(ConfigError::DirectoryNotFound {
|
||||
directory: format!(
|
||||
"Windows SDK Directory in {}",
|
||||
path_to_search.to_string_lossy()
|
||||
),
|
||||
})?
|
||||
.file_name()
|
||||
.expect("path should never terminate in ..")
|
||||
.to_str()
|
||||
.expect("directory name should always be valid Unicode")
|
||||
.to_string())
|
||||
}
|
||||
|
||||
/// Detect architecture based on cargo TARGET variable.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CARGO_CFG_TARGET_ARCH` environment variable is not set,
|
||||
/// or if the cargo architecture is unsupported.
|
||||
#[must_use]
|
||||
pub fn detect_cpu_architecture_in_build_script() -> CpuArchitecture {
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect(
|
||||
"Cargo should have set the CARGO_CFG_TARGET_ARCH environment variable when executing \
|
||||
build.rs",
|
||||
);
|
||||
|
||||
CpuArchitecture::try_from_cargo_str(&target_arch).unwrap_or_else(|| {
|
||||
panic!("The target architecture, {target_arch}, is currently not supported.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Validates that a given string matches the WDK version format (10.xxx.yyy.zzz
|
||||
/// where xxx, yyy, and zzz are numeric and not necessarily 3 digits long).
|
||||
#[rustversion::attr(
|
||||
nightly,
|
||||
allow(
|
||||
clippy::nonminimal_bool,
|
||||
reason = "is_some_or is not stable until 1.82.0 is released on 10/17/24"
|
||||
)
|
||||
)]
|
||||
pub fn validate_wdk_version_format<S: AsRef<str>>(version_string: S) -> bool {
|
||||
let version = version_string.as_ref();
|
||||
let version_parts: Vec<&str> = version.split('.').collect();
|
||||
|
||||
// First, check if we have "10" as our first value
|
||||
if version_parts.first().is_none_or(|first| *first != "10") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now check that we have four entries.
|
||||
if version_parts.len() != 4 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finally, confirm each part is numeric.
|
||||
if !version_parts
|
||||
.iter()
|
||||
.all(|version_part| version_part.parse::<i32>().is_ok())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the version number from a full WDK version string.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns a [`ConfigError::WdkVersionStringFormatError`] if the
|
||||
/// version string provided is ill-formed.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the WDK version format validation function is ever changed not to
|
||||
/// validate that there are 4 substrings in the WDK version string, this
|
||||
/// function will panic.
|
||||
pub fn get_wdk_version_number<S: AsRef<str> + ToString + ?Sized>(
|
||||
version_string: &S,
|
||||
) -> Result<String, ConfigError> {
|
||||
if !validate_wdk_version_format(version_string) {
|
||||
return Err(ConfigError::WdkVersionStringFormatError {
|
||||
version: version_string.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let version_substrings = version_string.as_ref().split('.').collect::<Vec<&str>>();
|
||||
let version_substring = version_substrings.get(2).expect(
|
||||
"WDK version string was validated to be well-formatted, but we couldn't get the \
|
||||
appropriate substring!",
|
||||
);
|
||||
Ok((*version_substring).to_string())
|
||||
}
|
||||
|
||||
/// Read a string value from a registry key
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_handle` - a [`windows::Win32::System::Registry::HKEY`] to the base
|
||||
/// key
|
||||
/// * `sub_key` - a [`windows::core::PCSTR`] that is the path of a registry key
|
||||
/// relative to the `key_handle` argument
|
||||
/// * `value` - a [`windows::core::PCSTR`] that is the name of the string
|
||||
/// registry value to read
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if read value isn't valid UTF-8 or if the opened regkey could not be
|
||||
/// closed
|
||||
fn read_registry_key_string_value(
|
||||
key_handle: HKEY,
|
||||
sub_key: PCSTR,
|
||||
value: PCSTR,
|
||||
) -> Option<String> {
|
||||
let mut opened_key_handle = HKEY::default();
|
||||
let mut len = 0;
|
||||
if
|
||||
// SAFETY: `&mut opened_key_handle` is coerced to a &raw mut, so the address passed as the
|
||||
// argument is always valid. `&mut opened_key_handle` is coerced to a pointer of the correct
|
||||
// type.
|
||||
unsafe { RegOpenKeyExA(key_handle, sub_key, 0, KEY_READ, &raw mut opened_key_handle) }
|
||||
.is_ok()
|
||||
{
|
||||
if
|
||||
// SAFETY: `opened_key_handle` is valid key opened with the `KEY_QUERY_VALUE` access right
|
||||
// (included in `KEY_READ`). `&mut len` is coerced to a &raw mut, so the address passed as
|
||||
// the argument is always valid. `&mut len` is coerced to a pointer of the correct
|
||||
// type.
|
||||
unsafe {
|
||||
RegGetValueA(
|
||||
opened_key_handle,
|
||||
None,
|
||||
value,
|
||||
RRF_RT_REG_SZ,
|
||||
None,
|
||||
None,
|
||||
Some(&raw mut len),
|
||||
)
|
||||
}
|
||||
.is_ok()
|
||||
{
|
||||
let mut buffer = vec![0u8; len as usize];
|
||||
if
|
||||
// SAFETY: `opened_key_handle` is valid key opened with the `KEY_QUERY_VALUE` access
|
||||
// right (included in `KEY_READ`). `&mut buffer` is coerced to a &raw mut,
|
||||
// so the address passed as the argument is always valid. `&mut buffer` is
|
||||
// coerced to a pointer of the correct type. `&mut len` is coerced to a &raw
|
||||
// mut, so the address passed as the argument is always valid. `&mut len` is
|
||||
// coerced to a pointer of the correct type.
|
||||
unsafe {
|
||||
RegGetValueA(
|
||||
opened_key_handle,
|
||||
None,
|
||||
value,
|
||||
RRF_RT_REG_SZ,
|
||||
None,
|
||||
Some(buffer.as_mut_ptr().cast()),
|
||||
Some(&raw mut len),
|
||||
)
|
||||
}
|
||||
.is_ok()
|
||||
{
|
||||
// SAFETY: `opened_key_handle` is valid opened key that was opened by
|
||||
// `RegOpenKeyExA`
|
||||
unsafe { RegCloseKey(opened_key_handle) }
|
||||
.ok()
|
||||
.expect("opened_key_handle should be successfully closed");
|
||||
return Some(
|
||||
CStr::from_bytes_with_nul(&buffer[..len as usize])
|
||||
.expect(
|
||||
"RegGetValueA should always return a null-terminated string. The read \
|
||||
string (REG_SZ) from the registry should not contain any interior \
|
||||
nulls.",
|
||||
)
|
||||
.to_str()
|
||||
.expect("Registry value should be parseable as UTF8")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: `opened_key_handle` is valid opened key that was opened by
|
||||
// `RegOpenKeyExA`
|
||||
unsafe { RegCloseKey(opened_key_handle) }
|
||||
.ok()
|
||||
.expect("opened_key_handle should be successfully closed");
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Detects the Windows SDK version from the `Version_Number` env var or from
|
||||
/// the WDK content's `Lib` directory.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `wdk_content_root` - A reference to the path where the WDK content root is
|
||||
/// located.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `ConfigError::DirectoryNotFound` error if the directory provided
|
||||
/// does not exist.
|
||||
pub fn detect_windows_sdk_version(wdk_content_root: &Path) -> Result<String, ConfigError> {
|
||||
env::var("Version_Number")
|
||||
.or_else(|_| get_latest_windows_sdk_version(&wdk_content_root.join("Lib")))
|
||||
}
|
||||
|
||||
/// Finds the maximum version in a directory where subdirectories are named with
|
||||
/// version format "x.y"
|
||||
pub fn find_max_version_in_directory<P: AsRef<Path>>(
|
||||
directory_path: P,
|
||||
) -> Result<TwoPartVersion, IoError> {
|
||||
let directory_path = directory_path.as_ref();
|
||||
std::fs::read_dir(directory_path)
|
||||
.map_err(|source| IoError::with_path(directory_path, source))?
|
||||
.flatten()
|
||||
.filter(|entry| entry.file_type().is_ok_and(|ft| ft.is_dir()))
|
||||
.filter_map(|entry| entry.file_name().to_str()?.parse().ok())
|
||||
.max()
|
||||
.ok_or_else(|| {
|
||||
IoError::with_path(
|
||||
directory_path,
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("Maximum version in {} not found", directory_path.display()),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Safely sets an environment variable. Will not compile if crate is not
|
||||
/// targeted for Windows.
|
||||
///
|
||||
/// This function provides a safe wrapper around [`std::env::set_var`] that
|
||||
/// became unsafe in Rust 2024 edition.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if key is empty, contains an ASCII equals sign '='
|
||||
/// or the NUL character '\0', or when value contains the NUL character.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn set_var<K, V>(key: K, value: V)
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
// SAFETY: this function is only conditionally compiled for windows targets, and
|
||||
// env::set_var is always safe for windows targets
|
||||
unsafe {
|
||||
env::set_var(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn set_var<K, V>(_key: K, _value: V)
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
compile_error!(
|
||||
"windows-drivers-rs is designed to be run on a Windows host machine in a WDK environment. \
|
||||
Please build using a Windows target."
|
||||
);
|
||||
}
|
||||
|
||||
/// Safely removes an environment variable. Will not compile if crate is not
|
||||
/// targeted for Windows.
|
||||
///
|
||||
/// This function provides a safe wrapper around [`std::env::remove_var`] that
|
||||
/// became unsafe in Rust 2024 edition.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if key is empty, contains an ASCII equals sign '='
|
||||
/// or the NUL character '\0', or when value contains the NUL character.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn remove_var<K>(key: K)
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
{
|
||||
// SAFETY: this function is only conditionally compiled for windows targets, and
|
||||
// env::remove_var is always safe for windows targets
|
||||
unsafe {
|
||||
env::remove_var(key);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn remove_var<K>(_key: K)
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
{
|
||||
compile_error!(
|
||||
"windows-drivers-rs is designed to be run on a Windows host machine in a WDK environment. \
|
||||
Please build using a Windows target."
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assert_fs::prelude::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Function with_clean_env clears the inputted environment variable and runs the
|
||||
// closure
|
||||
fn with_clean_env<F>(key: &str, f: F)
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
let original = env::var(key).ok();
|
||||
|
||||
// SAFETY: We have verified that this is built for a Windows host due to no
|
||||
// compile errors from building `set_var`.
|
||||
unsafe {
|
||||
env::remove_var(key);
|
||||
}
|
||||
|
||||
f();
|
||||
|
||||
if let Some(val) = &original {
|
||||
// SAFETY: We have verified that this is built for a Windows host due to no
|
||||
// compile errors from building `set_var`.
|
||||
unsafe {
|
||||
env::set_var(key, val);
|
||||
}
|
||||
} else {
|
||||
// SAFETY: We have verified that this is built for a Windows host due to no
|
||||
// compile errors from building `set_var`.
|
||||
unsafe {
|
||||
env::remove_var(key);
|
||||
}
|
||||
}
|
||||
|
||||
assert!(env::var(key).ok() == original);
|
||||
}
|
||||
|
||||
mod read_registry_key_string_value {
|
||||
use windows::Win32::UI::Shell::{
|
||||
FOLDERID_ProgramFiles,
|
||||
KF_FLAG_DEFAULT,
|
||||
SHGetKnownFolderPath,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn read_reg_key_programfilesdir() {
|
||||
let program_files_dir =
|
||||
// SAFETY: FOLDERID_ProgramFiles is a constant from the windows crate, so the pointer (resulting from its reference being coerced) is always valid to be dereferenced
|
||||
unsafe { SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, None) }
|
||||
.expect("Program Files Folder should always resolve via SHGetKnownFolderPath.");
|
||||
|
||||
assert_eq!(
|
||||
read_registry_key_string_value(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
s!(r"SOFTWARE\Microsoft\Windows\CurrentVersion"),
|
||||
s!("ProgramFilesDir")
|
||||
),
|
||||
Some(
|
||||
// SAFETY: program_files_dir pointer stays valid for reads up until and
|
||||
// including its terminating null
|
||||
unsafe { program_files_dir.to_string() }
|
||||
.expect("Path resolved from FOLDERID_ProgramFiles should be valid UTF16.")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_wdk_strings() {
|
||||
let test_string = "10.0.12345.0";
|
||||
assert_eq!(
|
||||
get_wdk_version_number(test_string).ok(),
|
||||
Some("12345".to_string())
|
||||
);
|
||||
let test_string = "10.0.5.0";
|
||||
assert_eq!(
|
||||
get_wdk_version_number(test_string).ok(),
|
||||
Some("5".to_string())
|
||||
);
|
||||
let test_string = "10.0.0.0";
|
||||
assert_eq!(
|
||||
get_wdk_version_number(test_string).ok(),
|
||||
Some("0".to_string())
|
||||
);
|
||||
let test_string = "11.0.0.0";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
let test_string = "10.0.12345.0.0";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
let test_string = "10.0.12345.a";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
let test_string = "10.0.12345";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
let test_string = "10.0.1234!5.0";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
let test_string = "Not a real version!";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
let test_string = "";
|
||||
assert_eq!(
|
||||
format!("{}", get_wdk_version_number(test_string).err().unwrap()),
|
||||
format!(
|
||||
"the WDK version string provided ({}) was not in a valid format",
|
||||
test_string
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
mod find_max_version_in_directory {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_directory() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
let result = find_max_version_in_directory(temp_dir.path());
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().source.kind(),
|
||||
std::io::ErrorKind::NotFound
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonexistent_directory() {
|
||||
let nonexistent_path = std::path::Path::new("/this/path/does/not/exist");
|
||||
let result = find_max_version_in_directory(nonexistent_path);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_version_directories() {
|
||||
// Single valid version directory
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("3.14").create_dir_all().unwrap();
|
||||
temp_dir.child("folder1").create_dir_all().unwrap();
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(3, 14)
|
||||
);
|
||||
// Multiple valid version directories
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("1.2").create_dir_all().unwrap();
|
||||
temp_dir.child("1.10").create_dir_all().unwrap();
|
||||
temp_dir.child("2.0").create_dir_all().unwrap();
|
||||
temp_dir.child("not_a_version").create_dir_all().unwrap();
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(2, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_version_directories() {
|
||||
// Single invalid directory
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("folder1").create_dir_all().unwrap();
|
||||
let result = find_max_version_in_directory(temp_dir.path());
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().source.kind(),
|
||||
std::io::ErrorKind::NotFound
|
||||
);
|
||||
|
||||
// Multiple invalid directories
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("folder1").create_dir_all().unwrap();
|
||||
temp_dir.child("1.2.3").create_dir_all().unwrap(); // Too many dots
|
||||
temp_dir.child("a.b").create_dir_all().unwrap(); // Non-numeric
|
||||
temp_dir.child("1").create_dir_all().unwrap(); // No dot
|
||||
temp_dir.child("1.").create_dir_all().unwrap(); // Missing minor
|
||||
temp_dir.child(".5").create_dir_all().unwrap(); // Missing major
|
||||
let result = find_max_version_in_directory(temp_dir.path());
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().source.kind(),
|
||||
std::io::ErrorKind::NotFound
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_version_priority() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("1.999").create_dir_all().unwrap();
|
||||
temp_dir.child("2.0").create_dir_all().unwrap();
|
||||
temp_dir.child("1.1000").create_dir_all().unwrap();
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(2, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_version_comparison() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("1.5").create_dir_all().unwrap();
|
||||
temp_dir.child("1.10").create_dir_all().unwrap();
|
||||
temp_dir.child("1.2").create_dir_all().unwrap();
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(1, 10)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_versions() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("0.0").create_dir_all().unwrap();
|
||||
temp_dir.child("0.1").create_dir_all().unwrap();
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(0, 1)
|
||||
);
|
||||
temp_dir.child("1.0").create_dir_all().unwrap();
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_valid_and_invalid_entries() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
temp_dir.child("1.5").create_dir_all().unwrap();
|
||||
temp_dir.child("2.0").create_dir_all().unwrap();
|
||||
temp_dir.child("invalid").create_dir_all().unwrap();
|
||||
temp_dir.child("1.2.3").create_dir_all().unwrap(); // Invalid: too many dots
|
||||
temp_dir.child("a.b").create_dir_all().unwrap(); // Invalid: non-numeric
|
||||
temp_dir.child("not_version").touch().unwrap(); // File: ignored
|
||||
temp_dir.child("3.0").touch().unwrap(); // File: ignored
|
||||
// Should find the maximum among valid version directories only
|
||||
assert_eq!(
|
||||
find_max_version_in_directory(temp_dir.path()).unwrap(),
|
||||
TwoPartVersion(2, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod safe_env_vars {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn set_var_and_remove_var() {
|
||||
let key = "WDK_BUILD_TEST_VAR";
|
||||
with_clean_env(key, || {
|
||||
set_var(key, "test_value");
|
||||
assert_eq!(env::var(key).unwrap(), "test_value");
|
||||
remove_var(key);
|
||||
assert!(env::var(key).is_err());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user