Skip to content

Commit

Permalink
Implement support for BAZELISK_FORMAT_URL
Browse files Browse the repository at this point in the history
This new configuration setting provides a format-like string to compute the
URL from which to fetch Bazel.  Takes precedence over BAZELISK_BASE_URL as
this is a more general concept.

Fixes #423.
  • Loading branch information
jmmv committed Apr 6, 2023
1 parent b76d71d commit 017c0a2
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 16 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,21 @@ By default Bazelisk retrieves Bazel releases, release candidates and binaries bu

As mentioned in the previous section, the `<FORK>/<VERSION>` version format allows you to use your own Bazel fork hosted on GitHub:

If you want to create a fork with your own releases, you have to follow the naming conventions that we use in `bazelbuild/bazel` for the binary file names.
If you want to create a fork with your own releases, you should follow the naming conventions that we use in `bazelbuild/bazel` for the binary file names as this results in predictable URLs that are similar to the official ones.
The URL format looks like `https://github.com/<FORK>/bazel/releases/download/<VERSION>/<FILENAME>`.

You can also override the URL by setting the environment variable `$BAZELISK_BASE_URL`. Bazelisk will then append `/<VERSION>/<FILENAME>` to the base URL instead of using the official release server. Bazelisk will read file [`~/.netrc`](https://everything.curl.dev/usingcurl/netrc) for credentials for Basic authentication.

If for any reason none of this works, you can also override the URL format altogether by setting the environment variable `$BAZELISK_FORMAT_URL`. This variable takes a format-like string with placeholders and performs the following replacements to compute the download URL:

- `%e`: Extension suffix, such as the empty string or `.exe`.
- `%h`: Value of `BAZELISK_VERIFY_SHA256`, respecting uppercase/lowercase characters.
- `%m`: Machine architecture name, such as `arm64` or `x86_64`.
- `%o`: Operating system name, such as `darwin` or `linux`.
- `%v`: Bazel version as determined by Bazelisk.
- `%%`: Literal `%` for escaping purposes.
- All other characters after `%` are reserved for future use and result in a processing error.

## Ensuring that your developers use Bazelisk rather than Bazel

Bazel installers typically provide Bazel's [shell wrapper script] as the `bazel` on the PATH.
Expand Down
23 changes: 20 additions & 3 deletions bazelisk_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,20 @@ function test_bazel_version_from_file() {
(echo "FAIL: Expected to find 'Build label: 5.0.0' in the output of 'bazelisk version'"; exit 1)
}

function test_bazel_version_from_url() {
function test_bazel_version_from_format_url() {
setup

echo "0.19.0" > .bazelversion

BAZELISK_FORMAT_URL="https://github.com/bazelbuild/bazel/releases/download/%v/bazel-%v-%o-%m%e" \
BAZELISK_HOME="$BAZELISK_HOME" \
bazelisk version 2>&1 | tee log

grep "Build label: 0.19.0" log || \
(echo "FAIL: Expected to find 'Build label: 0.19.0' in the output of 'bazelisk version'"; exit 1)
}

function test_bazel_version_from_base_url() {
setup

echo "0.19.0" > .bazelversion
Expand Down Expand Up @@ -427,8 +440,12 @@ if [[ $BAZELISK_VERSION == "GO" ]]; then
test_bazel_last_rc
echo

echo "# test_bazel_version_from_url"
test_bazel_version_from_url
echo "# test_bazel_version_from_format_url"
test_bazel_version_from_format_url
echo

echo "# test_bazel_version_from_base_url"
test_bazel_version_from_base_url
echo

echo "# test_bazel_version_prefer_environment_to_bazeliskrc"
Expand Down
5 changes: 4 additions & 1 deletion core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ go_library(

go_test(
name = "go_default_test",
srcs = ["core_test.go"],
srcs = [
"core_test.go",
"repositories_test.go",
],
embed = [":go_default_library"],
)
10 changes: 8 additions & 2 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,14 @@ func downloadBazelIfNecessary(version string, baseDirectory string, repos *Repos
}

var tmpDestPath string
if url := GetEnvOrConfig(BaseURLEnv); url != "" {
tmpDestPath, err = repos.DownloadFromBaseURL(url, version, destDir, tmpDestFile)
baseURL := GetEnvOrConfig(BaseURLEnv)
formatURL := GetEnvOrConfig(FormatURLEnv)
if baseURL != "" && formatURL != "" {
return "", fmt.Errorf("cannot set %s and %s at once", BaseURLEnv, FormatURLEnv)
} else if formatURL != "" {
tmpDestPath, err = repos.DownloadFromFormatURL(formatURL, version, destDir, tmpDestFile)
} else if baseURL != "" {
tmpDestPath, err = repos.DownloadFromBaseURL(baseURL, version, destDir, tmpDestFile)
} else {
tmpDestPath, err = downloader(destDir, tmpDestFile)
}
Expand Down
62 changes: 62 additions & 0 deletions core/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
const (
// BaseURLEnv is the name of the environment variable that stores the base URL for downloads.
BaseURLEnv = "BAZELISK_BASE_URL"

// FormatURLEnv is the name of the environment variable that stores the format string to generate URLs for downloads.
FormatURLEnv = "BAZELISK_FORMAT_URL"
)

// DownloadFunc downloads a specific Bazel binary to the given location and returns the absolute path.
Expand Down Expand Up @@ -232,6 +235,65 @@ func (r *Repositories) DownloadFromBaseURL(baseURL, version, destDir, destFile s
return httputil.DownloadBinary(url, destDir, destFile)
}

func BuildURLFromFormat(formatURL, version string) (string, error) {
osName, err := platforms.DetermineOperatingSystem()
if err != nil {
return "", err
}

machineName, err := platforms.DetermineArchitecture(osName, version)
if err != nil {
return "", err
}

var b strings.Builder
b.Grow(len(formatURL) * 2) // Approximation.
for i := 0; i < len(formatURL); i++ {
ch := formatURL[i]
if ch == '%' {
i++
if i == len(formatURL) {
return "", errors.New("trailing %")
}

ch = formatURL[i]
switch ch {
case 'e':
b.WriteString(platforms.DetermineExecutableFilenameSuffix())
case 'h':
b.WriteString(GetEnvOrConfig("BAZELISK_VERIFY_SHA256"))
case 'm':
b.WriteString(machineName)
case 'o':
b.WriteString(osName)
case 'v':
b.WriteString(version)
case '%':
b.WriteByte('%')
default:
return "", fmt.Errorf("unknown placeholder %%%c", ch)
}
} else {
b.WriteByte(ch)
}
}
return b.String(), nil
}

// DownloadFromFormatURL can download Bazel binaries from a specific URL while ignoring the predefined repositories.
func (r *Repositories) DownloadFromFormatURL(formatURL, version, destDir, destFile string) (string, error) {
if formatURL == "" {
return "", fmt.Errorf("%s is not set", FormatURLEnv)
}

url, err := BuildURLFromFormat(formatURL, version)
if err != nil {
return "", err
}

return httputil.DownloadBinary(url, destDir, destFile)
}

// CreateRepositories creates a new Repositories instance with the given repositories. Any nil repository will be replaced by a dummy repository that raises an error whenever a download is attempted.
func CreateRepositories(releases ReleaseRepo, candidates CandidateRepo, fork ForkRepo, commits CommitRepo, rolling RollingRepo, supportsBaseURL bool) *Repositories {
repos := &Repositories{supportsBaseURL: supportsBaseURL}
Expand Down
78 changes: 78 additions & 0 deletions core/repositories_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package core

import (
"errors"
"fmt"
"os"
"testing"

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

func TestBuildURLFromFormat(t *testing.T) {
osName, err := platforms.DetermineOperatingSystem()
if err != nil {
t.Fatalf("Cannot get operating system name: %v", err)
}

version := "6.0.0"

machineName, err := platforms.DetermineArchitecture(osName, version)
if err != nil {
t.Fatalf("Cannot get machine architecture name: %v", err)
}

suffix := platforms.DetermineExecutableFilenameSuffix()

previousSha256, hadSha256 := os.LookupEnv("BAZELISK_VERIFY_SHA256")
sha256 := "SomeSha256ValueThatIsIrrelevant"
if err := os.Setenv("BAZELISK_VERIFY_SHA256", sha256); err != nil {
t.Fatalf("Failed to set BAZELISK_VERIFY_SHA256")
}
defer func() {
if hadSha256 {
os.Setenv("BAZELISK_VERIFY_SHA256", previousSha256)
} else {
os.Unsetenv("BAZELISK_VERIFY_SHA256")
}
}()

type test struct {
format string
want string
wantErr error
}

tests := []test{
{format: "", want: ""},
{format: "no/placeholders", want: "no/placeholders"},

{format: "%", wantErr: errors.New("trailing %")},
{format: "%%", want: "%"},
{format: "%%%%", want: "%%"},
{format: "invalid/trailing/%", wantErr: errors.New("trailing %")},
{format: "escaped%%placeholder", want: "escaped%placeholder"},

{format: "foo-%e-bar", want: fmt.Sprintf("foo-%s-bar", suffix)},
{format: "foo-%h-bar", want: fmt.Sprintf("foo-%s-bar", sha256)},
{format: "foo-%m-bar", want: fmt.Sprintf("foo-%s-bar", machineName)},
{format: "foo-%o-bar", want: fmt.Sprintf("foo-%s-bar", osName)},
{format: "foo-%v-bar", want: fmt.Sprintf("foo-%s-bar", version)},

{format: "repeated %v %m %v", want: fmt.Sprintf("repeated %s %s %s", version, machineName, version)},

{format: "https://real.example.com/%e/%m/%o/%v#%%20trailing", want: fmt.Sprintf("https://real.example.com/%s/%s/%s/%s#%%20trailing", suffix, machineName, osName, version)},
}

for _, tc := range tests {
got, err := BuildURLFromFormat(tc.format, version)
if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tc.wantErr) {
if got != "" {
t.Errorf("format '%s': got non-empty '%s' on error", tc.format, got)
}
t.Errorf("format '%s': got error %v, want error %v", tc.format, err, tc.wantErr)
} else if got != tc.want {
t.Errorf("format '%s': got %s, want %s", tc.format, got, tc.want)
}
}
}
6 changes: 3 additions & 3 deletions httputil/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ go_library(
"fake.go",
"httputil.go",
],
importpath = "github.com/bazelbuild/bazelisk/httputil",
visibility = ["//visibility:public"],
deps = [
"@com_github_bgentry_go_netrc//:go_default_library",
"@com_github_mitchellh_go_homedir//:go_default_library",
"@com_github_bgentry_go_netrc//:go_default_library"
],
importpath = "github.com/bazelbuild/bazelisk/httputil",
visibility = ["//visibility:public"],
)

go_test(
Expand Down
6 changes: 6 additions & 0 deletions platforms/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ go_test(
srcs = ["platforms_test.go"],
embed = [":go_default_library"],
)

go_test(
name = "go_default_test",
srcs = ["platforms_test.go"],
embed = [":go_default_library"],
)
27 changes: 21 additions & 6 deletions platforms/platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ func DetermineExecutableFilenameSuffix() string {
return filenameSuffix
}

// DetermineBazelFilename returns the correct file name of a local Bazel binary.
func DetermineBazelFilename(version string, includeSuffix bool) (string, error) {
func DetermineArchitecture(osName, version string) (string, error) {
var machineName string
switch runtime.GOARCH {
case "amd64":
Expand All @@ -66,16 +65,32 @@ func DetermineBazelFilename(version string, includeSuffix bool) (string, error)
return "", fmt.Errorf("unsupported machine architecture \"%s\", must be arm64 or x86_64", runtime.GOARCH)
}

var osName string
if osName == "darwin" {
machineName = DarwinFallback(machineName, version)
}

return machineName, nil
}

func DetermineOperatingSystem() (string, error) {
switch runtime.GOOS {
case "darwin", "linux", "windows":
osName = runtime.GOOS
return runtime.GOOS, nil
default:
return "", fmt.Errorf("unsupported operating system \"%s\", must be Linux, macOS or Windows", runtime.GOOS)
}
}

if osName == "darwin" {
machineName = DarwinFallback(machineName, version)
// DetermineBazelFilename returns the correct file name of a local Bazel binary.
func DetermineBazelFilename(version string, includeSuffix bool) (string, error) {
osName, err := DetermineOperatingSystem()
if err != nil {
return "", err
}

machineName, err := DetermineArchitecture(osName, version)
if err != nil {
return "", err
}

var filenameSuffix string
Expand Down

0 comments on commit 017c0a2

Please sign in to comment.