Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions crates/cargo-wdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
[package]
edition.workspace = true
name = "cargo-wdk"
version = "0.0.1"
version = "0.1.0"
authors = ["Microsoft"]
# set to false until the tool is stable and ready to be published
publish = false
description = "A Cargo extension for building Windows drivers using the Windows Driver Kit (WDK)"
repository.workspace = true
license.workspace = true
keywords = ["wdk", "windows", "cargo"]
categories = ["command-line-utilities", "development-tools::cargo-plugins"]
exclude = ["tests"]

[dependencies]
anyhow.workspace = true
Expand Down
142 changes: 70 additions & 72 deletions crates/cargo-wdk/README.md
Original file line number Diff line number Diff line change
@@ -1,117 +1,115 @@
# cargo-wdk

`cargo-wdk` is a Cargo extension (plugin) that provides a Cargo-like command line interface for developers to create and build Windows Rust Driver crates that depend on the Windows Driver Kit (WDK) and windows-drivers-rs. It extends `new` and `build` functionalities of Cargo and allows developers to start new projects and build existing or new projects with simple “Cargo-like" commands.
A development tool for Windows Rust drivers based on [windows-drivers-rs](https://github.com/microsoft/windows-drivers-rs).

## Features

`cargo-wdk` currently supports creating and building Windows Rust driver crates.
## Installation

- **`new`** command takes a path and the driver type (KMDF, UMDF or WDM) as inputs and creates a new Rust driver project of the specified type at the specified path. It relies on pre-defined templates (in `./crates/cargo-wdk/templates`) to scaffold the project with the required files and boilerplate code.
To install, run:

- **`build`** command compiles, builds and packages Rust driver projects. The build profile and the target architecture are optional arguments, and can be passed to the command invocation. Consists of a **`build_task`** that invokes `cargo build` and **`package_task`** that invokes WDK binaries - `StampInf`, `Inf2Cat`, `InfVerif`, `Signtool`, `CertMgr` in the correct order, and generates the final driver package. If no valid WDK configuration is found in the package/workspace `package_task` is skipped.
```pwsh
cargo install cargo-wdk
```

The command can be run from:

- Root of an individual/stand-alone crate: Final package available under the crate's **target** directory - `./target/<target_triple>/<profile>/<driver_crate_name>_package`.
## Commands

- Root of a workspace: Final package will be available under the workspace's `target` directory - `./target/<target_triple>/<profile>/<driver_crate_name>_package`.

- Root of a member crate of a workspace: Recognizes the workspace the member is part of, executes the build and package tasks for this member alone. Final package will be available under the workspace's `target` directory.

- Root of an emulated workspace: An emulated workspace is a directory containing one or more Rust workspaces. In this case, `cargo-wdk` builds each workspace individually and the final driver packages can be found under the `target` directory of the specific workspaces/crates.
`cargo-wdk` exposes two commands `new` and `build`.

**NOTE**: The `build` command can build workspaces containing both driver and non-driver crates: driver crates are built and packaged, while non-driver crates are only built and the packaging step is skipped.
`new` creates new driver projects from pre-defined templates and helps you get started faster. It invokes `cargo new` to create the project structure and then adds all the necessary files from a template.

## Installation
`build` compiles the source code of a driver project and creates a [driver package](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/driver-packages). It invokes `cargo build` to compile the code and then runs other required tools like `stampinf`, `inf2cat` and `signtool` in the correct order to produce the final driver package.

To install `cargo-wdk`, you need to have [Rust installed on your system](https://www.rust-lang.org/tools/install).
## Usage

Once you have Rust installed, you can install `cargo-wdk` as follows:
### `new` Command

```pwsh
cargo install --git https://github.com/microsoft/windows-drivers-rs.git cargo-wdk --locked
```
Usage: cargo wdk new [OPTIONS] <--kmdf|--umdf|--wdm> <PATH>

The install command compiles the `cargo-wdk` binary and copies it to Cargo's bin directory - `%USERPROFILE%.cargo\bin`.
Arguments:
<PATH> Path at which the new driver crate should be created

You can test the installation by running:
```pwsh
cargo wdk --version
```
Options:
--kmdf Create a KMDF driver crate
--umdf Create a UMDF driver crate
--wdm Create a WDM driver crate
-h, --help Print help

For help, run:
```pwsh
cargo wdk --help
Verbosity:
-v, --verbose... Increase logging verbosity
-q, --quiet... Decrease logging verbosity
```

## Installing WDK
`new` takes the type of driver project you want to create (`kmdf`, `umdf` or `wdm`) and its destination path (`PATH`) as inputs along with flags specifying log verbosity.

`cargo-wdk` builds the drivers using the WDK. Please ensure that the WDK is installed on the development system.
The recommended way to do this is to [enter an eWDK developer prompt](https://learn.microsoft.com/en-us/windows-hardware/drivers/develop/using-the-enterprise-wdk#getting-started).
The last component of `PATH` is used as the name of the crate.

## Usage Examples
#### Examples

1. `new` command to create a new Rust driver project:
```pwsh
cargo wdk new [OPTIONS] <DRIVER_PROJECT_PATH>
- To create a new KMDF project called `my_driver` under the current folder run:

```pwsh
cargo wdk new my_driver --kmdf
```

Example Usage:

* Create a UMDF project called `sample_driver` under the folder `my_projects`
- To create a new UMDF project called `my_driver` under the folder `my_projects` run:

```pwsh
cargo wdk new my_projects\sample_driver --umdf
cargo wdk new my_projects\my_driver --umdf
```

* Create a KMDF project called `sample_driver` under the current folder
```pwsh
cargo wdk new sample_driver --kmdf
```
### `build` Command

Use `--help` for more information on arguments and options
```pwsh
Usage: cargo wdk build [OPTIONS]

Options:
--profile <PROFILE> Build artifacts with the specified profile
--target-arch <TARGET_ARCH> Build for the target architecture
--verify-signature Verify the signature
--sample Build sample class driver project
-h, --help Print help

Verbosity:
-v, --verbose... Increase logging verbosity
-q, --quiet... Decrease logging verbosity
```

2. `build` command to build and package driver projects:
```pwsh
cargo wdk build [OPTIONS]
```

Example Usage:
* Navigate to the project/workspace root and run
`build` takes a number of inputs specifying build profile (`dev` or `release`), target architecture (`amd64` or `arm64`), a flag enabling signature verification and a flag indicating a sample driver along with verbosity flags.

```pwsh
cargo wdk build
```
When the command completes the packaged driver artifacts are emitted at the path `target\<profile>\<project-name>-package`.

* With `--target-arch`
#### Workspace support

```pwsh
cargo wdk build --target-arch arm64
```
`build` supports workspaces. If run at the root of a workspace, it will build and package all driver projects in it. If the workspace contains any non-driver projects they will also be built but not packaged.

* With `--profile`
#### Sample Drivers

```pwsh
cargo wdk build --profile Release
```
Building a sample driver requires the `--sample` flag. If it is not specified, the build will fail.

Please use `--help` for more information on arguments and options.
If you have a workspace with a mix of sample and non-sample driver projects, the build will fail as that scenario is not supported yet. In the future `build` will be able to automatically detect sample projects. That will remove the need for the `--sample` flag and enable support for this scenario.

## Driver Package Signature Verification
#### Signing and Verification

The `build` command can be run with `--verify-signature` option to enable the verification of the `.sys/.dll` and `.cat` files generated in the final package. Currently, the `build` command uses a **test certificate** named "WDRLocalTestCert" in a store named "WDRTestCertStore" to sign the files. Verification using the `signtool verify` command requires these certificates to be present in the host system's `Trusted Root Certification Authorities`. Typically, these test certificates are only installed into `Trusted Root Certification Authorities` on computers dedicated to testing drivers, and not personal development machines, given the security implications of installing your own root certificates.
To sign driver artifacts `build` looks for a certificate called `WDRLocalTestCert` in a store called `WDRTestCertStore`. Make sure you place your signing certificate there with that name. If no certificate is found, `build` will automatically generate a new self-signed one and add it for you.

If you understand these implications, and have installed the test certificate, then you may validate the signatures as follows:
If the `--verify-signature` flag is provided, the signatures are verified after signing. For verification to work, make sure you add a copy of the signing certificate in the `Trusted Root Certification Authorities` store. For security reasons `build` does not automatically do this even when it automatically generates the cert. You will have to always perform this step manually.

#### Examples

- To build a driver project with default options, navigate to the root of the project and run:

```pwsh
cargo wdk build --verify-signature
cargo wdk build
```

## Building Sample Class Drivers

The `build` command can be used to build drivers whose class is defined as `Sample` in its `.inx` file, for ex, [echo (kmdf) DriverSync](https://github.com/microsoft/Windows-rust-driver-samples/tree/main/general/echo/kmdf/driver/DriverSync). The command handles passing additional flags to `InfVerif` task based on the WDK Version being used. So, if you are building a `Sample` class driver, you may use the `build` command with the `--sample` flag as follows,
- To build for target `arm64` and the `release` profile, navigate to the root of the project and run:

```pwsh
cargo wdk build --sample
cargo wdk build --target-arch arm64 --profile release
```

**NOTE**: Running `cargo wdk build --sample` from a workspace root will try to package **all** the driver crates in that workspace as `Sample` class drivers. If the workspace contains a non-sample class driver, it will result in an error. A workaround is to build each crate individually (pass `--sample` only for "Sample" class driver) or ensure all driver crates in a workspace are "Sample" class drivers.
- To build projects in a workspace for target `amd64`, navigate to the root of the workspace and run:

```pwsh
cargo wdk build --target-arch amd64
```
13 changes: 5 additions & 8 deletions crates/cargo-wdk/src/actions/build/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1740,14 +1740,11 @@ impl TestBuildAction {
}

expected_cargo_build_args.push("-v".to_string());
let expected_output = override_output.map_or_else(
|| Output {
status: ExitStatus::default(),
stdout: vec![],
stderr: vec![],
},
|output| output,
);
let expected_output = override_output.unwrap_or_else(|| Output {
status: ExitStatus::default(),
stdout: vec![],
stderr: vec![],
});
self.mock_run_command
.expect_run()
.withf(
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-wdk/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub struct BuildArgs {
#[arg(long)]
pub verify_signature: bool,

/// Build Sample Class Driver Project
/// Build sample class driver project
#[arg(long)]
pub sample: bool,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/wdk-build/src/cargo_make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ pub fn load_rust_driver_sample_makefile() -> Result<(), ConfigError> {
/// This is necessary so that paths in the [`wdk_build`] makefile can be
/// relative to `CARGO_MAKE_CURRENT_TASK_INITIAL_MAKEFILE_DIRECTORY`. The
/// version of `wdk-build` from which the file being symlinked to comes from is
/// determined by the workding directory of the process that invokes this
/// determined by the working directory of the process that invokes this
/// function. For example, if this function is ultimately executing in a
/// `cargo_make` `load_script`, the files will be symlinked from the `wdk-build`
/// version that is in the `.Cargo.lock` file, and not the `wdk-build` version
Expand Down
Loading