diff --git a/BUILD b/BUILD index 906cbe28..3b3abec5 100644 --- a/BUILD +++ b/BUILD @@ -58,6 +58,7 @@ go_test( embed = [":go_default_library"], importpath = "github.com/bazelbuild/bazelisk", deps = [ + "//config:go_default_library", "//core:go_default_library", "//httputil:go_default_library", "//repositories:go_default_library", @@ -154,7 +155,6 @@ go_binary( pkg_npm( name = "npm_package", package_name = "@bazel/bazelisk", - substitutions = {"0.0.0-PLACEHOLDER": "{BUILD_SCM_VERSION}"}, srcs = [ "LICENSE", "README.md", @@ -162,6 +162,7 @@ pkg_npm( "bazelisk.js", "package.json", ], + substitutions = {"0.0.0-PLACEHOLDER": "{BUILD_SCM_VERSION}"}, deps = [ ":bazelisk-darwin-amd64", ":bazelisk-darwin-arm64", diff --git a/README.md b/README.md index 35d46226..ab59fc7d 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ The following variables can be set: - `BAZELISK_GITHUB_TOKEN` - `BAZELISK_HOME` - `BAZELISK_INCOMPATIBLE_FLAGS` +- `BAZELISK_SHOW_PROGRESS` - `BAZELISK_SHUTDOWN` - `BAZELISK_SKIP_WRAPPER` - `BAZELISK_USER_AGENT` diff --git a/WORKSPACE b/WORKSPACE index 651b35a9..dae92e68 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -49,6 +49,20 @@ go_repository( version = "v0.0.0-20140422174119-9fd32a8b3d3d", ) +go_repository( + name = "org_golang_x_term", + importpath = "golang.org/x/term", + sum = "h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=", + version = "v0.13.0", +) + +go_repository( + name = "org_golang_x_sys", + importpath = "golang.org/x/sys", + sum = "h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=", + version = "v0.13.0", +) + go_rules_dependencies() go_register_toolchains(version = "1.19.4") diff --git a/bazelisk_version_test.go b/bazelisk_version_test.go index e5423720..61579f53 100644 --- a/bazelisk_version_test.go +++ b/bazelisk_version_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/bazelbuild/bazelisk/config" "github.com/bazelbuild/bazelisk/core" "github.com/bazelbuild/bazelisk/httputil" "github.com/bazelbuild/bazelisk/repositories" @@ -42,7 +43,7 @@ func TestResolveVersion(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.0.0") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.0.0", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) @@ -60,7 +61,7 @@ func TestResolvePatchVersion(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.0.0-patch1") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.0.0-patch1", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) @@ -82,7 +83,7 @@ func TestResolveLatestRcVersion(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "last_rc") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "last_rc", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) @@ -100,7 +101,7 @@ func TestResolveLatestRcVersion_WithFullRelease(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "last_rc") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "last_rc", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) @@ -120,7 +121,7 @@ func TestResolveLatestVersion_TwoLatestVersionsDoNotHaveAReleaseYet(t *testing.T gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) @@ -142,7 +143,7 @@ func TestResolveLatestVersion_ShouldOnlyReturnStableReleases(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest-1") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest-1", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) @@ -161,7 +162,7 @@ func TestResolveLatestVersion_ShouldFailIfNotEnoughReleases(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false) - _, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest-1") + _, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest-1", config.Null()) if err == nil { t.Fatal("Expected ResolveVersion() to fail.") @@ -178,7 +179,7 @@ func TestResolveLatestVersion_GCSIsDown(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false) - _, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest") + _, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest", config.Null()) if err == nil { t.Fatal("Expected resolveLatestVersion() to fail.") @@ -196,7 +197,7 @@ func TestResolveLatestVersion_GitHubIsDown(t *testing.T) { gh := repositories.CreateGitHubRepo("test_token") repos := core.CreateRepositories(nil, nil, gh, nil, nil, false) - _, _, err := repos.ResolveVersion(tmpDir, "some_fork", "latest") + _, _, err := repos.ResolveVersion(tmpDir, "some_fork", "latest", config.Null()) if err == nil { t.Fatal("Expected resolveLatestVersion() to fail.") @@ -212,7 +213,7 @@ func TestAcceptRollingReleaseName(t *testing.T) { repos := core.CreateRepositories(nil, nil, nil, nil, gcs, false) for _, version := range []string{"10.0.0-pre.20201103.4", "10.0.0-pre.20201103.4.2"} { - resolvedVersion, _, err := repos.ResolveVersion(tmpDir, "", version) + resolvedVersion, _, err := repos.ResolveVersion(tmpDir, "", version, config.Null()) if err != nil { t.Fatalf("ResolveVersion(%q, \"\", %q): expected no error, but got %v", tmpDir, version, err) @@ -233,7 +234,7 @@ func TestResolveLatestRollingRelease(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(nil, nil, nil, nil, gcs, false) - version, _, err := repos.ResolveVersion(tmpDir, "", rollingReleaseIdentifier) + version, _, err := repos.ResolveVersion(tmpDir, "", rollingReleaseIdentifier, config.Null()) if err != nil { t.Fatalf("ResolveVersion(%q, \"\", %q): expected no error, but got %v", tmpDir, rollingReleaseIdentifier, err) @@ -257,7 +258,7 @@ func TestAcceptFloatingReleaseVersions(t *testing.T) { gcs := &repositories.GCSRepo{} repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false) - version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.x") + version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.x", config.Null()) if err != nil { t.Fatalf("Version resolution failed unexpectedly: %v", err) diff --git a/core/core.go b/core/core.go index cab98d9b..cab6179a 100644 --- a/core/core.go +++ b/core/core.go @@ -322,7 +322,7 @@ func downloadBazel(bazelVersionString string, bazeliskHome string, repos *Reposi return "", fmt.Errorf("could not parse Bazel fork and version: %v", err) } - resolvedBazelVersion, downloader, err := repos.ResolveVersion(bazeliskHome, bazelFork, bazelVersion) + resolvedBazelVersion, downloader, err := repos.ResolveVersion(bazeliskHome, bazelFork, bazelVersion, config) if err != nil { return "", fmt.Errorf("could not resolve the version '%s' to an actual version number: %v", bazelVersion, err) } @@ -415,7 +415,7 @@ func downloadBazelToCAS(version string, bazeliskHome string, repos *Repositories } else if formatURL != "" { tmpDestPath, err = repos.DownloadFromFormatURL(config, formatURL, version, temporaryDownloadDir, tmpDestFile) } else if baseURL != "" { - tmpDestPath, err = repos.DownloadFromBaseURL(baseURL, version, temporaryDownloadDir, tmpDestFile) + tmpDestPath, err = repos.DownloadFromBaseURL(baseURL, version, temporaryDownloadDir, tmpDestFile, config) } else { tmpDestPath, err = downloader(temporaryDownloadDir, tmpDestFile) } diff --git a/core/repositories.go b/core/repositories.go index e11e4460..b84a1f46 100644 --- a/core/repositories.go +++ b/core/repositories.go @@ -46,7 +46,7 @@ type ReleaseRepo interface { GetReleaseVersions(bazeliskHome string, filter ReleaseFilter) ([]string, error) // DownloadRelease downloads the given Bazel version into the specified location and returns the absolute path. - DownloadRelease(version, destDir, destFile string) (string, error) + DownloadRelease(version, destDir, destFile string, config config.Config) (string, error) } // CandidateRepo represents a repository that stores Bazel release candidates. @@ -55,7 +55,7 @@ type CandidateRepo interface { GetCandidateVersions(bazeliskHome string) ([]string, error) // DownloadCandidate downloads the given Bazel release candidate into the specified location and returns the absolute path. - DownloadCandidate(version, destDir, destFile string) (string, error) + DownloadCandidate(version, destDir, destFile string, config config.Config) (string, error) } // ForkRepo represents a repository that stores a fork of Bazel (releases). @@ -64,7 +64,7 @@ type ForkRepo interface { GetVersions(bazeliskHome, fork string) ([]string, error) // DownloadVersion downloads the given Bazel binary from the specified fork into the given location and returns the absolute path. - DownloadVersion(fork, version, destDir, destFile string) (string, error) + DownloadVersion(fork, version, destDir, destFile string, config config.Config) (string, error) } // CommitRepo represents a repository that stores Bazel binaries built at specific commits. @@ -76,7 +76,7 @@ type CommitRepo interface { GetLastGreenCommit(bazeliskHome string, downstreamGreen bool) (string, error) // DownloadAtCommit downloads a Bazel binary built at the given commit into the specified location and returns the absolute path. - DownloadAtCommit(commit, destDir, destFile string) (string, error) + DownloadAtCommit(commit, destDir, destFile string, config config.Config) (string, error) } // RollingRepo represents a repository that stores rolling Bazel releases. @@ -85,7 +85,7 @@ type RollingRepo interface { GetRollingVersions(bazeliskHome string) ([]string, error) // DownloadRolling downloads the given Bazel version into the specified location and returns the absolute path. - DownloadRolling(version, destDir, destFile string) (string, error) + DownloadRolling(version, destDir, destFile string, config config.Config) (string, error) } // Repositories offers access to different types of Bazel repositories, mainly for finding and downloading the correct version of Bazel. @@ -99,28 +99,28 @@ type Repositories struct { } // ResolveVersion resolves a potentially relative Bazel version string such as "latest" to an absolute version identifier, and returns this identifier alongside a function to download said version. -func (r *Repositories) ResolveVersion(bazeliskHome, fork, version string) (string, DownloadFunc, error) { +func (r *Repositories) ResolveVersion(bazeliskHome, fork, version string, config config.Config) (string, DownloadFunc, error) { vi, err := versions.Parse(fork, version) if err != nil { return "", nil, err } if vi.IsFork { - return r.resolveFork(bazeliskHome, vi) + return r.resolveFork(bazeliskHome, vi, config) } else if vi.IsRelease { - return r.resolveRelease(bazeliskHome, vi) + return r.resolveRelease(bazeliskHome, vi, config) } else if vi.IsCandidate { - return r.resolveCandidate(bazeliskHome, vi) + return r.resolveCandidate(bazeliskHome, vi, config) } else if vi.IsCommit { - return r.resolveCommit(bazeliskHome, vi) + return r.resolveCommit(bazeliskHome, vi, config) } else if vi.IsRolling { - return r.resolveRolling(bazeliskHome, vi) + return r.resolveRolling(bazeliskHome, vi, config) } return "", nil, fmt.Errorf("Unsupported version identifier '%s'", version) } -func (r *Repositories) resolveFork(bazeliskHome string, vi *versions.Info) (string, DownloadFunc, error) { +func (r *Repositories) resolveFork(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) { if vi.IsRelative && (vi.IsCandidate || vi.IsCommit) { return "", nil, errors.New("forks do not support last_rc, last_green and last_downstream_green") } @@ -132,12 +132,12 @@ func (r *Repositories) resolveFork(bazeliskHome string, vi *versions.Info) (stri return "", nil, err } downloader := func(destDir, destFile string) (string, error) { - return r.Fork.DownloadVersion(vi.Fork, version, destDir, destFile) + return r.Fork.DownloadVersion(vi.Fork, version, destDir, destFile, config) } return version, downloader, nil } -func (r *Repositories) resolveRelease(bazeliskHome string, vi *versions.Info) (string, DownloadFunc, error) { +func (r *Repositories) resolveRelease(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) { lister := func(bazeliskHome string) ([]string, error) { var filter ReleaseFilter if vi.TrackRestriction > 0 { @@ -154,23 +154,23 @@ func (r *Repositories) resolveRelease(bazeliskHome string, vi *versions.Info) (s return "", nil, err } downloader := func(destDir, destFile string) (string, error) { - return r.Releases.DownloadRelease(version, destDir, destFile) + return r.Releases.DownloadRelease(version, destDir, destFile, config) } return version, downloader, nil } -func (r *Repositories) resolveCandidate(bazeliskHome string, vi *versions.Info) (string, DownloadFunc, error) { +func (r *Repositories) resolveCandidate(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) { version, err := resolvePotentiallyRelativeVersion(bazeliskHome, r.Candidates.GetCandidateVersions, vi) if err != nil { return "", nil, err } downloader := func(destDir, destFile string) (string, error) { - return r.Candidates.DownloadCandidate(version, destDir, destFile) + return r.Candidates.DownloadCandidate(version, destDir, destFile, config) } return version, downloader, nil } -func (r *Repositories) resolveCommit(bazeliskHome string, vi *versions.Info) (string, DownloadFunc, error) { +func (r *Repositories) resolveCommit(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) { version := vi.Value if vi.IsRelative { var err error @@ -180,12 +180,12 @@ func (r *Repositories) resolveCommit(bazeliskHome string, vi *versions.Info) (st } } downloader := func(destDir, destFile string) (string, error) { - return r.Commits.DownloadAtCommit(version, destDir, destFile) + return r.Commits.DownloadAtCommit(version, destDir, destFile, config) } return version, downloader, nil } -func (r *Repositories) resolveRolling(bazeliskHome string, vi *versions.Info) (string, DownloadFunc, error) { +func (r *Repositories) resolveRolling(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) { lister := func(bazeliskHome string) ([]string, error) { return r.Rolling.GetRollingVersions(bazeliskHome) } @@ -194,7 +194,7 @@ func (r *Repositories) resolveRolling(bazeliskHome string, vi *versions.Info) (s return "", nil, err } downloader := func(destDir, destFile string) (string, error) { - return r.Rolling.DownloadRolling(version, destDir, destFile) + return r.Rolling.DownloadRolling(version, destDir, destFile, config) } return version, downloader, nil } @@ -220,7 +220,7 @@ func resolvePotentiallyRelativeVersion(bazeliskHome string, lister listVersionsF } // DownloadFromBaseURL can download Bazel binaries from a specific URL while ignoring the predefined repositories. -func (r *Repositories) DownloadFromBaseURL(baseURL, version, destDir, destFile string) (string, error) { +func (r *Repositories) DownloadFromBaseURL(baseURL, version, destDir, destFile string, config config.Config) (string, error) { if !r.supportsBaseURL { return "", fmt.Errorf("downloads from %s are forbidden", BaseURLEnv) } else if baseURL == "" { @@ -233,7 +233,7 @@ func (r *Repositories) DownloadFromBaseURL(baseURL, version, destDir, destFile s } url := fmt.Sprintf("%s/%s/%s", baseURL, version, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) } func BuildURLFromFormat(config config.Config, formatURL, version string) (string, error) { @@ -292,7 +292,7 @@ func (r *Repositories) DownloadFromFormatURL(config config.Config, formatURL, ve return "", err } - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) } // 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. @@ -343,7 +343,7 @@ func (nrr *noReleaseRepo) GetReleaseVersions(bazeliskHome string, filter Release return nil, nrr.err } -func (nrr *noReleaseRepo) DownloadRelease(version, destDir, destFile string) (string, error) { +func (nrr *noReleaseRepo) DownloadRelease(version, destDir, destFile string, config config.Config) (string, error) { return "", nrr.err } @@ -355,7 +355,7 @@ func (ncc *noCandidateRepo) GetCandidateVersions(bazeliskHome string) ([]string, return nil, ncc.err } -func (ncc *noCandidateRepo) DownloadCandidate(version, destDir, destFile string) (string, error) { +func (ncc *noCandidateRepo) DownloadCandidate(version, destDir, destFile string, config config.Config) (string, error) { return "", ncc.err } @@ -367,7 +367,7 @@ func (nfr *noForkRepo) GetVersions(bazeliskHome, fork string) ([]string, error) return nil, nfr.err } -func (nfr *noForkRepo) DownloadVersion(fork, version, destDir, destFile string) (string, error) { +func (nfr *noForkRepo) DownloadVersion(fork, version, destDir, destFile string, config config.Config) (string, error) { return "", nfr.err } @@ -379,7 +379,7 @@ func (nlgr *noCommitRepo) GetLastGreenCommit(bazeliskHome string, downstreamGree return "", nlgr.err } -func (nlgr *noCommitRepo) DownloadAtCommit(commit, destDir, destFile string) (string, error) { +func (nlgr *noCommitRepo) DownloadAtCommit(commit, destDir, destFile string, config config.Config) (string, error) { return "", nlgr.err } @@ -391,6 +391,6 @@ func (nrr *noRollingRepo) GetRollingVersions(bazeliskHome string) ([]string, err return nil, nrr.err } -func (nrr *noRollingRepo) DownloadRolling(version, destDir, destFile string) (string, error) { +func (nrr *noRollingRepo) DownloadRolling(version, destDir, destFile string, config config.Config) (string, error) { return "", nrr.err } diff --git a/go.mod b/go.mod index f2d53e11..67e4f47a 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,6 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d github.com/hashicorp/go-version v1.6.0 github.com/mitchellh/go-homedir v1.1.0 + golang.org/x/term v0.13.0 + golang.org/x/sys v0.13.0 ) diff --git a/httputil/BUILD b/httputil/BUILD index 4c7ac896..d15189b1 100644 --- a/httputil/BUILD +++ b/httputil/BUILD @@ -13,6 +13,8 @@ go_library( importpath = "github.com/bazelbuild/bazelisk/httputil", visibility = ["//visibility:public"], deps = [ + "//config:go_default_library", + "//httputil/progress:go_default_library", "@com_github_bgentry_go_netrc//netrc:go_default_library", "@com_github_mitchellh_go_homedir//:go_default_library", ], @@ -20,6 +22,8 @@ go_library( go_test( name = "go_default_test", - srcs = ["httputil_test.go"], + srcs = [ + "httputil_test.go", + ], embed = [":go_default_library"], ) diff --git a/httputil/httputil.go b/httputil/httputil.go index 36db6782..07eb8c59 100644 --- a/httputil/httputil.go +++ b/httputil/httputil.go @@ -18,6 +18,9 @@ import ( netrc "github.com/bgentry/go-netrc/netrc" homedir "github.com/mitchellh/go-homedir" + + "github.com/bazelbuild/bazelisk/config" + "github.com/bazelbuild/bazelisk/httputil/progress" ) var ( @@ -167,7 +170,7 @@ func tryFindNetrcFileCreds(host string) (string, error) { } // DownloadBinary downloads a file from the given URL into the specified location, marks it executable and returns its full path. -func DownloadBinary(originURL, destDir, destFile string) (string, error) { +func DownloadBinary(originURL, destDir, destFile string, config config.Config) (string, error) { err := os.MkdirAll(destDir, 0755) if err != nil { return "", fmt.Errorf("could not create directory %s: %v", destDir, err) @@ -211,7 +214,11 @@ func DownloadBinary(originURL, destDir, destFile string) (string, error) { return "", fmt.Errorf("HTTP GET %s failed with error %v", originURL, resp.StatusCode) } - _, err = io.Copy(tmpfile, resp.Body) + _, err = io.Copy( + // Add a progress bar during download. + progress.Writer(tmpfile, "Downloading", resp.ContentLength, config), + resp.Body) + progress.Finish(config) if err != nil { return "", fmt.Errorf("could not copy from %s to %s: %v", originURL, tmpfile.Name(), err) } diff --git a/httputil/progress/BUILD b/httputil/progress/BUILD new file mode 100644 index 00000000..07bd8c70 --- /dev/null +++ b/httputil/progress/BUILD @@ -0,0 +1,26 @@ +load("@bazel_gazelle//:def.bzl", "gazelle") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +# gazelle:prefix github.com/bazelbuild/bazelisk/httputil/progress +gazelle(name = "gazelle") + +go_library( + name = "go_default_library", + srcs = [ + "progress.go", + ], + importpath = "github.com/bazelbuild/bazelisk/httputil/progress", + visibility = ["//visibility:public"], + deps = [ + "//config:go_default_library", + "@org_golang_x_term//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "progress_test.go", + ], + embed = [":go_default_library"], +) diff --git a/httputil/progress/progress.go b/httputil/progress/progress.go new file mode 100644 index 00000000..2df66127 --- /dev/null +++ b/httputil/progress/progress.go @@ -0,0 +1,115 @@ +// Package progress makes it possible to dissplay download progress. +package progress + +import ( + "fmt" + "io" + "os" + "strings" + + "golang.org/x/term" + + "github.com/bazelbuild/bazelisk/config" +) + +// Progress shows a download progress bar. +type progress struct { + header string + total int64 + current int64 + lastMessage string +} + +func showProgress(config config.Config) bool { + // If stdout is a terminal, don't show progress. + if !term.IsTerminal(int(os.Stdout.Fd())) { + return false + } + + // Check the config variable. + showProgress := config.Get("BAZELISK_SHOW_PROGRESS") + if len(showProgress) == 0 { + // Default to showing progress + return true + } + switch strings.ToLower(showProgress) { + case "": + return true // Default to on + case "yes": + return true + case "y": + return true + case "true": + return true + case "1": + return true + case "no": + return false + case "n": + return false + case "false": + return false + case "0": + return false + } + // TODO: default: error + + return true +} + +func Writer(w io.Writer, header string, total int64, config config.Config) io.Writer { + if !showProgress(config) { + return w + } + prog := &progress{ + header: header, + total: total, + } + out := io.MultiWriter(w, prog) + return out +} + +func Finish(config config.Config) { + if showProgress(config) { + // Add a newline after the progress bar + fmt.Println() + } +} + +func (p *progress) Write(buf []byte) (int, error) { + l := len(buf) + p.current += int64(l) + p.ShowProgress() + return l, nil +} + +// Writes the current download progress to stdout. +func (p *progress) ShowProgress() { + message := fmt.Sprintf("%s: %s out of %s (%s)", + p.header, + formatMb(p.current), + formatMb(p.total), + formatPercentage(p.current, p.total)) + + if message == p.lastMessage { + return + } + + // Clear the line. + fmt.Printf("\r%s", strings.Repeat(" ", 39)) + + // Show a message, don't add a newline + fmt.Printf("\r%s", message) + p.lastMessage = message +} + +func formatMb(size int64) string { + // TODO: Use units other than MB + inMb := size / (1024 * 1024) + return fmt.Sprintf("%d MB", inMb) +} + +func formatPercentage(current, size int64) string { + percentage := current * 100 / size + return fmt.Sprintf("%d%%", percentage) +} diff --git a/httputil/progress/progress_test.go b/httputil/progress/progress_test.go new file mode 100644 index 00000000..0ff9b968 --- /dev/null +++ b/httputil/progress/progress_test.go @@ -0,0 +1,52 @@ +package progress + +import ( + "testing" +) + +func TestFormatMb(t *testing.T) { + type test struct { + input int64 + want string + } + tests := []test{ + {input: 48*1024*1024 + 512, want: "48 MB"}, + {input: 58538527, want: "55 MB"}, + {input: 0, want: "0 MB"}, + {input: 48*1024*1024 - 1, want: "47 MB"}, + {input: 48 * 1024 * 1024, want: "48 MB"}, + {input: 48 * 1024 * 1024 * 1024, want: "49152 MB"}, + } + + for _, tc := range tests { + name := tc.want + t.Run(name, func(t *testing.T) { + got := formatMb(tc.input) + if got != tc.want { + t.Fatalf("Expected %q, but got %q", tc.want, got) + } + }) + } +} + +func TestFormatPercentage(t *testing.T) { + type test struct { + curr, total int64 + want string + } + tests := []test{ + {curr: 0, total: 1000, want: "0%"}, + {curr: 1000, total: 1000, want: "100%"}, + {curr: 500, total: 1000, want: "50%"}, + } + + for _, tc := range tests { + name := tc.want + t.Run(name, func(t *testing.T) { + got := formatPercentage(tc.curr, tc.total) + if got != tc.want { + t.Fatalf("Expected %q, but got %q", tc.want, got) + } + }) + } +} diff --git a/repositories/BUILD b/repositories/BUILD index c612bf40..6e04af44 100644 --- a/repositories/BUILD +++ b/repositories/BUILD @@ -9,6 +9,7 @@ go_library( importpath = "github.com/bazelbuild/bazelisk/repositories", visibility = ["//visibility:public"], deps = [ + "//config:go_default_library", "//core:go_default_library", "//httputil:go_default_library", "//platforms:go_default_library", diff --git a/repositories/gcs.go b/repositories/gcs.go index db6f0b8f..d5a107dc 100644 --- a/repositories/gcs.go +++ b/repositories/gcs.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/bazelbuild/bazelisk/config" "github.com/bazelbuild/bazelisk/core" "github.com/bazelbuild/bazelisk/httputil" "github.com/bazelbuild/bazelisk/platforms" @@ -137,14 +138,14 @@ type GcsListResponse struct { } // DownloadRelease downloads the given Bazel release into the specified location and returns the absolute path. -func (gcs *GCSRepo) DownloadRelease(version, destDir, destFile string) (string, error) { +func (gcs *GCSRepo) DownloadRelease(version, destDir, destFile string, config config.Config) (string, error) { srcFile, err := platforms.DetermineBazelFilename(version, true) if err != nil { return "", err } url := fmt.Sprintf("%s/%s/release/%s", candidateBaseURL, version, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) } func (gcs *GCSRepo) removeCandidates(history []string, filter core.ReleaseFilter) ([]string, error) { @@ -229,7 +230,7 @@ func (gcs *GCSRepo) GetCandidateVersions(bazeliskHome string) ([]string, error) } // DownloadCandidate downloads the given release candidate into the specified location and returns the absolute path. -func (gcs *GCSRepo) DownloadCandidate(version, destDir, destFile string) (string, error) { +func (gcs *GCSRepo) DownloadCandidate(version, destDir, destFile string, config config.Config) (string, error) { if !strings.Contains(version, "rc") { return "", fmt.Errorf("'%s' does not refer to a release candidate", version) } @@ -243,7 +244,7 @@ func (gcs *GCSRepo) DownloadCandidate(version, destDir, destFile string) (string baseVersion := versionComponents[0] rcVersion := "rc" + versionComponents[1] url := fmt.Sprintf("%s/%s/%s/%s", candidateBaseURL, baseVersion, rcVersion, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) } // CommitRepo @@ -261,14 +262,14 @@ func (gcs *GCSRepo) GetLastGreenCommit(bazeliskHome string, downstreamGreen bool } // DownloadAtCommit downloads a Bazel binary built at the given commit into the specified location and returns the absolute path. -func (gcs *GCSRepo) DownloadAtCommit(commit, destDir, destFile string) (string, error) { +func (gcs *GCSRepo) DownloadAtCommit(commit, destDir, destFile string, config config.Config) (string, error) { log.Printf("Using unreleased version at commit %s", commit) platform, err := platforms.GetPlatform() if err != nil { return "", err } url := fmt.Sprintf("%s/%s/%s/bazel", nonCandidateBaseURL, platform, commit) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) } // RollingRepo @@ -297,7 +298,7 @@ func (gcs *GCSRepo) GetRollingVersions(bazeliskHome string) ([]string, error) { } // DownloadRolling downloads the given Bazel version into the specified location and returns the absolute path. -func (gcs *GCSRepo) DownloadRolling(version, destDir, destFile string) (string, error) { +func (gcs *GCSRepo) DownloadRolling(version, destDir, destFile string, config config.Config) (string, error) { srcFile, err := platforms.DetermineBazelFilename(version, true) if err != nil { return "", err @@ -305,5 +306,5 @@ func (gcs *GCSRepo) DownloadRolling(version, destDir, destFile string) (string, releaseVersion := strings.Split(version, "-")[0] url := fmt.Sprintf("%s/%s/rolling/%s/%s", candidateBaseURL, releaseVersion, version, srcFile) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) } diff --git a/repositories/github.go b/repositories/github.go index 0d020670..0dddd3c7 100644 --- a/repositories/github.go +++ b/repositories/github.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/bazelbuild/bazelisk/config" "github.com/bazelbuild/bazelisk/httputil" "github.com/bazelbuild/bazelisk/platforms" ) @@ -84,11 +85,11 @@ type gitHubRelease struct { } // DownloadVersion downloads a Bazel binary for the given version and fork to the specified location and returns the absolute path. -func (gh *GitHubRepo) DownloadVersion(fork, version, destDir, destFile string) (string, error) { +func (gh *GitHubRepo) DownloadVersion(fork, version, destDir, destFile string, config config.Config) (string, error) { filename, err := platforms.DetermineBazelFilename(version, true) if err != nil { return "", err } url := fmt.Sprintf(urlPattern, fork, version, filename) - return httputil.DownloadBinary(url, destDir, destFile) + return httputil.DownloadBinary(url, destDir, destFile, config) }