Skip to content

[bazel] Sandbox bash and git on Windows with tools/bazel.bat - #9341

Open
AustinSchuh wants to merge 16 commits into
wpilibsuite:mainfrom
AustinSchuh:bazel-windows-bash-sandbox
Open

[bazel] Sandbox bash and git on Windows with tools/bazel.bat#9341
AustinSchuh wants to merge 16 commits into
wpilibsuite:mainfrom
AustinSchuh:bazel-windows-bash-sandbox

Conversation

@AustinSchuh

@AustinSchuh AustinSchuh commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Bazelisk delegates to tools/bazel.bat on Windows, which lets us stop depending on whatever bash, sh, and git happen to be installed on the developer's machine. The wrapper downloads a private, SHA256 verified copy of PortableGit into %USERPROFILE%.cache\bazel\portable_git, points BAZEL_SH and PATH at it, and then clears the rest of the environment before starting Bazel so that stray variables can't leak into actions. It also shortens %USERPROFILE% to its 8.3 form so that a user name containing a space doesn't break paths downstream.

BAZEL_VC, BAZEL_VS, BAZEL_VC_FULL_VERSION, and BAZEL_WINSDK_FULL_VERSION survive the scrub so that a non-default Visual Studio install can still be pointed at explicitly.

.bat files are marked -text so that their CRLF line endings round-trip unchanged rather than being normalized on checkin.

Bazelisk delegates to tools/bazel.bat on Windows, which lets us stop
depending on whatever bash, sh, and git happen to be installed on the
developer's machine.  The wrapper downloads a private, SHA256 verified
copy of PortableGit into %USERPROFILE%\.cache\bazel\portable_git, points
BAZEL_SH, BAZEL_GIT, and GIT_BIN_PATH at it, and then clears the rest of
the environment before starting Bazel so that stray variables can't leak
into actions.  It also shortens %USERPROFILE% to its 8.3 form so that a
user name containing a space doesn't break paths downstream.

BAZEL_VC, BAZEL_VS, BAZEL_VC_FULL_VERSION, and BAZEL_WINSDK_FULL_VERSION
survive the scrub so that a non-default Visual Studio install can still
be pointed at explicitly.

.bat files are marked -text so that their CRLF line endings round-trip
unchanged rather than being normalized on checkin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
@AustinSchuh
AustinSchuh requested a review from a team as a code owner August 24, 2026 04:20

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0bafb24a05

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Stop on a failed PortableGit extraction.  The self-extractor's exit
status was ignored and then clobbered by the rmdir of the temp
directory, so an extraction that ran out of disk or got blocked by
security software still reported success and launched Bazel against a
missing or partial Git runtime.  Capture the status, sanity check that
git.exe and bash.exe actually landed, and delete the incomplete cache on
the way out so that the next invocation retries instead of trusting it.

Keep a few more variables through the scrub.  rules_cc finds vswhere.exe
under %ProgramFiles(x86)%, publishing_rule.bzl reads
WPI_PUBLISH_CLASSIFIER_FILTER straight out of the environment, and
nothing can fetch from behind a corporate proxy without HTTP_PROXY and
friends.

Comments inside parenthesized blocks now use rem rather than ::, since a
label inside a block is a cmd.exe parser hazard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe8186e32b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Key the cache directory by the pinned version.  The presence of
cmd\git.exe short circuited the whole download, so bumping the pin left
everyone who had already bootstrapped on the old release forever,
including after a security driven update.

Install through a staging directory private to the invocation and
publish it with a single rename.  Two Bazelisk invocations racing on a
cold cache used to share one temp directory and one destination, so they
could delete each other's download, extract over each other, or tear
down a cache the other had started using.  Nothing partial is ever
visible under the cache path now, and the loser of the race keeps the
winner's copy, which passed the same checksum and layout checks.

Turn delayed expansion back off before forwarding %* to Bazel.  A "!" in
any argument -- a remote cache header or a proxy password, say -- was
being eaten when the command line got reparsed.

Point git at nonexistent global and system config files.  HOME has to
keep pointing at the real profile because Bazel reads the user .bazelrc
from there, but that also handed the pinned git the developer's
~/.gitconfig, where core.autocrlf or url.*.insteadOf would make fetches
machine dependent.

Document the one deliberate hole in the scrub: the original PATH is
still appended behind the system and pinned-git directories, because
dropping it breaks anyone whose tools live elsewhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de5a0883dc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
An environment value containing "!" was being mangled on its way through
the save and restore, because delayed expansion was enabled for the
whole script and applies to whatever percent expansion just produced.
A proxy password like http://user:p!ss@proxy came out the other side
different from what the developer set, which is exactly the case the
proxy passthrough was added for.

Turn delayed expansion off instead of on, explicitly rather than by
inheritance so that a shell started with /V:ON or a machine with the
registry default flipped behaves the same, and move the two places that
needed to read a variable assigned in the same block into subroutines.
The separate scope that was wrapping the handoff is now redundant.

Key the git cache by the release tag and checksum rather than the
package version alone, so that a rebuild of the same upstream version --
v2.44.0.windows.1 to .2, say -- is picked up rather than being short
circuited by the existing install.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6
@auscompgeek

Copy link
Copy Markdown
Member

As much as we love batch scripts, would PowerShell be simpler?

@AustinSchuh

Copy link
Copy Markdown
Contributor Author

I'm not sure we get a choice. Bazel automatically loads //tools:bazel.bat. Unless I'm missing something.

@auscompgeek

Copy link
Copy Markdown
Member

Doesn't Bazelisk support tools\bazel.ps1?

@Gold856

Gold856 commented Aug 24, 2026

Copy link
Copy Markdown
Member

According to https://github.com/bazelbuild/bazelisk#toolsbazel, it does.

@AustinSchuh

Copy link
Copy Markdown
Contributor Author

Yea, it does. I didn't know it existed until you brought it up.

Digging in some more, the string quoting process is a fair amount easier with bazel.bat, and bazel.ps1 support was released the beginning of this year. Every argument which gets passed through with something like a space will need to be re-quoted. Those two things make me prefer the current bazel.bat, though I'm happy to convert it over.

Comment thread tools/bazel.bat Outdated
Comment thread tools/bazel.bat Outdated
Rewriting USERPROFILE to its 8.3 form unconditionally hands a mangled
path to everyone, including the vast majority of users whose profile
directory was never a problem -- 8.3 shortening applies to long names,
not just to spaces.  Do it only when there is a space to get rid of, and
warn when the volume has 8.3 name generation disabled and there is
nothing to fall back to.

Also write down why the environment scrub is worth doing when
--incompatible_strict_action_env already fixes the environment actions
run under: it doesn't cover repository rules, which read the client
environment in full.  rules_cc declares thirty-odd variables as inputs
to cc_autoconf and hands the whole environment to the subprocesses it
runs to locate MSVC, so anything that differs between two machines ends
up in local_config_cc and from there in every compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ee4c11143

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/bazel.bat
--experimental_strict_repo_env limits repository rules and module
extensions to PATH, PATHEXT, and the variables named by --repo_env,
which is what the wrapper's environment purge was there to approximate.
Bazel does it better: it is a whitelist rather than a blocklist, and it
is scoped to repository rules, so it doesn't take the environment away
from bazel run targets.  Losing credentials and the like out from under
a script that bazel run invokes is a real cost, and one that is hard to
connect back to a wrapper the user never sees.

So turn the flag on for Windows, name the variables the wrapper exports
in .bazelrc, and delete the save, purge, and restore -- about seventy
lines, along with its allowlist to keep up to date.  What is left is the
part Bazel can't do: pin a git and a bash, and put them at the front of
PATH, which is one of the two variables strict repo env still inherits.

Everything MSVC discovery reads has a documented fallback in
windows_cc_configure.bzl -- SYSTEMROOT and TMP to C:\Windows,
PROGRAMFILES(X86) to the standard location -- and setup_vc_env_vars
already runs vcvarsall.bat under an environment it builds itself, so
dropping the passthrough for those doesn't change how it resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67cb1725d9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/bazel.bat Outdated
GIT_CONFIG_GLOBAL and friends were exported by the wrapper, so every
process Bazel launches inherited them, `bazel run` targets included.
shared/bazel/copybara/run_copybara.py runs `bazel run //:copybara` with
a git destination URL it then pushes to, and pointing git at an empty
global config takes away both the credential helper it authenticates
with and the identity it commits as.

Move all three into --repo_env lines in .bazelrc, where they reach the
repository rules they were meant for and nothing else.  Same reason the
environment purge came out one commit ago: what a wrapper does to the
whole process tree is not what it looks like it is doing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1241b543e3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .bazelrc
publishing_rule.bzl declares WPI_PUBLISH_CLASSIFIER_FILTER as an environ
input and reads it out of the environment, and .bazelrc only sets it
explicitly under the ci configs.  So the shell variable is how a
developer picks a subset of the artifact variants locally, and turning
on --experimental_strict_repo_env stopped it from arriving on Windows.
An unset filter means no filter at all -- publishing.bzl falls back to
the full artifact set -- so the symptom is a much longer build rather
than an error.

Name only, so the value comes from the shell the way it does on Linux
and macOS.  CI is unaffected: it passes --config=ci on the command line,
which expands after the rc files and sets the variable explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@auscompgeek auscompgeek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Claude has left behind inaccurate comments.

Comment thread .bazelrc Outdated
Comment thread .bazelrc Outdated
Comment thread .bazelrc Outdated
AustinSchuh and others added 5 commits August 30, 2026 23:45
Neither variable is read by anything.  They appear nowhere in Bazel's
embedded tools, nowhere in Bazel's own source, and nowhere in any
external repository this build fetches; they came along from the wrapper
this was adapted from.  BAZEL_SH is the real one -- rules_shell's
sh_config.bzl and rules_cc's windows_cc_configure.bzl both read it --
and git is found through PATH, which strict repo env passes through.

Take the two suggested comment trims while here.  The claim that CI
relied on the --repo_env line in a bazelrc was wrong: the workflow
passes the classifier filter explicitly on the command line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
http.sslBackend=openssl and http.sslVerify=true came along from the
wrapper this was adapted from.  Nothing in this repository is fetched
over git, and where a git_repository would be the consumer Bazel
discards the variable before it ever runs git: _GIT_LOCAL_ENV_VARS in
tools/build_defs/repo/git_worker.bzl unsets GIT_CONFIG_PARAMETERS along
with the rest of `git rev-parse --local-env-vars`.

GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM stay.  Those aren't on that list,
so they still do what they're there for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b97eb0aba

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/bazel.bat Outdated
Comment thread .bazelrc
Comment thread tools/bazel.bat
Comment thread tools/bazel.bat Outdated
Move the pin from Git for Windows 2.44.0 to 2.55.0(5).  2.44.0 predates
the May 2024 clone fixes, CVE-2024-32002 among them, and since the
wrapper puts the pinned copy at the front of PATH it hands that git to
everything Bazel launches -- `bazel run //:copybara` clones with it --
and shadows a patched system git while doing so.  Checksum taken from
the release asset digest and confirmed by downloading it.

Invoke Bazelisk by name in the Windows CI jobs.  The runner image
installs a real bazel.exe through Chocolatey and Bazelisk through npm,
and the npm package provides a `bazel` binary of its own, so which one
`bazel` resolves to comes down to PATH order inside the image.  Only
Bazelisk dispatches tools/bazel.bat, so leaving that to chance means CI
may never exercise any of this.  Naming bazelisk settles it.

Stop filtering certutil's output by text.  A cache path containing
"CertUtil" would match the header line as well as the completion line,
and skip=1 would then discard the digest instead, leaving an empty hash
that fails the comparison every time.  Take the first line after the
header instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6
@github-actions github-actions Bot added the build Monorepo organization, Gradle/CMake/Bazel, upstream_utils, etc label Sep 3, 2026
Comment thread .github/workflows/bazel.yml Outdated
A real bazel.exe ignores .bazelversion entirely, so the runner image's
Bazel and Bazelisk are interchangeable right up until the wrapper matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkRkDjJmAEa54pYn3oWAN6
PeterJohnson pushed a commit that referenced this pull request Sep 5, 2026
See
#9341 (comment)

The `setup-bazel` action handles putting the output root in an
appropriate place, so we don't have to do it ourselves.

Also disable saving to GitHub Actions cache from PRs to avoid using up
the limited cache space.
…ndbox

setup-bazel now runs on every platform (wpilibsuite#9383), so Windows already goes
through Bazelisk and gets an output base on D: without the if statement
this branch had added.  Take main's workflow as-is.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
@github-actions github-actions Bot removed the build Monorepo organization, Gradle/CMake/Bazel, upstream_utils, etc label Sep 6, 2026
@auscompgeek auscompgeek linked an issue Sep 6, 2026 that may be closed by this pull request

@auscompgeek auscompgeek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The PR description needs updating though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bazel: Non WSL Bash required on Windows

3 participants