diff --git a/MODULE.bazel b/MODULE.bazel index 519387366b87..541065809512 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -105,6 +105,11 @@ single_version_override( # Temporary until rules_pkg > 1.2.0 is in BCR archive_override( module_name = "rules_pkg", + patch_strip = 1, + patches = [ + # Windows runfiles junctions cannot point at files; follow them on copy. + "//bazel/patches:rules_pkg-windows-junction-copy.patch", + ], sha256 = "9923c6c8855b153f837a953760159908507f571b4b77cb24ed862dda1de20f90", strip_prefix = "rules_pkg-401969d4367c42dcbb45d33a637eae87788d025e", urls = ["https://github.com/bazelbuild/rules_pkg/archive/401969d4367c42dcbb45d33a637eae87788d025e.tar.gz"], # main as of April 22, 2026 diff --git a/bazel/AGENTS.md b/bazel/AGENTS.md index 15d985d2acf2..e1d021020068 100644 --- a/bazel/AGENTS.md +++ b/bazel/AGENTS.md @@ -881,6 +881,14 @@ local and remote execution to be configured. All Windows developers are expected to have **Developer Mode enabled**, which grants the necessary privileges for symlink creation without administrator elevation. The `.bazelrc` sets `--enable_runfiles` accordingly. +**Runfiles file junctions.** `--enable_runfiles` builds the runfiles tree with directory junctions. Windows junctions +cannot point at files, so a file runfile shows up as a directory (`d----l`) and `open()` fails with `Permission +denied` / `The directory name is invalid`. `pkg_install` copies from that tree, not from the MANIFEST real path; +`bazel/patches/rules_pkg-windows-junction-copy.patch` makes the copier follow the reparse point. For generated trees +that must be reachable as a directory (not file-by-file), `copy_to_directory` so the runfiles entry is one directory +junction to a real directory of real files (see `//rtloader/test:dir_with_python_home`). Prefer the runfiles library +over constructing paths under `*.runfiles`. + **No sandbox.** Windows uses `--strategy=standalone`. Builds are less hermetic by default — undeclared dependencies that happen to be present locally will succeed locally and fail in CI or RBE. diff --git a/bazel/patches/rules_pkg-windows-junction-copy.patch b/bazel/patches/rules_pkg-windows-junction-copy.patch new file mode 100644 index 000000000000..13a0dd353a48 --- /dev/null +++ b/bazel/patches/rules_pkg-windows-junction-copy.patch @@ -0,0 +1,15 @@ +diff --git a/pkg/private/install.py.tpl b/pkg/private/install.py.tpl +index 5242b18a..00000000 100644 +--- a/pkg/private/install.py.tpl ++++ b/pkg/private/install.py.tpl +@@ -82,6 +82,10 @@ class NativeInstaller(object): + def _do_file_copy(self, src, dest): + logging.debug("COPY %s <- %s", dest, src) ++ # Windows --enable_runfiles uses directory junctions for every runfile. ++ # Junctions cannot point at files, so open() fails with Permission denied. ++ # Follow the reparse point to the real bazel-out file. ++ src = os.path.realpath(src) + # Copy to a temporary directory and then move it to the destination. + # This ensures code-signed executables on certain platforms + # behave correctly. + # See: https://developer.apple.com/documentation/security/updating-mac-software diff --git a/deps/cpython.BUILD.bazel b/deps/cpython.BUILD.bazel index 1ec93e380462..6eaf917f0cfb 100644 --- a/deps/cpython.BUILD.bazel +++ b/deps/cpython.BUILD.bazel @@ -401,6 +401,7 @@ pkg_filegroup( pkg_files( name = "install_files_win", srcs = [":python_win"], + strip_prefix = "build", ) # On Windows, python_win is built via MSBuild (not cc_shared_library), so there are no