Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
26 changes: 26 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,32 @@ build --experimental_cc_static_library
build --experimental_cc_shared_library
build --incompatible_strict_action_env

# Actions get a fixed environment from the flag above, but repository rules and
# module extensions still see the client environment in full, and that is where
# the C++ toolchain gets configured -- rules_cc declares roughly thirty
# variables as inputs to cc_autoconf. Lock that down on Windows, where
# tools/bazel.bat pins the shell and git that the configuration goes looking
# for, and pass through only what the wrapper sets. Keep this list in sync with
# the wrapper: anything it exports for a repository rule has to be named here.
Comment thread
AustinSchuh marked this conversation as resolved.
Outdated
common:windows --experimental_strict_repo_env
Comment thread
AustinSchuh marked this conversation as resolved.
Comment thread
AustinSchuh marked this conversation as resolved.
common:windows --repo_env=BAZEL_SH
common:windows --repo_env=BAZEL_GIT
common:windows --repo_env=GIT_BIN_PATH
Comment thread
auscompgeek marked this conversation as resolved.
Outdated
# Escape hatches for a Visual Studio install outside the default location.
common:windows --repo_env=BAZEL_VC
common:windows --repo_env=BAZEL_VS
common:windows --repo_env=BAZEL_VC_FULL_VERSION
common:windows --repo_env=BAZEL_WINSDK_FULL_VERSION
# Keep the pinned git away from the developer's ~/.gitconfig, where settings
# like core.autocrlf or url.*.insteadOf would make a fetch machine dependent. A
# nonexistent config path reads as an empty config. This has to live here rather
# than in the wrapper: exported to every process Bazel launches it would also
# reach `bazel run //:copybara`, which needs the real configuration to find a
# credential helper and an identity to push with.
common:windows --repo_env=GIT_CONFIG_GLOBAL=/dev/null
common:windows --repo_env=GIT_CONFIG_SYSTEM=/dev/null
common:windows --repo_env="GIT_CONFIG_PARAMETERS='http.sslBackend=openssl' 'http.sslVerify=true'"

build --java_language_version=25
build --java_runtime_version=remotejdk_25
build --tool_java_language_version=25
Expand Down
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.adoc text eol=lf
*.bzl text eol=lf
*.bazel text eol=lf
*.bat -text
*.c text eol=lf
*.clang-format text eol=lf
*.clang-tidy text eol=lf
Expand Down
10 changes: 10 additions & 0 deletions README-Bazel.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ WPILib is normally built with Gradle, but [Bazel](https://www.bazel.build/) can
## Prerequisites
- Install [Bazelisk](https://github.com/bazelbuild/bazelisk/releases) and add it to your path. Bazelisk is a wrapper that will download the correct version of Bazel specified in the repository. Note: You can alias/rename the binary to `bazel` if you want to keep the familiar `bazel build` vs `bazelisk build` syntax.

### Windows
On Windows, Bazelisk hands off to `tools/bazel.bat` instead of running Bazel directly. That wrapper downloads a private copy of [PortableGit](https://github.com/git-for-windows/git/releases) into `%USERPROFILE%\.cache\bazel\portable_git\<release tag>-<checksum prefix>` (verified against a pinned SHA256), puts it at the front of `PATH`, and binds `BAZEL_SH`, `BAZEL_GIT`, and `GIT_BIN_PATH` to it. The build then gets the same `bash`, `sh`, and `git` on every machine rather than whichever ones happen to be installed.

Keeping the rest of your environment out of the build is Bazel's job rather than the wrapper's. Actions are covered by `--incompatible_strict_action_env`, and repository rules and module extensions by `--experimental_strict_repo_env`, which limits them to `PATH`, `PATHEXT`, and the variables named by the `--repo_env` lines in `.bazelrc`. Your environment is otherwise left alone, so `bazel run` targets still see it.

Consequences worth knowing about:
- You must invoke Bazel through Bazelisk. Running a `bazel.exe` binary directly skips the wrapper, and the build will fall back to whatever shell it can find.
- The first invocation on a machine spends a minute or so fetching and unpacking PortableGit. Later invocations reuse the cached copy.
- Set `BAZEL_VC` (or `BAZEL_VS`) if Visual Studio isn't installed in the default location. `.bazelrc` passes those through to the toolchain configuration; without a `--repo_env` line a variable will not reach it.

## Building
To build the entire repository, simply run `bazel build //...`. To run all of the unit tests, run `bazel test //...`
Other examples:
Expand Down
168 changes: 168 additions & 0 deletions tools/bazel.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
@echo off
:: Delayed expansion stays off for the whole script, explicitly rather than by
:: inheritance, so that a "!" in a path or in a forwarded Bazel argument is never
:: eaten while a line is reparsed. Nothing below may use "!var!".
setlocal disabledelayedexpansion

:: Bazelisk runs this wrapper instead of Bazel itself on Windows. It bootstraps
:: a private copy of PortableGit, which supplies both git and bash/sh, and puts
:: that in front of whatever is installed on the machine. Otherwise the shell
:: and git the build ends up using are whichever ones happen to be on PATH.
::
:: Keeping the rest of the environment out of the build is Bazel's job rather
:: than this script's. Actions are covered by --incompatible_strict_action_env,
:: and repository rules by --experimental_strict_repo_env, which limits them to
:: PATH, PATHEXT, and the variables named by --repo_env. Anything exported here
:: for a repository rule has to be listed in .bazelrc as well, or it won't
:: arrive.

:: A space in the profile directory breaks paths downstream, so fall back to the
:: short (8.3) form for those users. Only for those users: 8.3 names have
:: nothing to do with spaces otherwise, and there is no reason to hand everyone
:: else a mangled path.
if not "%USERPROFILE%"=="%USERPROFILE: =%" (
for %%I in ("%USERPROFILE%") do set "USERPROFILE=%%~sI"
)
:: 8.3 name generation can be turned off, in which case there is nothing to fall
:: back to and Bazel is going to fail in a way that is hard to connect to this.
if not "%USERPROFILE%"=="%USERPROFILE: =%" (
echo [Wrapper] Warning: %USERPROFILE% contains a space and has no 8.3 short name. >&2
echo [Wrapper] Bazel may fail on it. Enable 8.3 names, or move the profile. >&2
)

set "WRAPPER_CACHE_DIR=%USERPROFILE%\.cache\bazel"

:: 1. Figure out which bazel we are supposed to hand off to.
if defined BAZEL_OVERRIDE (
echo Actually calling "%BAZEL_OVERRIDE%"
set "BAZEL_TARGET=%BAZEL_OVERRIDE%"
) else (
rem Ensure Bazelisk integration.
if not defined BAZEL_REAL (
echo Error: This script must be run via Bazelisk on Windows. >&2
exit /b 1
Comment thread
AustinSchuh marked this conversation as resolved.
)
set "BAZEL_TARGET=%BAZEL_REAL%"
)

:: 2. The pinned hermetic git.
set "GIT_RELEASE_TAG=v2.44.0.windows.1"
Comment thread
AustinSchuh marked this conversation as resolved.
Outdated
set "GIT_ARCHIVE_NAME=PortableGit-2.44.0-64-bit.7z.exe"
set "GIT_EXPECTED_SHA256=1fc64ca91b9b475ab0ada72c9f7b3addbe69a6c8f520be31425cf21841cca369"

:: Key the cache by the whole pin, tag and checksum both, so that any change to
:: the three lines above installs the new release instead of being short
:: circuited by a git.exe that an older revision of this script left behind.
set "GIT_CACHE_DIR=%WRAPPER_CACHE_DIR%\portable_git\%GIT_RELEASE_TAG%-%GIT_EXPECTED_SHA256:~0,12%"
set "GIT_EXE_PATH=%GIT_CACHE_DIR%\cmd\git.exe"

if exist "%GIT_EXE_PATH%" goto git_ready
Comment thread
AustinSchuh marked this conversation as resolved.
call :install_git
if errorlevel 1 exit /b 1
:git_ready

:: 3. Put the pinned toolchain in front of anything installed on the machine.
:: PATH is one of the two variables --experimental_strict_repo_env still lets
:: through, so this is what a repository rule resolves bash, sh, and git to.
set "PATH=%GIT_CACHE_DIR%\cmd;%GIT_CACHE_DIR%\bin;%GIT_CACHE_DIR%\usr\bin;%PATH%"

:: Bind the tools explicitly as well, so nothing depends on a PATH lookup at all.
:: Each of these is passed through by a --repo_env line in .bazelrc.
set "BAZEL_SH=%GIT_CACHE_DIR%\bin\bash.exe"
set "BAZEL_GIT=%GIT_CACHE_DIR%\cmd\git.exe"
set "GIT_BIN_PATH=%GIT_CACHE_DIR%\cmd\git.exe"

:: Isolating the pinned git from the developer's ~/.gitconfig belongs in
:: .bazelrc, not here. Exported from this script it would reach every process
:: Bazel launches, and "bazel run //:copybara" needs the real configuration to
:: find a credential helper and an identity to push with.

:: 4. Execute Bazel.
"%BAZEL_TARGET%" %*
exit /b %ERRORLEVEL%

:: Strip the spaces certutil pads its digest with. A subroutine because reading
:: a variable assigned in the same block would need delayed expansion.
:set_computed_sha256
set "SHA_LINE=%~1"
set "COMPUTED_SHA256=%SHA_LINE: =%"
exit /b 0

:: Fetch, verify, and install the pinned PortableGit release.
::
:: Everything happens in a staging directory private to this process and is
:: published with a single rename, so that two Bazelisk invocations racing on a
:: cold cache can't corrupt each other's download and a failed install never
:: leaves a half unpacked tree behind for the next invocation to trust.
:install_git
echo [Wrapper] Git %GIT_RELEASE_TAG% is not in the runtime cache. Fetching isolated PortableGit... >&2

set "STAGE_DIR=%WRAPPER_CACHE_DIR%\portable_git\staging_%RANDOM%_%RANDOM%"
if exist "%STAGE_DIR%" rmdir /s /q "%STAGE_DIR%"
mkdir "%STAGE_DIR%" 2>nul
if not exist "%STAGE_DIR%" (
echo Error: Unable to create the staging directory %STAGE_DIR% >&2
exit /b 1
)

set "GIT_URL=https://github.com/git-for-windows/git/releases/download/%GIT_RELEASE_TAG%/%GIT_ARCHIVE_NAME%"
echo [Wrapper] Downloading from %GIT_URL% ... >&2

curl -fL --silent --show-error --output "%STAGE_DIR%\git.7z.exe" "%GIT_URL%"
if errorlevel 1 (
echo Error: Failed to download the hermetic Git toolchain >&2
rmdir /s /q "%STAGE_DIR%"
exit /b 1
)

echo [Wrapper] Validating cryptographic payload checksum... >&2
set "COMPUTED_SHA256="
for /f "skip=1 delims=" %%A in ('certutil -hashfile "%STAGE_DIR%\git.7z.exe" SHA256 ^| findstr /v "CertUtil"') do call :set_computed_sha256 "%%A"
Comment thread
AustinSchuh marked this conversation as resolved.
Outdated

if /i not "%COMPUTED_SHA256%"=="%GIT_EXPECTED_SHA256%" (
echo. >&2
echo =============================================================== >&2
echo SECURITY ERROR: Cryptographic checksum mismatch detected. >&2
echo Expected: %GIT_EXPECTED_SHA256% >&2
echo Received: %COMPUTED_SHA256% >&2
echo =============================================================== >&2
rmdir /s /q "%STAGE_DIR%"
exit /b 1
)
echo [Wrapper] Integrity verification successful. SHA256 matches. >&2

echo [Wrapper] Extracting archive package... >&2
"%STAGE_DIR%\git.7z.exe" -y -o"%STAGE_DIR%\portable_git" >nul
set "EXTRACT_STATUS=%errorlevel%"
del /q "%STAGE_DIR%\git.7z.exe" 2>nul

if not "%EXTRACT_STATUS%"=="0" (
echo Error: Extracting the hermetic Git toolchain failed with status %EXTRACT_STATUS% >&2
rmdir /s /q "%STAGE_DIR%"
exit /b 1
)
if not exist "%STAGE_DIR%\portable_git\cmd\git.exe" (
echo Error: cmd\git.exe is missing from the extracted toolchain >&2
rmdir /s /q "%STAGE_DIR%"
exit /b 1
)
if not exist "%STAGE_DIR%\portable_git\bin\bash.exe" (
echo Error: bin\bash.exe is missing from the extracted toolchain >&2
rmdir /s /q "%STAGE_DIR%"
exit /b 1
)

:: Publish with a rename. If another process won the race then GIT_CACHE_DIR
:: already exists, and move drops our copy inside it rather than replacing it,
:: so undo that and keep theirs -- it passed the same checks ours did.
move "%STAGE_DIR%\portable_git" "%GIT_CACHE_DIR%" >nul 2>&1
if exist "%GIT_CACHE_DIR%\portable_git" rmdir /s /q "%GIT_CACHE_DIR%\portable_git"
rmdir /s /q "%STAGE_DIR%" 2>nul

if not exist "%GIT_EXE_PATH%" (
echo Error: Failed to install the hermetic Git toolchain into %GIT_CACHE_DIR% >&2
exit /b 1
)

echo [Wrapper] Isolated Git runtime setup completed successfully. >&2
exit /b 0
Loading