Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4eb6489
add tool_header2rdl: C/C++ header to RDL converter
Copilot Apr 7, 2026
c567c9a
address code review: safe pointee unwrap, better unnamed identifiers
Copilot Apr 7, 2026
e03fc8c
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 7, 2026
f002771
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 7, 2026
7c022d2
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 7, 2026
e899265
plan: replace CLI-args API with builder pattern
Copilot Apr 7, 2026
7005f8b
replace args-based API with Converter builder pattern; update tests
Copilot Apr 7, 2026
432d5a1
address review: rename idx, unify unnamed identifiers to param_/field_
Copilot Apr 7, 2026
b54a62a
fix build: use clang runtime feature so libclang isn't required at co…
Copilot Apr 7, 2026
38cec2e
fix clippy: use is_some_and instead of map_or(false, ...)
Copilot Apr 7, 2026
4d4b7de
fix test: exclude test_header2rdl from stable arm64 CI (no libclang o…
Copilot Apr 7, 2026
c6198ff
fix: exclude test_header2rdl from all Windows CI test runs (libclang …
Copilot Apr 7, 2026
5115741
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 7, 2026
f347f6a
fix: install LLVM via KyleMayes/install-llvm-action@v2 and set LIBCLA…
Copilot Apr 7, 2026
1a68cb9
ci: fix double space in test.yml Test run step (triggers CI re-run wi…
Copilot Apr 7, 2026
0bfb4e9
fix: exclude test_header2rdl from i686 CI (64-bit libclang.dll not lo…
Copilot Apr 7, 2026
3864e26
header2rdl: emit primitive typedefs as #[typedef] structs
Copilot Apr 8, 2026
610ab2a
header2rdl: extract anonymous nested structs as named types
Copilot Apr 8, 2026
bcef58a
header2rdl: use numeric suffix naming for anonymous nested types (OUT…
Copilot Apr 8, 2026
00fd8ba
placeholder for planning
Copilot Apr 8, 2026
7cfa92e
Add #[typedef] pseudo-attr, roundtrip RDL validation in test_header2r…
Copilot Apr 8, 2026
3e7d4ae
Fix cargo fmt: expand RdlStruct struct literal in lib.rs
Copilot Apr 8, 2026
80a90c8
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 8, 2026
88dc05b
Simplify convert test, surface libclang errors, add panic.rs for test…
Copilot Apr 8, 2026
27fe1cd
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 8, 2026
204963b
Add file/line/col formatting to libclang errors, update panic test ex…
Copilot Apr 8, 2026
a2773a9
Fix cargo fmt: collapse path join onto single line in panic.rs
Copilot Apr 8, 2026
8b3f159
retrigger checks
kennykerr Apr 8, 2026
eff4023
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 8, 2026
ce96608
Quote file path in error messages so VS Code auto-links work for path…
Copilot Apr 8, 2026
ec9c90c
Normalize file path in error messages to remove redundant separators …
Copilot Apr 8, 2026
b02cdaa
Improve comment explaining the path normalization fix
Copilot Apr 8, 2026
5e4fb92
Remove quotes from error path: use plain `path:line:col` format (rust…
Copilot Apr 8, 2026
a293890
Refactor Converter API to match cc::Build style (file/files/AsRef<Pat…
Copilot Apr 9, 2026
7ce7fff
Fix forward-declaration typedefs blocking COM interface collection; a…
Copilot Apr 9, 2026
b2dadba
Hoist seen.insert out of duplicated branches in collect_typedef
Copilot Apr 9, 2026
005a6d8
Merge branch 'master' into copilot/parse-win32-headers-to-rdl
kennykerr Apr 9, 2026
f1420c2
fmt
kennykerr Apr 9, 2026
7cd9929
Update midl.h to use #include <windows.h> via self-contained shim; ad…
Copilot Apr 9, 2026
10f967b
midl.h: use real Windows SDK windows.h, remove custom shim
Copilot Apr 9, 2026
a580063
Read INCLUDE env var for Windows SDK headers; skip midl.h on non-Windows
Copilot Apr 9, 2026
6d55be1
fix-environment: set INCLUDE for aarch64 so windows.h resolves on ARM…
Copilot Apr 9, 2026
113bf08
fix-environment: add break to aarch64 block to prevent "* " case from…
Copilot Apr 9, 2026
090f5d1
midl.rdl: update golden to match Windows output (HRESULT return type,…
Copilot Apr 9, 2026
17ae273
fix: set complete INCLUDE in fix-environment wildcard case so windows…
Copilot Apr 9, 2026
a435365
feat: Linux cross-parse of WebView2.h with windows.h shim and GUID ex…
Copilot Apr 10, 2026
df79246
fix: code review improvements - parse_guid_str comment, emit_interfac…
Copilot Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/actions/fix-environment/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,20 @@ runs:
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.26100.0\arm64" >> $env:GITHUB_PATH
((Resolve-Path "$vs_root\VC\Tools\MSVC\*\bin\Hostx64\arm64")
| Sort-Object -Descending | Select -First 1).ToString() >> $env:GITHUB_PATH
$msvc_inc = ((Resolve-Path "$vs_root\VC\Tools\MSVC\*\include")
| Sort-Object -Descending | Select-Object -First 1).ToString()
$wk = "${env:ProgramFiles(x86)}\Windows Kits\10\include\10.0.26100.0"
"INCLUDE=$msvc_inc;$wk\ucrt;$wk\um;$wk\shared;$wk\winrt;$wk\cppwinrt" >> $env:GITHUB_ENV
break
}
"*"
{
(Join-Path $env:GITHUB_WORKSPACE "target\debug\deps").ToString() >> $env:GITHUB_PATH
(Join-Path $env:GITHUB_WORKSPACE "target\test\debug\deps").ToString() >> $env:GITHUB_PATH
"INCLUDE=${env:ProgramFiles(x86)}\Windows Kits\10\include\10.0.26100.0\winrt;${env:ProgramFiles(x86)}\Windows Kits\10\include\10.0.26100.0\cppwinrt" `
$msvc_inc = ((Resolve-Path "$vs_root\VC\Tools\MSVC\*\include")
| Sort-Object -Descending | Select-Object -First 1).ToString()
$wk = "${env:ProgramFiles(x86)}\Windows Kits\10\include\10.0.26100.0"
"INCLUDE=$msvc_inc;$wk\ucrt;$wk\um;$wk\shared;$wk\winrt;$wk\cppwinrt" `
>> $env:GITHUB_ENV
}
}
Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,25 @@ jobs:
host: x86_64-pc-windows-msvc
target: x86_64-pc-windows-msvc
runner: windows-2025
extra_excludes: ""
- name: nightly i686
version: nightly
host: x86_64-pc-windows-msvc
target: i686-pc-windows-msvc
runner: windows-2025
extra_excludes: "--exclude test_header2rdl"
- name: nightly gnu
version: nightly
host: x86_64-pc-windows-gnu
target: x86_64-pc-windows-gnu
runner: windows-2025
extra_excludes: ""
- name: stable arm64
version: stable
host: aarch64-pc-windows-msvc
target: aarch64-pc-windows-msvc
runner: windows-11-arm
extra_excludes: ""

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

Expand All @@ -65,8 +69,15 @@ jobs:
uses: ./.github/actions/fix-environment
with:
target: ${{ matrix.target }}
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: "18"
- name: Set LIBCLANG_PATH
run: echo "LIBCLANG_PATH=${{ env.LLVM_PATH }}/bin" >> "$GITHUB_ENV"
shell: bash
- name: Test
run: cargo test --all --target ${{ matrix.target }} --exclude windows_aarch64_gnullvm --exclude windows_aarch64_msvc --exclude windows_i686_gnu --exclude windows_i686_gnullvm --exclude windows_i686_msvc --exclude windows_x86_64_gnu --exclude windows_x86_64_gnullvm --exclude windows_x86_64_msvc
run: cargo test --all --target ${{ matrix.target }} --exclude windows_aarch64_gnullvm --exclude windows_aarch64_msvc --exclude windows_i686_gnu --exclude windows_i686_gnullvm --exclude windows_i686_msvc --exclude windows_x86_64_gnu --exclude windows_x86_64_gnullvm --exclude windows_x86_64_msvc ${{ matrix.extra_excludes }}
- name: Check diff
shell: bash
run: |
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"crates/tests/misc/*",
"crates/tests/winrt/*",
"crates/tests/libs/*",
"crates/tests/tools/*",
"crates/tools/*",
]
exclude = [
Expand Down
2 changes: 1 addition & 1 deletion crates/libs/rdl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![doc = include_str!("../readme.md")]

mod error;
mod formatter;
pub mod formatter;
mod reader;
mod writer;

Expand Down
17 changes: 17 additions & 0 deletions crates/libs/rdl/src/reader/attribute_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,23 @@ impl Encoder<'_> {
continue;
}

// `#[typedef]` is a pseudo-attribute that maps to the Win32 metadata
// `Windows.Win32.Foundation.Metadata.NativeTypedefAttribute`.
if path.is_ident("typedef") {
if !matches!(attr.meta, syn::Meta::Path(_)) {
return self.err(attr, "`#[typedef]` does not accept arguments");
}
let attr_ref = AttributeRef {
type_name: metadata::TypeName::named(
"Windows.Win32.Foundation.Metadata",
"NativeTypedefAttribute",
),
args: vec![],
};
self.encode_named_attribute(has_attribute, &attr_ref);
continue;
}

let attr_ref = self.resolve_attribute_ref(attr)?;
self.encode_named_attribute(has_attribute, &attr_ref);
}
Expand Down
7 changes: 7 additions & 0 deletions crates/libs/rdl/src/writer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,13 @@ fn write_custom_attributes_except<'a>(
!namespace_starts_with(attr.namespace(), "System") && !exclude.contains(&attr.name())
})
.map(|attr| {
// `NativeTypedefAttribute` is written back as the `#[typedef]` pseudo-attribute.
if attr.namespace() == "Windows.Win32.Foundation.Metadata"
&& attr.name() == "NativeTypedefAttribute"
{
return Ok(quote! { #[typedef] });
}

let attr_ns = attr.namespace();
let attr_short = attr
.name()
Expand Down
15 changes: 15 additions & 0 deletions crates/tests/tools/header2rdl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "test_header2rdl"
version = "0.0.0"
edition = "2021"
publish = false

[lib]
doc = false
doctest = false

[dependencies]
tool_header2rdl = { path = "../../../tools/header2rdl" }

[lints]
workspace = true
1 change: 1 addition & 0 deletions crates/tests/tools/header2rdl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Integration tests are in tests/
95 changes: 95 additions & 0 deletions crates/tests/tools/header2rdl/tests/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/// For each `tests/*.h` file, run the `tool_header2rdl` converter and write
/// the output back to the matching `tests/*.rdl` file in the source tree.
///
/// Validation relies on `git diff`: if the tool output changes the committed
/// `.rdl` file the CI diff check will fail. To regenerate the golden files,
/// simply run this test and commit the result.
///
/// A `tests/<name>.h.args` sidecar file may supply extra options
/// (whitespace-separated): supported tokens are `--cpp`, `--library NAME`,
/// `--include PATH` (resolved relative to the `tests/` directory),
/// `--system-include PATH` (resolved relative to `tests/`; adds the directory
/// as a system include so types defined there are NOT emitted in the RDL), and
/// `--windows-only` (skip the test on non-Windows platforms).
/// All tests use `namespace("Test")` by default.
#[test]
fn convert() {
let tests_dir = std::path::Path::new("tests");

let mut headers: Vec<_> = std::fs::read_dir(tests_dir)
.expect("tests directory not found – run tests from the crate root")
.flatten()
.filter_map(|e| {
let p = e.path();
if p.extension().is_some_and(|ext| ext == "h") {
Some(p)
} else {
None
}
})
.collect();
headers.sort();

for h_path in &headers {
let stem = h_path.file_stem().unwrap().to_string_lossy();

let mut c = tool_header2rdl::converter();
c.file(h_path).namespace("Test");

// Load optional sidecar options from `<name>.h.args`.
let sidecar = h_path.with_extension("h.args");
let mut windows_only = false;
if sidecar.exists() {
let content = std::fs::read_to_string(&sidecar)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", sidecar.display()));
let mut tokens = content.split_whitespace();
while let Some(token) = tokens.next() {
match token {
"--cpp" => {
c.cpp(true);
}
"--library" => {
let name = tokens.next().unwrap_or_else(|| {
panic!("`--library` requires a name in {}", sidecar.display())
});
c.library(name);
}
"--include" => {
let path = tokens.next().unwrap_or_else(|| {
panic!("`--include` requires a path in {}", sidecar.display())
});
c.include(tests_dir.join(path));
}
"--system-include" => {
let path = tokens.next().unwrap_or_else(|| {
panic!(
"`--system-include` requires a path in {}",
sidecar.display()
)
});
c.system_include(tests_dir.join(path));
}
"--windows-only" => {
windows_only = true;
}
other => panic!(
"unrecognised sidecar token `{other}` in {}",
sidecar.display()
),
}
}
}

if windows_only && !cfg!(target_os = "windows") {
continue;
}

let rdl = c
.convert()
.unwrap_or_else(|e| panic!("convert failed for {stem}.h: {e}"));

let rdl_path = tests_dir.join(format!("{stem}.rdl"));
std::fs::write(&rdl_path, &rdl)
.unwrap_or_else(|e| panic!("failed to write {}: {e}", rdl_path.display()));
}
}
12 changes: 12 additions & 0 deletions crates/tests/tools/header2rdl/tests/enum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
typedef enum _STATUS {
STATUS_IDLE = 0,
STATUS_RUNNING = 1,
STATUS_ERROR = -1,
} STATUS;

typedef enum _FLAGS {
FLAG_NONE = 0,
FLAG_READ = 1,
FLAG_WRITE = 2,
FLAG_EXECUTE = 4,
} FLAGS;
16 changes: 16 additions & 0 deletions crates/tests/tools/header2rdl/tests/enum.rdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[win32]
mod Test {
#[repr(i32)]
enum STATUS {
STATUS_IDLE = 0,
STATUS_RUNNING = 1,
STATUS_ERROR = -1,
}
#[repr(i32)]
enum FLAGS {
FLAG_NONE = 0,
FLAG_READ = 1,
FLAG_WRITE = 2,
FLAG_EXECUTE = 4,
}
}
8 changes: 8 additions & 0 deletions crates/tests/tools/header2rdl/tests/fn.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
typedef long HRESULT;
typedef unsigned int DWORD;
typedef void* HANDLE;

int Add(int a, int b);
HRESULT CreateHandle(DWORD flags, HANDLE* out);
void CloseHandle(HANDLE handle);
DWORD GetCount(const void* data, DWORD length);
1 change: 1 addition & 0 deletions crates/tests/tools/header2rdl/tests/fn.h.args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--library test
23 changes: 23 additions & 0 deletions crates/tests/tools/header2rdl/tests/fn.rdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[win32]
mod Test {
#[typedef]
struct HRESULT {
value: i32,
}
#[typedef]
struct DWORD {
value: u32,
}
#[typedef]
struct HANDLE {
value: *mut u8,
}
#[library("test")]
extern fn Add(a: i32, b: i32) -> i32;
#[library("test")]
extern fn CreateHandle(flags: DWORD, out: *mut HANDLE) -> HRESULT;
#[library("test")]
extern fn CloseHandle(handle: HANDLE);
#[library("test")]
extern fn GetCount(data: *const u8, length: DWORD) -> DWORD;
}
4 changes: 4 additions & 0 deletions crates/tests/tools/header2rdl/tests/include/oaidl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once
/* Shim for <oaidl.h> — pulled in by some COM headers; nothing from here
* is needed for WebView2, so we just include objidl.h. */
#include <objidl.h>
61 changes: 61 additions & 0 deletions crates/tests/tools/header2rdl/tests/include/objidl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once
/* Shim for <objidl.h> — provides ISequentialStream and IStream. */
#include <unknwn.h>

/* -------------------------------------------------------------------------
* ISequentialStream
* ---------------------------------------------------------------------- */
#ifndef __ISequentialStream_INTERFACE_DEFINED__
#define __ISequentialStream_INTERFACE_DEFINED__
MIDL_INTERFACE("0c733a30-2a1c-11ce-ade5-00aa0044773d")
ISequentialStream : public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE Read(
void *pv, ULONG cb, ULONG *pcbRead) = 0;
virtual HRESULT STDMETHODCALLTYPE Write(
const void *pv, ULONG cb, ULONG *pcbWritten) = 0;
};
#endif /* __ISequentialStream_INTERFACE_DEFINED__ */

/* -------------------------------------------------------------------------
* STATSTG
* ---------------------------------------------------------------------- */
typedef struct tagSTATSTG {
LPWSTR pwcsName;
DWORD type;
UINT64 cbSize;
struct { DWORD dwLowDateTime; DWORD dwHighDateTime; } mtime;
struct { DWORD dwLowDateTime; DWORD dwHighDateTime; } ctime;
struct { DWORD dwLowDateTime; DWORD dwHighDateTime; } atime;
DWORD grfMode;
DWORD grfLocksSupported;
CLSID clsid;
DWORD grfStateBits;
DWORD reserved;
} STATSTG;

/* -------------------------------------------------------------------------
* IStream
* ---------------------------------------------------------------------- */
#ifndef __IStream_INTERFACE_DEFINED__
#define __IStream_INTERFACE_DEFINED__
MIDL_INTERFACE("0000000c-0000-0000-C000-000000000046")
IStream : public ISequentialStream {
public:
virtual HRESULT STDMETHODCALLTYPE Seek(
long long dlibMove, DWORD dwOrigin, UINT64 *plibNewPosition) = 0;
virtual HRESULT STDMETHODCALLTYPE SetSize(UINT64 libNewSize) = 0;
virtual HRESULT STDMETHODCALLTYPE CopyTo(
IStream *pstm, UINT64 cb,
UINT64 *pcbRead, UINT64 *pcbWritten) = 0;
virtual HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags) = 0;
virtual HRESULT STDMETHODCALLTYPE Revert() = 0;
virtual HRESULT STDMETHODCALLTYPE LockRegion(
UINT64 libOffset, UINT64 cb, DWORD dwLockType) = 0;
virtual HRESULT STDMETHODCALLTYPE UnlockRegion(
UINT64 libOffset, UINT64 cb, DWORD dwLockType) = 0;
virtual HRESULT STDMETHODCALLTYPE Stat(
STATSTG *pstatstg, DWORD grfStatFlag) = 0;
virtual HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm) = 0;
};
#endif /* __IStream_INTERFACE_DEFINED__ */
3 changes: 3 additions & 0 deletions crates/tests/tools/header2rdl/tests/include/unknwn.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once
/* Shim for <unknwn.h> — pulls in windows.h which defines IUnknown */
#include <windows.h>
Loading
Loading