Skip to content
Open
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
21 changes: 18 additions & 3 deletions bindings/js/src/com_completion_test_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,15 +652,30 @@ unsafe extern "system" fn result_u32(this: *mut c_void, output: *mut u32) -> HRE
}

macro_rules! stub {
($name:ident($($arg:ident: $typ:ty),*)) => {
($name:ident($($arg:ident: $typ:ty),* $(,)?)) => {
unsafe extern "system" fn $name(_this: *mut c_void, $($arg: $typ),*) -> HRESULT {
E_NOTIMPL
}
};
}
stub!(audio_initialize(_mode: AUDCLNT_SHAREMODE, _flags: u32, _duration: i64, _period: i64, _format: *const WAVEFORMATEX, _session: *const GUID));
stub!(
audio_initialize(
_mode: AUDCLNT_SHAREMODE,
_flags: u32,
_duration: i64,
_period: i64,
_format: *const WAVEFORMATEX,
_session: *const GUID,
)
);
stub!(audio_i64(_value: *mut i64));
stub!(audio_format_supported(_mode: AUDCLNT_SHAREMODE, _format: *const WAVEFORMATEX, _closest: *mut *mut WAVEFORMATEX));
stub!(
audio_format_supported(
_mode: AUDCLNT_SHAREMODE,
_format: *const WAVEFORMATEX,
_closest: *mut *mut WAVEFORMATEX,
)
);
stub!(audio_mix_format(_format: *mut *mut WAVEFORMATEX));
stub!(audio_period(_default: *mut i64, _minimum: *mut i64));
stub!(audio_noargs());
Expand Down
6 changes: 6 additions & 0 deletions tools/dynwinrt-codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ complete output. Python module components longer than 120 characters are
shortened with a stable readable prefix and hash suffix while public type names
remain unchanged.

That component budget does not constrain an arbitrary output root. On Windows,
Python generation warns once about final `.py`/`.pyi` paths of 260 or more
UTF-16 code units, without rejecting generation or changing generated bytes.
See [Python short-path guidance](python/README.md#windows-path-length) for
import, type-checking, build, and installation considerations.

Implementation helper names are allocated alongside metadata types and follow
their owning interfaces through incremental renames. Python's package-level
root exports drop an interface and its helpers when its short name becomes
Expand Down
70 changes: 70 additions & 0 deletions tools/dynwinrt-codegen/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,76 @@ closed identity, even when their local projection names differ.
Python module components longer than 120 characters are shortened with a stable
readable prefix and hash suffix while public type names remain unchanged.

### Windows path length

A short module name does not guarantee a short **absolute** path. In deep
checkouts, codegen can successfully write files that ordinary Python imports,
mypy, or pip cannot open. CPython supports long paths on appropriately
configured Windows; see the [Python Windows guide](https://docs.python.org/3.11/using/windows.html#removing-the-max-path-limitation)
and [Windows long-path requirements](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation).
Extended `\\?\` paths used by the generator do not automatically make downstream
tools long-path compatible; the Windows `LongPathsEnabled` setting alone does
not prove every consumer supports them.

On Windows, the generator emits one stderr warning before publishing output
when its longest final `.py`/`.pyi` path reaches 260 UTF-16 code units (the
legacy limit allows 259, excluding the terminating NUL). The warning names
that file, measures its path without the extended prefix, and states how much
shorter the output root needs to be. Relative `--output` paths are resolved
from the current directory; temporary transactional staging paths are not
counted. `--dry-run` checks the requested projection's planned source paths,
aggregating all selected namespaces into one warning; `--no-pyi` excludes
stubs. This is a **compatibility risk diagnostic**:
generation still succeeds, and module names, imports, layout, and bytes are
unchanged.

Prefer a shorter checkout such as `C:\src\dynwinrt`, or generate directly into
a short root and run consumers there:

```powershell
dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri `
--lang py --output C:\g\generated_uri
Set-Location C:\g
python -c "from generated_uri.windows.foundation import Uri; print(Uri)"
python -m mypy --strict .\consumer.py
python -m pip install .\generated_uri
```

Here `consumer.py` is your application or typing fixture beside the generated
package. Leave additional headroom for pip's build staging and the destination
venv/site-packages path. Those paths cannot all be predicted from `--output`;
no warning is not a guarantee that a later build or deep-venv install will work.
Keep the venv short as well, for example `C:\g\venv`.

For an existing deep source tree, an optional, user-owned short junction can
expose the **same generated bytes** without renaming modules or copying code:

```powershell
New-Item -ItemType Directory -Path C:\g -Force | Out-Null
New-Item -ItemType Junction -Path C:\g\project -Target C:\src\deep\project
Set-Location C:\g\project
dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri `
--lang py --output .\generated
python -c "from generated.windows.foundation import Uri; print(Uri)"
python -m mypy --strict .\consumer.py
python -m pip install .\generated
Set-Location C:\g
Remove-Item -LiteralPath C:\g\project
```

Link the parent checkout, not the generated directory itself: transactional
generation rejects a linked output directory. Use the **same shortened path**
for generation, imports, mypy, and package builds; returning to the deep path
reintroduces its limits. Remove only the junction you created, without
recursively deleting its target.

Alternatively, map an unused drive for the session with
`subst.exe R: C:\src\deep\project`, use `R:\` consistently for these commands,
and remove only that mapping with `subst.exe R: /D` after leaving the drive.
These short-path remedies have been validated with Python imports, strict
mypy, and generated-package installation on Windows with long paths disabled;
they do not automatically fix arbitrary deep build or install destinations.

## Links

- [`dynwinrt` runtime on PyPI](https://pypi.org/project/dynwinrt/)
Expand Down
46 changes: 42 additions & 4 deletions tools/dynwinrt-codegen/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

mod python_output_paths;
mod win32_census;
mod win32_output;

Expand Down Expand Up @@ -535,6 +536,7 @@ fn run() -> Result<(), String> {
remove_all_generated_python_stubs(output_dir)?;
}

let mut python_namespace_summary = None;
if let Some(ref cls_arg) = class_name {
let class_requests = parse_class_requests(cls_arg, namespace.as_deref())?;

Expand Down Expand Up @@ -962,6 +964,7 @@ fn run() -> Result<(), String> {
let (_, _, _, shared_interfaces) = generate_for_types(
&winmd,
output_dir,
final_output_dir,
classes.clone(),
implicit_interfaces.clone(),
Vec::new(),
Expand Down Expand Up @@ -1116,6 +1119,7 @@ fn run() -> Result<(), String> {
generate_for_types(
&winmd,
output_dir,
final_output_dir,
selected_classes,
selected_interfaces,
selected_enums,
Expand Down Expand Up @@ -1210,6 +1214,8 @@ fn run() -> Result<(), String> {
"Done. {} class(es) + {} interface(s) + {} enum(s) validated (dry run)",
total_classes, total_interfaces, total_enums,
);
} else if lang == "py" {
python_namespace_summary = Some((total_classes, total_interfaces, total_enums));
} else {
println!(
"Done. {} class(es) + {} interface(s) + {} enum(s) generated in {}",
Expand All @@ -1223,11 +1229,18 @@ fn run() -> Result<(), String> {

if lang == "py" && !dry_run {
write_python_package_manifest(output_dir, final_output_dir)?;
write_python_generated_inventory(output_dir, pyi)?;
let files = write_python_generated_inventory(output_dir, pyi)?;
python_output_paths::warn(final_output_dir, &files)?;
}
if let Some(transaction) = output_transaction.take() {
transaction.commit()?;
}
if let Some((classes, interfaces, enums)) = python_namespace_summary {
println!(
"Done. {classes} class(es) + {interfaces} interface(s) + {enums} enum(s) generated in {}",
final_output_dir.display()
);
}
}
}
Ok(())
Expand All @@ -1238,6 +1251,7 @@ fn run() -> Result<(), String> {
fn generate_for_types(
winmd: &str,
output_dir: &Path,
final_output_dir: &Path,
classes: Vec<meta::ClassMeta>,
interfaces: Vec<meta::InterfaceMeta>,
enums: Vec<TypeMeta>,
Expand Down Expand Up @@ -1502,6 +1516,27 @@ fn generate_for_types(
} else {
None
};
if dry_run && let Some(context) = python_context.as_ref() {
let mut struct_interfaces = emittable_interfaces.clone();
struct_interfaces.extend_from_slice(&shared_interfaces);
let mut implementations =
python_type_identities(&all_classes, &struct_interfaces, &all_enums);
let mut public_types =
python_type_identities(&all_classes, &emittable_interfaces, &all_enums);
implementations
.retain(|identity| !python_type_shadowed_by_class(identity, &class_identities_all));
public_types
.retain(|identity| !python_type_shadowed_by_class(identity, &class_identities_all));
python_output_paths::warn(
final_output_dir,
&python_output_paths::dry_run_source_files(
context,
&implementations,
&public_types,
pyi,
),
)?;
}
let (delegate_signatures, delegate_sig_refs, delegate_param_wraps) =
if let Some(context) = javascript_context.as_ref() {
project::build_delegate_signatures(
Expand Down Expand Up @@ -6895,7 +6930,7 @@ fn clean_python_generated_output(output_dir: &Path) -> Result<(), String> {
Ok(())
}

fn write_python_generated_inventory(output_dir: &Path, pyi: bool) -> Result<(), String> {
fn write_python_generated_inventory(output_dir: &Path, pyi: bool) -> Result<Vec<PathBuf>, String> {
let files = collect_generated_python_files(output_dir, true, pyi)?;
let content = files
.iter()
Expand All @@ -6905,7 +6940,8 @@ fn write_python_generated_inventory(output_dir: &Path, pyi: bool) -> Result<(),
write_file(
&output_dir.join(PYTHON_GENERATED_INVENTORY),
&format!("{content}\n"),
)
)?;
Ok(files)
}

fn collect_generated_python_files(
Expand Down Expand Up @@ -10603,9 +10639,11 @@ mod tests {
..Default::default()
};

let output_dir = test_directory("python-struct-symbol-collision");
let error = generate_for_types(
"",
&test_directory("python-struct-symbol-collision"),
&output_dir,
&output_dir,
vec![class],
Vec::new(),
Vec::new(),
Expand Down
Loading
Loading