Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,19 @@ You can set `BAZELISK_CLEAN` to run `clean --expunge` between builds when migrat

## tools/bazel

If `tools/bazel` exists in your workspace root and is executable, Bazelisk will run this file, instead of the Bazel version it downloaded.
Bazelisk will try to run a Bazel wrapper from the `tools` directory if present, instead of the Bazel version it downloaded. When doing so, Bazelisk will set the environment variable `BAZEL_REAL` to the path of the downloaded Bazel binary. This can be useful, for example, if you have a wrapper script that ensures that environment variables are set to known good values.
Copy link
Member

Choose a reason for hiding this comment

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

This sounds as if BAZEL_REAL is only set when tools/bazel exists, which is not the case.


It will set the environment variable `BAZEL_REAL` to the path of the downloaded Bazel binary.
This can be useful, if you have a wrapper script that e.g. ensures that environment variables are set to known good values.
This behavior can be disabled by setting the environment variable `BAZELISK_SKIP_WRAPPER` to any value (except the empty string) before launching Bazelisk.
Bazelisk looks for the following wrappers, in order:

You can control the user agent that Bazelisk sends in all HTTP requests by setting `BAZELISK_USER_AGENT` to the desired value.
* `tools/bazel.<OSNAME>-<ARCH>`: An executable that's OS- and platform-specific.
* `tools/bazel.<ARCH>`: An executable that's platform-specific (for cases where your project only supports one operating system anyway).
* `tools/bazel`: An executable or shell script.
* `tools/bazel.ps1`: A PowerShell script on Windows.
* `tools/bazel.bat`: A batch file on Windows.

On Windows, Bazelisk will also consider the following files in addition to `tools/bazel`:
This behavior can be disabled by setting the environment variable `BAZELISK_SKIP_WRAPPER` to any value (except the empty string) before launching Bazelisk.

* `tools/bazel.ps1` (PowerShell)
* `tools/bazel.bat`
You can control the user agent that Bazelisk sends in all HTTP requests by setting `BAZELISK_USER_AGENT` to the desired value.

# .bazeliskrc configuration file

Expand Down
38 changes: 28 additions & 10 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,20 +607,38 @@ func maybeDelegateToWrapperFromDir(bazel string, wd string, config config.Config
}

root := ws.FindWorkspaceRoot(wd)
wrapper := filepath.Join(root, wrapperPath)
if stat, err := os.Stat(wrapper); err == nil && !stat.Mode().IsDir() && stat.Mode().Perm()&0111 != 0 {
return wrapper
}
exeSuffix := platforms.DetermineExecutableFilenameSuffix()

// The list of candidate wrappers must go from most specific to least specific.
candidates := []string{}
osName, err := platforms.DetermineOperatingSystem()
if err == nil {
// We pass platforms.DarwinArm64MinVersion to disable the Darwin x86_64 fallback because this feature
// was added years after Apple Silicon launched and it's not worth trying to be backwards compatible.
arch, err := platforms.DetermineArchitecture(osName, platforms.DarwinArm64MinVersion)
if err == nil {
candidates = append(candidates, filepath.Join(root, wrapperPath+"."+runtime.GOOS+"-"+arch+exeSuffix))
candidates = append(candidates, filepath.Join(root, wrapperPath+"."+arch+exeSuffix))
}
}
candidates = append(candidates, filepath.Join(root, wrapperPath))
if runtime.GOOS == "windows" {
powershellWrapper := filepath.Join(root, wrapperPath+".ps1")
if stat, err := os.Stat(powershellWrapper); err == nil && !stat.Mode().IsDir() {
return powershellWrapper
candidates = append(candidates, filepath.Join(root, wrapperPath+".ps1"))
candidates = append(candidates, filepath.Join(root, wrapperPath+".bat"))
}

for _, wrapper := range candidates {
stat, err := os.Stat(wrapper)
if err != nil {
continue
}

batchWrapper := filepath.Join(root, wrapperPath+".bat")
if stat, err := os.Stat(batchWrapper); err == nil && !stat.Mode().IsDir() {
return batchWrapper
valid := !stat.Mode().IsDir()
if runtime.GOOS != "windows" {
valid = valid && stat.Mode().Perm()&0111 != 0
}
if valid {
return wrapper
}
}

Expand Down
83 changes: 83 additions & 0 deletions core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/bazelbuild/bazelisk/config"
"github.com/bazelbuild/bazelisk/platforms"
)

func TestMaybeDelegateToNoWrapper(t *testing.T) {
Expand Down Expand Up @@ -58,6 +59,88 @@ func TestMaybeDelegateToNoNonExecutableWrapper(t *testing.T) {
}
}

func TestMaybeDelegateToOsAndArchSpecificWrapper(t *testing.T) {
// It's not guaranteed that `tools/bazel` is executable on the
// Windows host running this test. Thus the test is skipped on
// this platform to guarantee consistent results.
if runtime.GOOS == "windows" {
return
}

var tmpDir, err = os.MkdirTemp("", "TestMaybeDelegateToOsAndArchSpecificWrapper")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpDir)

osName, err := platforms.DetermineOperatingSystem()
if err != nil {
log.Fatal(err)
}
arch, err := platforms.DetermineArchitecture(osName, platforms.DarwinArm64MinVersion)
if err != nil {
log.Fatal(err)
}

os.MkdirAll(tmpDir, os.ModeDir|0700)
os.WriteFile(filepath.Join(tmpDir, "WORKSPACE"), []byte(""), 0600)
os.WriteFile(filepath.Join(tmpDir, "BUILD"), []byte(""), 0600)

os.MkdirAll(filepath.Join(tmpDir, "tools"), os.ModeDir|0700)
os.WriteFile(filepath.Join(tmpDir, "tools", "bazel."+runtime.GOOS+"-"+arch), []byte(""), 0700)
// Also create the standard wrapper to ensure we prefer the os/arch-specific wrapper.
os.WriteFile(filepath.Join(tmpDir, "tools", "bazel"), []byte(""), 0700)
// Also create the plaform-specific wrapper to ensure we prefer the os/arch-specific wrapper.
os.WriteFile(filepath.Join(tmpDir, "tools", "bazel."+arch), []byte(""), 0700)

entrypoint := maybeDelegateToWrapperFromDir("bazel_real", tmpDir, config.Null())
expected := filepath.Join(tmpDir, "tools", "bazel."+runtime.GOOS+"-"+arch)

if entrypoint != expected {
t.Fatalf("Expected to delegate bazel to %q, but got %q", expected, entrypoint)
}
}

func TestMaybeDelegateToArchSpecificWrapper(t *testing.T) {
// It's not guaranteed that `tools/bazel` is executable on the
// Windows host running this test. Thus the test is skipped on
// this platform to guarantee consistent results.
if runtime.GOOS == "windows" {
return
}

var tmpDir, err = os.MkdirTemp("", "TestMaybeDelegateToArchSpecificWrapper")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpDir)

osName, err := platforms.DetermineOperatingSystem()
if err != nil {
log.Fatal(err)
}
arch, err := platforms.DetermineArchitecture(osName, platforms.DarwinArm64MinVersion)
if err != nil {
log.Fatal(err)
}

os.MkdirAll(tmpDir, os.ModeDir|0700)
os.WriteFile(filepath.Join(tmpDir, "WORKSPACE"), []byte(""), 0600)
os.WriteFile(filepath.Join(tmpDir, "BUILD"), []byte(""), 0600)

os.MkdirAll(filepath.Join(tmpDir, "tools"), os.ModeDir|0700)
os.WriteFile(filepath.Join(tmpDir, "tools", "bazel."+arch), []byte(""), 0700)
// Also create the standard wrapper to ensure we prefer the arch-specific wrapper.
os.WriteFile(filepath.Join(tmpDir, "tools", "bazel"), []byte(""), 0700)

entrypoint := maybeDelegateToWrapperFromDir("bazel_real", tmpDir, config.Null())
expected := filepath.Join(tmpDir, "tools", "bazel."+arch)

if entrypoint != expected {
t.Fatalf("Expected to delegate bazel to %q, but got %q", expected, entrypoint)
}
}

func TestMaybeDelegateToStandardWrapper(t *testing.T) {
// It's not guaranteed that `tools/bazel` is executable on the
// Windows host running this test. Thus the test is skipped on
Expand Down
5 changes: 4 additions & 1 deletion platforms/platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ func DetermineBazelFilename(version string, includeSuffix bool, config config.Co
return fmt.Sprintf("%s-%s-%s-%s%s", flavor, version, osName, machineName, filenameSuffix), nil
}

// DarwinArm64MinVersion represents the minimum Darwin version that supported arm64.
const DarwinArm64MinVersion = "4.1.0"

// DarwinFallback Darwin arm64 was supported since 4.1.0, before 4.1.0, fall back to x86_64
func DarwinFallback(machineName string, version string) (alterMachineName string) {
// Do not use fallback for commits since they are likely newer than Bazel 4.1
Expand All @@ -124,7 +127,7 @@ func DarwinFallback(machineName string, version string) (alterMachineName string
return machineName
}

armSupportVer, _ := semver.NewVersion("4.1.0")
armSupportVer, _ := semver.NewVersion(DarwinArm64MinVersion)

if machineName == "arm64" && v.LessThan(armSupportVer) {
log.Printf("WARN: Fallback to x86_64 because arm64 is not supported on Apple Silicon until 4.1.0")
Expand Down