From 23b8fc9d183554468eab87f06bd36d305b9ab072 Mon Sep 17 00:00:00 2001
From: samypr100 <3933065+samypr100@users.noreply.github.com>
Date: Mon, 1 Dec 2025 15:02:35 -0500
Subject: [PATCH] Add a Windows manifest to uv binaries (#16894)
## Summary
Currently we do not include a Windows manifest on the uv binary for
windows builds. This can cause problems such as the one in
https://github.com/astral-sh/uv/issues/16877 which can limit what uv can
do for some Windows operations (e.g. symlinks) that can have
restrictions imposed by the OS unbeknownst to us and make it none
obvious to isolate the issue.
Given we already do this for the `uv-trampoline`, we should also do it
for uv. In the case of uv, I opted for explicit entries in the manifest
rather than using the defaults embed_manifest crate provides which are
not appropriate in all general cases.
The manifest now includes declarations for:
* Explicit "system" codepage declaration to retain backwards compat with
previous uv releases. We should move to utf-8 codepage in the future to
align with `uv-trampoline`, but it's arguably a breaking change in rare
cases. We shouldn't have issues with using utf-8 as we don't really rely
on *A calls to begin with.
* Explicit Windows 10+ support to ensure the executables are not treated
as a legacy, preventing application compatibility layers being wrongly
applied to it all the way back to NT 6.0 (Windows Vista). Note, other
Windows compatibility entries do not imply support, rather they imply
awareness as a preventive measure.
* Long Path support to avoid Windows operations assuming
[MAX_PATH](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation)
applies. This still requires the system to have long paths enabled via
``HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem@LongPathsEnabled``
dword being set to ``1`` (see
[ref](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation#registry-setting-to-enable-long-paths)).
* Standard invoker execution levels for CLI applications to disable UAC
virtualization after including the manifest.
The resulting manifest is the following
```xml
true
```
For reference, here's `cargo`'s manifest from 1.91
```xml
true
UTF-8
```
Closes #16877
## Test Plan
Before changes on Windows 11 25H2 (without
SeCreateSymbolicLinkPrivilege)
```console
$ uv init
$ uv add jupyterlab-widgets==3.0.16 --link-mode=symlink
...
Resolved 2 packages in [TIME]
error: Failed to install: jupyterlab_widgets-3.0.16-py3-none-any.whl (jupyterlab-widgets==3.0.16)
Caused by: failed to symlink file from [CACHE_DIR]\archive-v0\aQcqEjLJAkVwuSzohqymc\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js to [ROOT]\.venv\Lib\site-packages\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js: A required privilege is not held by the client. (os error 1314)
```
Before changes on Windows 11 25H2 (with SeCreateSymbolicLinkPrivilege)
```console
$ uv init
$ uv add jupyterlab-widgets==3.0.16 --link-mode=symlink
...
Resolved 2 packages in [TIME]
error: Failed to install: jupyterlab_widgets-3.0.16-py3-none-any.whl (jupyterlab-widgets==3.0.16)
Caused by: failed to symlink file from [CACHE_DIR]\archive-v0\aQcqEjLJAkVwuSzohqymc\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js to [ROOT]\.venv\Lib\site-packages\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js: The parameter is incorrect. (os error 87)
```
After changes on Windows 11 25H2 (with or without
SeCreateSymbolicLinkPrivilege)
```console
$ uv init
$ uv add jupyterlab-widgets==3.0.16 --link-mode=symlink
...
Resolved 2 packages in [TIME]
Installed 1 package in [TIME]
+ jupyterlab-widgets==3.0.16
```
---
Cargo.lock | 7 +++++++
Cargo.toml | 1 +
crates/uv/Cargo.toml | 5 +++++
crates/uv/build.rs | 39 +++++++++++++++++++++++++++++++++++++++
4 files changed, 52 insertions(+)
create mode 100644 crates/uv/build.rs
diff --git a/Cargo.lock b/Cargo.lock
index 74ad83677..a589d8f13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1317,6 +1317,12 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+[[package]]
+name = "embed-manifest"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94cdc65b1cf9e871453ce2f86f5aaec24ff2eaa36a1fa3e02e441dddc3613b99"
+
[[package]]
name = "encode_unicode"
version = "1.0.0"
@@ -5398,6 +5404,7 @@ dependencies = [
"ctrlc",
"dotenvy",
"dunce",
+ "embed-manifest",
"filetime",
"flate2",
"fs-err",
diff --git a/Cargo.toml b/Cargo.toml
index dc4129981..66afd27b3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -107,6 +107,7 @@ dotenvy = { version = "0.15.7" }
dunce = { version = "1.0.5" }
either = { version = "1.13.0" }
encoding_rs_io = { version = "0.1.7" }
+embed-manifest = { version = "1.5.0" }
etcetera = { version = "0.11.0" }
fastrand = { version = "2.3.0" }
flate2 = { version = "1.0.33", default-features = false, features = ["zlib-rs"] }
diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml
index b61756e36..30b61a8bc 100644
--- a/crates/uv/Cargo.toml
+++ b/crates/uv/Cargo.toml
@@ -121,6 +121,11 @@ arrayvec = { workspace = true }
self-replace = { workspace = true }
windows = { workspace = true }
+[build-dependencies]
+uv-version = { workspace = true }
+
+embed-manifest = { workspace = true }
+
[dev-dependencies]
uv-publish = { workspace = true, features = ["test"] }
diff --git a/crates/uv/build.rs b/crates/uv/build.rs
new file mode 100644
index 000000000..8a7fb8843
--- /dev/null
+++ b/crates/uv/build.rs
@@ -0,0 +1,39 @@
+//! This embeds a "manifest" - a special XML document - into the uv binary on Windows builds.
+//!
+//! It includes reasonable defaults for Windows binaries:
+//! - `System` codepage to retain backwards compatibility with previous uv releases.
+//! We can set to the utf-8 codepage in a future breaking release which lets us use the
+//! *A versions of Windows API functions without utf-16 conversion.
+//! - Long path awareness allows paths longer than 260 characters in Windows operations.
+//! This still requires `LongPathsEnabled` to be set in the Windows registry.
+//! - Declared Windows 7-10+ compatibility to avoid legacy compat layers from being potentially
+//! applied by not specifying any. This does not imply actual Windows 7, 8.0, 8.1 support. In
+//! cases where the application is run on Windows 7, the app is treated as Windows 7 aware
+//! rather than an unspecified legacy application (e.g. Windows XP).
+//! - Standard invoker execution levels for CLI applications to disable UAC virtualization.
+//!
+//! See
+use embed_manifest::manifest::{ActiveCodePage, ExecutionLevel, Setting, SupportedOS};
+use embed_manifest::{embed_manifest, empty_manifest};
+
+fn main() {
+ if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
+ let [major, minor, patch] = uv_version::version()
+ .splitn(3, '.')
+ .map(str::parse)
+ .collect::, _>>()
+ .ok()
+ .and_then(|v| v.try_into().ok())
+ .expect("uv version must be in x.y.z format");
+ let manifest = empty_manifest()
+ .name("uv")
+ .version(major, minor, patch, 0)
+ .active_code_page(ActiveCodePage::System)
+ // "Windows10" includes Windows 10 and 11, and Windows Server 2016, 2019 and 2022
+ .supported_os(SupportedOS::Windows7..=SupportedOS::Windows10)
+ .requested_execution_level(ExecutionLevel::AsInvoker)
+ .long_path_aware(Setting::Enabled);
+ embed_manifest(manifest).expect("unable to embed manifest");
+ }
+ println!("cargo:rerun-if-changed=build.rs");
+}