Skip to content

Allow the creation of relative symlinks on Windows#24213

Draft
fmeum wants to merge 3 commits intobazelbuild:masterfrom
fmeum:14224-windows-absolute-symlinks
Draft

Allow the creation of relative symlinks on Windows#24213
fmeum wants to merge 3 commits intobazelbuild:masterfrom
fmeum:14224-windows-absolute-symlinks

Conversation

@fmeum
Copy link
Copy Markdown
Collaborator

@fmeum fmeum commented Nov 5, 2024

Fixes #14224

RELNOTES: Symlinks created via ctx.actions.symlink(..., target_path = "...") with --windows_enable_symlinks can now be relative on Windows.

@fmeum fmeum force-pushed the 14224-windows-absolute-symlinks branch from 075e5c5 to 7f91c3a Compare November 5, 2024 16:32
@fmeum fmeum changed the title Test creation of relative non-existent symlinks on Windows Allow the creation of relative symlinks on Windows Nov 6, 2024
@tomdegoede
Copy link
Copy Markdown
Contributor

Thanks for working on this!

I recently observed some weird behavior with relative symlinks created in pwsh.exe on windows. It went something like this:
a/b/c/external/file.x
a/b/c/out/x64/bin/link.x -> ../../../external/file.x
a/b/c/bin -> out/x64/bin
get-content bin/link.x : Error: a/external/file.x does not exist. (Ignoring the fact that link.x lives inside it symlink itself)

I'm not sure if this also works like this on linux and under which specific circumstances this occurs. Maybe the symlinks were made absolute for this reason. We have changed our PowerShell script to produce absolute symlinks (and disabled caching) to fix bazel-bin invocations.

@fmeum
Copy link
Copy Markdown
Collaborator Author

fmeum commented Nov 6, 2024

@meteorcloudy Do you happen to know what's up with relative symlinks on Windows? I can add more test cases.

@meteorcloudy
Copy link
Copy Markdown
Member

meteorcloudy commented Nov 6, 2024

I recently observed some weird behavior with relative symlinks created in pwsh.exe on windows. It went something like this:
a/b/c/external/file.x
a/b/c/out/x64/bin/link.x -> ../../../external/file.x
a/b/c/bin -> out/x64/bin
get-content bin/link.x : Error: a/external/file.x does not exist. (Ignoring the fact that link.x lives inside it symlink itself)

This is probably due to

if (OS.getCurrent() == OS.WINDOWS) {
// On Windows, symlinks are resolved differently.
// Given <external>/repo_foo/link,
// where <external>/repo_foo points to <vendor dir>/repo_foo in vendor mode
// and repo_foo/link points to a relative path ../bazel-external/repo_bar/data.
// Windows won't resolve `repo_foo` before resolving `link`, which causes
// <external>/repo_foo/link to be resolved to <external>/bazel-external/repo_bar/data
// To work around this, we create a symlink <external>/bazel-external -> <external>.
FileSystemUtils.ensureSymbolicLink(
externalRoot.getChild(VendorManager.EXTERNAL_ROOT_SYMLINK_NAME), externalRoot);
}

So given:

pcloudy@bazel-windows-playground:~/workdir/symlink_test
$ tree .
.
├── external
│   ├── bar -> /c/Users/pcloudy/workdir/symlink_test/vendor/bar
│   └── foo -> /c/Users/pcloudy/workdir/symlink_test/vendor/foo
└── vendor
    ├── bar
    │   └── file
    ├── external -> /c/Users/pcloudy/workdir/symlink_test/external
    └── foo
        └── link -> ../external/bar/file

We got different results based on which style of file path you use for cat (probably affects how symlinks in the path was resolved?).

pcloudy@bazel-windows-playground:~/workdir/symlink_test
$ cat c:/Users/pcloudy/workdir/symlink_test/external/foo/link
cat: 'c:/Users/pcloudy/workdir/symlink_test/external/foo/link': No such file or directory
pcloudy@bazel-windows-playground:~/workdir/symlink_test
$ cat /c/Users/pcloudy/workdir/symlink_test/external/foo/link
hi

So basically c:/Users/pcloudy/workdir/symlink_test/external/foo/link is resolved to

=> c:/Users/pcloudy/workdir/symlink_test/external/foo/../external/bar/file
=> c:/Users/pcloudy/workdir/symlink_test/external/external/bar/file ❌

but /c/Users/pcloudy/workdir/symlink_test/external/foo/link is resolved to

=> /c/Users/pcloudy/workdir/symlink_test/vendor/foo/link
=> /c/Users/pcloudy/workdir/symlink_test/vendor/foo/../external/bar/file
=> /c/Users/pcloudy/workdir/symlink_test/vendor/external/bar/file
=> /c/Users/pcloudy/workdir/symlink_test/external/bar/file
=> /c/Users/pcloudy/workdir/symlink_test/vendor/bar/file ✅

I think we might want to be a bit careful on opening up relative symlinks on Windows since they behave different due to this issue.

@KoltesDigital
Copy link
Copy Markdown

KoltesDigital commented Mar 10, 2025

@meteorcloudy if I understand correctly, with the case you described that works on PWSH, further actions that use MSYS2 but with PWSH-style paths would fail. What if paths are converted so that

  • in PWSH/Win32 API realm, paths are always like C:\...;
  • in MSYS2 realm, paths are always like /c/...?

@KoltesDigital
Copy link
Copy Markdown

@pauldraper years ago you mentioned running ln to bypass this limitation. Are you still using this trick? Have you got into the aforementioned issue of link resolution being different?

@github-actions
Copy link
Copy Markdown

Thank you for contributing to the Bazel repository! This pull request has been marked as stale since it has not had any activity in the last 30 days. It will be closed in the next 30 days unless any other activity occurs. If you think this PR is still relevant and should stay open, please post any comment here and the PR will no longer be marked as stale.

@github-actions github-actions bot added the stale Issues or PRs that are stale (no activity for 30 days) label Feb 18, 2026
@fmeum fmeum added not stale Issues or PRs that are inactive but not considered stale and removed stale Issues or PRs that are stale (no activity for 30 days) labels Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

not stale Issues or PRs that are inactive but not considered stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ctx.actions.symlink doesn't make relative symlink, contrary to docs

4 participants