Skip to content

Port wxc from C++ to Rust - #29

Merged
Branden Bonaby (bbonaby) merged 2 commits into
mainfrom
user/gudge/port_from_cpp_to_rust
Mar 16, 2026
Merged

Port wxc from C++ to Rust#29
Branden Bonaby (bbonaby) merged 2 commits into
mainfrom
user/gudge/port_from_cpp_to_rust

Conversation

@MGudgin

@MGudgin Gudge (MGudgin) commented Mar 13, 2026

Copy link
Copy Markdown
Member

Rewrite the entire wxc (Windows Container Executor) codebase from C++ to
Rust, replacing the Visual Studio / vcxproj build system with a Cargo
workspace. Update documentation, build scripts, and SDK/CLI tooling to
reflect the new Rust-based build.

New Rust crate structure (src/)

  • wxc: CLI entry point (wxc-exec binary) using clap for argument
    parsing. Supports config via file path, --config flag, or
    --config-base64. Includes --delete mode for container profile cleanup.
  • wxc_common: Shared library containing all core modules:
    • appcontainer: AppContainer profile creation and sandboxed script
      execution via Win32 Security Isolation APIs
    • config_parser: JSON configuration loading from file or base64
    • models: Data models (CodexRequest, ScriptResponse, NetworkPolicy,
      FilesystemPolicy, etc.) with serde serialization
    • error: Unified WxcError enum using thiserror
    • filesystem_bfs: BFS-based filesystem permission manager for
      read-write and read-only path policies
    • network_firewall: Windows Firewall rule management via COM/INetFwPolicy2
      for network allow/block enforcement
    • process_util: Process creation, pipe I/O, and output capture utilities
    • script_runner: Script execution orchestration
    • string_util: Wide-string (PCWSTR/HSTRING) conversion helpers
    • logger: Logging with debug and event-log modes
    • validator: Script request validation
  • wxc_test_driver: Test harness binary (wxc-test-driver) for
    integration testing

Deleted C++ sources

Remove all original C++ implementation files, headers, PCH files, and
Visual Studio project/filter files for wxc, wxc_common, wxc_test_driver,
and wxc_tests. Remove leftover C++ build artifacts: wxc.sln,
vcpkg.json, .clang-format, Directory.Build.props, build_all.bat,
build_debug.bat, and build_tests.bat.

Updated CI pipeline for Rust

Replace the C++ build steps (MSBuild, vcpkg) in .github/workflows/build.yml
with Rust toolchain setup (rustup), cargo build, and cargo test steps.
Use the pre-installed rustup on GitHub-hosted runners.

Updated docs and tooling for Rust migration

  • Readme.md: Replace VS 2022/C++/vcpkg prerequisites with Rust toolchain
    and cargo build instructions
  • build.bat: Replace msbuild wxc.sln with cargo build --release
  • sdk/README.md: Update build instructions to reference cargo
  • cli/ARCHITECTURE.md: Update C++ reference to Rust
  • cli/src/cli.ts: Update default wxc-exec.exe path from x64/Debug to
    src/target/debug
  • cli/example.ts: Same path update
  • sdk/src/platform.ts: Update findWxcExecutable() to search Rust build
    output paths (src/target/{release,debug}) instead of VS layout
    (bin/{x64,ARM64})

Other changes

  • .gitignore: Add **/target/ for Rust build artifacts
  • examples/08_pwsh.json: New PowerShell AppContainer example config
  • src/expert-code-review-results.md: Code review findings from the port

Key dependencies: windows 0.58 crate (Win32 API bindings), serde/serde_json,
clap 4, thiserror, anyhow, base64.

Co-authored-by: shschaefer stuart@theschaefers.com
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@MGudgin
Gudge (MGudgin) force-pushed the user/gudge/port_from_cpp_to_rust branch from 49e12d0 to 8090c73 Compare March 13, 2026 20:58
Rewrite the entire wxc (Windows Container Executor) codebase from C++ to
Rust, replacing the Visual Studio / vcxproj build system with a Cargo
workspace. Update documentation, build scripts, and SDK/CLI tooling to
reflect the new Rust-based build.

- **wxc**: CLI entry point (wxc-exec binary) using clap for argument
  parsing. Supports config via file path, --config flag, or
  --config-base64. Includes --delete mode for container profile cleanup.
- **wxc_common**: Shared library containing all core modules:
  - appcontainer: AppContainer profile creation and sandboxed script
    execution via Win32 Security Isolation APIs
  - config_parser: JSON configuration loading from file or base64
  - models: Data models (CodexRequest, ScriptResponse, NetworkPolicy,
    FilesystemPolicy, etc.) with serde serialization
  - error: Unified WxcError enum using thiserror
  - filesystem_bfs: BFS-based filesystem permission manager for
    read-write and read-only path policies
  - network_firewall: Windows Firewall rule management via COM/INetFwPolicy2
    for network allow/block enforcement
  - process_util: Process creation, pipe I/O, and output capture utilities
  - script_runner: Script execution orchestration
  - string_util: Wide-string (PCWSTR/HSTRING) conversion helpers
  - logger: Logging with debug and event-log modes
  - validator: Script request validation
- **wxc_test_driver**: Test harness binary (wxc-test-driver) for
  integration testing

Remove all original C++ implementation files, headers, PCH files, and
Visual Studio project/filter files for wxc, wxc_common, wxc_test_driver,
and wxc_tests. Remove leftover C++ build artifacts: wxc.sln,
vcpkg.json, .clang-format, Directory.Build.props, build_all.bat,
build_debug.bat, and build_tests.bat.

Replace the C++ build steps (MSBuild, vcpkg) in .github/workflows/build.yml
with Rust toolchain setup (rustup), cargo build, and cargo test steps.
Use the pre-installed rustup on GitHub-hosted runners.

- Readme.md: Replace VS 2022/C++/vcpkg prerequisites with Rust toolchain
  and cargo build instructions
- build.bat: Replace msbuild wxc.sln with cargo build --release
- sdk/README.md: Update build instructions to reference cargo
- cli/ARCHITECTURE.md: Update C++ reference to Rust
- cli/src/cli.ts: Update default wxc-exec.exe path from x64/Debug to
  src/target/debug
- cli/example.ts: Same path update
- sdk/src/platform.ts: Update findWxcExecutable() to search Rust build
  output paths (src/target/{release,debug}) instead of VS layout
  (bin/{x64,ARM64})

- .gitignore: Add **/target/ for Rust build artifacts
- examples/08_pwsh.json: New PowerShell AppContainer example config
- src/expert-code-review-results.md: Code review findings from the port

Key dependencies: windows 0.58 crate (Win32 API bindings), serde/serde_json,
clap 4, thiserror, anyhow, base64.

Co-authored-by: shschaefer <stuart@theschaefers.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MGudgin
Gudge (MGudgin) force-pushed the user/gudge/port_from_cpp_to_rust branch from 8090c73 to cf4a5b0 Compare March 13, 2026 21:05
Comment thread sdk/src/platform.ts
path.join(__dirname, '..', 'bin', platformDir, 'wxc-exec.exe'),
// Relative to project when used as a node module
path.join(__dirname, '..', 'node_modules', '@shschaefer', 'wxc-sdk', 'bin', platformDir, 'wxc-exec.exe'),
// Rust release build output

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to adjust this. It is not quite the same as what was there before. Also, how are you handling platform builds with Rust?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For differing x64 and arm64 windows builds in Rust we'd need to do something like this (for debug no --release):

cargo build --release --target x86_64-pc-windows-msvc
cargo build --release --target aarch64-pc-windows-msvc

which produces:

target/x86_64-pc-windows-msvc/release/wxc.exe
target/aarch64-pc-windows-msvc/release/wxc.exe

So the paths in this PR would need to change. Note: VS does not support rust only VS Code. The equivalent of the cpp post build event here in VS would be to create a tasks.json file in the .vscode/ folder. In there we would use powershell to run the cargo commands and copy the exes to where ever we wanted.

Going a bit further though, as we think about distribution. I think we should leverage npm's optional dependency feature as it will automatically install the dependency package based on the hosts OS and CPU.

We can do something like this:

  1. publish wxc-exec-win-x64 to npm: (prebuilt wxc-exec.exe)
    1. Package.json :
     {
       "name": "@microsoft/wxc-exec-win-x64",
       "os": ["win32"],
       "cpu": ["x64"],
       "files": ["wxc-exec.exe"]
      }
  2. publish wxc-exec-win-arm64 to npm: (prebuilt wxc-exec.exe)
    1. Package.json :
     {
       "name": "@microsoft/wxc-exec-win-arm64",
       "os": ["win32"],
       "cpu": ["arm64"],
       "files": ["wxc-exec.exe"]
      }
  3. publish mxc-sdk to npm
    1. Package.json :
     {
       "name": "@microsoft/wxc-sdk",
       "optionalDependencies": {
         "@microsoft/wxc-win-x64": "0.1.x",
         "@microsoft/wxc-win-arm64": "0.1.x"
        }
     }

Then the SDK will just need to do something like this to get the path to the executable:

import os from "os"

export function getWxcBinaryPath(): string {
  const platform = os.platform();
  const arch = os.arch();

  if (platform !== 'win32') {
    throw new Error(`Unsupported OS: ${platform}. WXC currently supports Windows only.`);
  }

  let packageName: string;
  if (arch === 'x64') {
    packageName = '@microsoft/wxc-exec-win-x64';
  } else if (arch === 'arm64') {
    packageName = '@microsoft/wxc-exec-win-arm64';
  } else {
    throw new Error(`Unsupported architecture: ${arch}`);
  }

  try {
    return require.resolve(`${packageName}/wxc-exec.exe`);
  } catch {
    throw new Error(
      `Failed to find wxc-exec binary. The platform package "${packageName}" may not be installed. `
    );
  }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated build.bat to build arm64/x64 release/debug. I'll tackle the package approach in a subsequent PR

Comment thread src/Cargo.toml
Comment thread src/wxc/Cargo.toml
Comment thread src/wxc/Cargo.toml
@@ -1 +1 @@
name: Build

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably be building the Rust components for both x64 and ARM64 windows targets.

Also, most Rust repositories include two standard checks in their CI pipelines:

rustfmt: enforces consistent code formatting

clippy : a linter that catches common mistakes and non-idiomatic patterns

These tools are widely used across the Rust ecosystem and are typically enforced in CI to ensure code quality and consistency. We might as well start now while we're early.

I also suggest splitting the SDK build from the wxc build. The SDK is currently Node/TypeScript, while wxc is a Rust binary crate, and the SDK could evolve to other languages in the future. Separating them will keep the pipeline easier to maintain and reason about.

I think this file should look something this, which should cover formatting, linting, and tests across the supported architectures for rust:

name: Build

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:

  # Check formatting and linting for the WXC Rust code
  wxc-exec-lint:
    name: WXC Exec Lint
    runs-on: windows-latest

    defaults:
      run:
        working-directory: src

    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust toolchain
        run: |
          rustup update stable
          rustup component add rustfmt clippy

      - name: Check formatting
        id: fmt
        continue-on-error: true
        run: cargo fmt --all -- --check

      - name: Run clippy
        run: cargo clippy --workspace --all-targets -- -D warnings

      - name: Fail if formatting check failed
        if: steps.fmt.outcome == 'failure'
        run: exit 1


  # Build and run Rust tests on x64 and ARM64
  wxc-exec-test:
    name: WXC Exec Rust tests
    strategy:
      matrix:
        include:
          - target: x86_64-pc-windows-msvc
            runner: windows-2025
          - target: aarch64-pc-windows-msvc
            runner: windows-11-arm

    runs-on: ${{ matrix.runner }}

    defaults:
      run:
        working-directory: src

    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust toolchain
        run: |
          rustup update stable
          rustup target add ${{ matrix.target }}

      - name: Run tests
        run: cargo test --workspace


  # Build and package the TypeScript SDK
  wxc-typescript-sdk:
    name: WXC TypeScript SDK
    runs-on: windows-latest

    defaults:
      run:
        working-directory: sdk

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Pack npm package
        run: npm pack

      - uses: actions/upload-artifact@v4
        with:
          name: sdk-npm-package
          path: sdk/*.tgz

If you take my change above. You can run the same checks locally before pushing:

Format Rust code automatically: cargo fmt --all
Check formatting (same as CI): cargo fmt --all -- --check
Run the linter: cargo clippy --workspace --all-targets -- -D warnings
Run tests: cargo test --workspace
If needed, install them via cmdline: rustup component add rustfmt clippy

I found that installing the GitHub CLI enables GitHub Copilot to retrieve build pipeline failure information and quickly fix, commit, and push changes if you tell it to. Note: GitHub CLI is different from the GitHub Copilot CLI.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, we should add clippy and rustfmt (only if we add a custom format, some of the standard rules are rough).

Comment thread src/wxc/src/main.rs
@@ -0,0 +1,139 @@
use std::fmt::Write;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI all rust files should have the following copyright header:

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

See: https://docs.opensource.microsoft.com/releasing/general-guidance/copyright-headers/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this as a commit/PR, rather than comment?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#35

Comment thread src/wxc/src/main.rs
Comment on lines +41 to +53
fn log_request(request: &CodexRequest, logger: &mut Logger) {
let _ = writeln!(logger, "Script code length: {}", request.script_code.len());
let _ = writeln!(logger, "Working directory: {}", request.working_directory);
let _ = writeln!(logger, "Script timeout: {}", request.script_timeout);
let _ = writeln!(
logger,
"Container name: {}",
request.policy.app_container_name
);
}

fn display_script_results(response: &ScriptResponse, logger: &mut Logger) {
let _ = writeln!(logger, "Exit code: {}", response.exit_code);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it's written like this because we don't care about logging failures? On the off chance we did care about them. We can return a fmt::Result and use the ? operator which acts like our short circuit RETURN_IF_FAILED macros in c++, so something like:

fn log_request(request: &CodexRequest, logger: &mut Logger) -> fmt::Result {
   writeln!(logger, "Script code length: {}", request.script_code.len())?;
   // other writeln statements below
}


/// Remove a specific Python "Failed to find real location of ..." line from stderr.
pub fn suppress_python_location_error(stderr: &mut String) {
let needle = "Failed to find real location of ";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure if this error is localized based on the system or if it's always in english.

Comment thread sdk/src/platform.ts
path.join(__dirname, '..', 'bin', platformDir, 'wxc-exec.exe'),
// Relative to project when used as a node module
path.join(__dirname, '..', 'node_modules', '@shschaefer', 'wxc-sdk', 'bin', platformDir, 'wxc-exec.exe'),
// Rust release build output

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For differing x64 and arm64 windows builds in Rust we'd need to do something like this (for debug no --release):

cargo build --release --target x86_64-pc-windows-msvc
cargo build --release --target aarch64-pc-windows-msvc

which produces:

target/x86_64-pc-windows-msvc/release/wxc.exe
target/aarch64-pc-windows-msvc/release/wxc.exe

So the paths in this PR would need to change. Note: VS does not support rust only VS Code. The equivalent of the cpp post build event here in VS would be to create a tasks.json file in the .vscode/ folder. In there we would use powershell to run the cargo commands and copy the exes to where ever we wanted.

Going a bit further though, as we think about distribution. I think we should leverage npm's optional dependency feature as it will automatically install the dependency package based on the hosts OS and CPU.

We can do something like this:

  1. publish wxc-exec-win-x64 to npm: (prebuilt wxc-exec.exe)
    1. Package.json :
     {
       "name": "@microsoft/wxc-exec-win-x64",
       "os": ["win32"],
       "cpu": ["x64"],
       "files": ["wxc-exec.exe"]
      }
  2. publish wxc-exec-win-arm64 to npm: (prebuilt wxc-exec.exe)
    1. Package.json :
     {
       "name": "@microsoft/wxc-exec-win-arm64",
       "os": ["win32"],
       "cpu": ["arm64"],
       "files": ["wxc-exec.exe"]
      }
  3. publish mxc-sdk to npm
    1. Package.json :
     {
       "name": "@microsoft/wxc-sdk",
       "optionalDependencies": {
         "@microsoft/wxc-win-x64": "0.1.x",
         "@microsoft/wxc-win-arm64": "0.1.x"
        }
     }

Then the SDK will just need to do something like this to get the path to the executable:

import os from "os"

export function getWxcBinaryPath(): string {
  const platform = os.platform();
  const arch = os.arch();

  if (platform !== 'win32') {
    throw new Error(`Unsupported OS: ${platform}. WXC currently supports Windows only.`);
  }

  let packageName: string;
  if (arch === 'x64') {
    packageName = '@microsoft/wxc-exec-win-x64';
  } else if (arch === 'arm64') {
    packageName = '@microsoft/wxc-exec-win-arm64';
  } else {
    throw new Error(`Unsupported architecture: ${arch}`);
  }

  try {
    return require.resolve(`${packageName}/wxc-exec.exe`);
  } catch {
    throw new Error(
      `Failed to find wxc-exec binary. The platform package "${packageName}" may not be installed. `
    );
  }
}

Comment thread src/Cargo.toml
thiserror = "2"
anyhow = "1"
base64 = "0.22"
clap = { version = "4", features = ["derive"] }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add wxc_common = { path = "wxc_common" } here as well and probably sort this list

Comment thread sdk/src/platform.ts
Comment on lines 37 to 48
/**
* Check Windows build version requirements for WXC
*
* Requirements:
* - Registry key HKLM\Software\Microsoft\Windows NT\CurrentVersion\BuildLab must exist
* - BuildLab format: buildNumber.branch.buildDate
* - Branch must be "ge_current_directwinai*"
* - Build number must be >= 26559
*
* @returns true if Windows build meets requirements, false otherwise
*/
function checkWindowsBuildVersion(): boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll need to update this before release

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have asked Anis to get me updated information. Will take care of this in a separate PR.

@bbonaby Branden Bonaby (bbonaby) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to Stuart, we'll check this in and I'll do my comments as a PR. Thanks Martin

@bbonaby
Branden Bonaby (bbonaby) merged commit 8fdaa86 into main Mar 16, 2026
2 checks passed
@MGudgin
Gudge (MGudgin) deleted the user/gudge/port_from_cpp_to_rust branch March 16, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants