Skip to content

Commit

Permalink
Refactor repositories API (#631)
Browse files Browse the repository at this point in the history
This commit merges ReleaseRepo with CandidateRepo into a single type called LTSRepo.

1. The new behavior more closely resembles reality.
2. It will be much easier to implement #630.

Progress towards #630.
  • Loading branch information
fweikert authored Nov 20, 2024
1 parent eb279d7 commit e81f342
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 226 deletions.
4 changes: 2 additions & 2 deletions bazelisk.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func main() {
gcs := &repositories.GCSRepo{}
config := core.MakeDefaultConfig()
gitHub := repositories.CreateGitHubRepo(config.Get("BAZELISK_GITHUB_TOKEN"))
// Fetch LTS releases, release candidates, rolling releases and Bazel-at-commits from GCS, forks from GitHub.
repos := core.CreateRepositories(gcs, gcs, gitHub, gcs, gcs, true)
// Fetch LTS releases & candidates, rolling releases and Bazel-at-commits from GCS, forks from GitHub.
repos := core.CreateRepositories(gcs, gitHub, gcs, gcs, true)

exitCode, err := core.RunBazeliskWithArgsFuncAndConfig(func(string) []string { return os.Args[1:] }, repos, config)
if err != nil {
Expand Down
35 changes: 23 additions & 12 deletions bazelisk_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"slices"
"strings"
"testing"

Expand All @@ -23,6 +24,16 @@ var (
tmpDir = ""
)

func TestGetInAscendingOrder(t *testing.T) {
input := []string{"7.0.1rc2", "6.1.0", "6.0.1", "10.11.12", "6.0.0", "7.0.1", "6.0.0rc2", "7.0.1rc1", "10.11.12rc1", "6.0.0rc1"}
want := []string{"6.0.0rc1", "6.0.0rc2", "6.0.0", "6.0.1", "6.1.0", "7.0.1rc1", "7.0.1rc2", "7.0.1", "10.11.12rc1", "10.11.12"}

got := versions.GetInAscendingOrder(input)
if !slices.Equal(got, want) {
t.Errorf("GetInAscendingOrder(): got %s, want %s", got, want)
}
}

func TestMain(m *testing.M) {
var err error
tmpDir, err = os.MkdirTemp("", "version_test")
Expand All @@ -41,7 +52,7 @@ func TestResolveVersion(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.0.0", config.Null())

if err != nil {
Expand All @@ -59,7 +70,7 @@ func TestResolvePatchVersion(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.0.0-patch1", config.Null())

if err != nil {
Expand All @@ -81,7 +92,7 @@ func TestResolveLatestRcVersion(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "last_rc", config.Null())

if err != nil {
Expand All @@ -99,7 +110,7 @@ func TestResolveLatestRcVersion_WithFullRelease(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(nil, gcs, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "last_rc", config.Null())

if err != nil {
Expand All @@ -119,7 +130,7 @@ func TestResolveLatestVersion_TwoLatestVersionsDoNotHaveAReleaseYet(t *testing.T
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest", config.Null())

if err != nil {
Expand All @@ -141,7 +152,7 @@ func TestResolveLatestVersion_ShouldOnlyReturnStableReleases(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest-1", config.Null())

if err != nil {
Expand All @@ -160,7 +171,7 @@ func TestResolveLatestVersion_ShouldFailIfNotEnoughReleases(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
_, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest-1", config.Null())

if err == nil {
Expand All @@ -177,7 +188,7 @@ func TestResolveLatestVersion_GCSIsDown(t *testing.T) {
g.Transport.AddResponse("https://www.googleapis.com/storage/v1/b/bazel/o?delimiter=/", 500, "", nil)

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
_, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "latest", config.Null())

if err == nil {
Expand All @@ -194,7 +205,7 @@ func TestResolveLatestVersion_GitHubIsDown(t *testing.T) {
transport.AddResponse("https://api.github.com/repos/bazelbuild/bazel/releases", 500, "", nil)

gh := repositories.CreateGitHubRepo("test_token")
repos := core.CreateRepositories(nil, nil, gh, nil, nil, false)
repos := core.CreateRepositories(nil, gh, nil, nil, false)

_, _, err := repos.ResolveVersion(tmpDir, "some_fork", "latest", config.Null())

Expand All @@ -209,7 +220,7 @@ func TestResolveLatestVersion_GitHubIsDown(t *testing.T) {

func TestAcceptRollingReleaseName(t *testing.T) {
gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(nil, nil, nil, nil, gcs, false)
repos := core.CreateRepositories(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, config.Null())
Expand All @@ -231,7 +242,7 @@ func TestResolveLatestRollingRelease(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(nil, nil, nil, nil, gcs, false)
repos := core.CreateRepositories(nil, nil, nil, gcs, false)

version, _, err := repos.ResolveVersion(tmpDir, "", rollingReleaseIdentifier, config.Null())

Expand All @@ -256,7 +267,7 @@ func TestAcceptFloatingReleaseVersions(t *testing.T) {
s.Finish()

gcs := &repositories.GCSRepo{}
repos := core.CreateRepositories(gcs, nil, nil, nil, nil, false)
repos := core.CreateRepositories(gcs, nil, nil, nil, false)
version, _, err := repos.ResolveVersion(tmpDir, versions.BazelUpstream, "4.x", config.Null())

if err != nil {
Expand Down
140 changes: 52 additions & 88 deletions core/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,24 @@ const (
// DownloadFunc downloads a specific Bazel binary to the given location and returns the absolute path.
type DownloadFunc func(destDir, destFile string) (string, error)

// ReleaseFilter filters Bazel versions based on specific criteria.
type ReleaseFilter func(matchesSoFar int, currentVersion string) bool
// LTSFilter filters Bazel versions based on specific criteria.
type LTSFilter func(string) bool

func lastNReleases(max int) ReleaseFilter {
return func(matchesSoFar int, currentVersion string) bool {
return max < 1 || matchesSoFar < max
}
}

// filterReleasesByTrack only works reliably if iterating on Bazel versions in descending order.
func filterReleasesByTrack(track int) ReleaseFilter {
prefix := fmt.Sprintf("%d.", track)
return func(matchesSoFar int, currentVersion string) bool {
return matchesSoFar == 0 && strings.HasPrefix(currentVersion, prefix)
}
}

// ReleaseRepo represents a repository that stores LTS Bazel releases.
type ReleaseRepo interface {
// GetReleaseVersions returns a list of all available release versions that match the given filter function.
// Warning: the filter only works reliably if the versions are processed in descending order!
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, config config.Config) (string, error)
// FilterOpts represents options relevant to filtering Bazel versions.
type FilterOpts struct {
MaxResults int
Track int
Filter LTSFilter
}

// CandidateRepo represents a repository that stores Bazel release candidates.
type CandidateRepo interface {
// GetCandidateVersions returns the versions of all available release candidates.
GetCandidateVersions(bazeliskHome string) ([]string, error)
// LTSRepo represents a repository that stores LTS Bazel releases and their candidates.
type LTSRepo interface {
// GetLTSVersions returns a list of all available LTS release (candidates) that match the given filter options.
// Warning: Filters only work reliably if the versions are processed in descending order!
GetLTSVersions(bazeliskHome string, opts *FilterOpts) ([]string, error)

// DownloadCandidate downloads the given Bazel release candidate into the specified location and returns the absolute path.
DownloadCandidate(version, destDir, destFile string, config config.Config) (string, error)
// DownloadLTS downloads the given Bazel version into the specified location and returns the absolute path.
DownloadLTS(version, destDir, destFile string, config config.Config) (string, error)
}

// ForkRepo represents a repository that stores a fork of Bazel (releases).
Expand Down Expand Up @@ -88,8 +72,7 @@ type RollingRepo interface {

// Repositories offers access to different types of Bazel repositories, mainly for finding and downloading the correct version of Bazel.
type Repositories struct {
Releases ReleaseRepo
Candidates CandidateRepo
LTS LTSRepo
Fork ForkRepo
Commits CommitRepo
Rolling RollingRepo
Expand All @@ -105,17 +88,15 @@ func (r *Repositories) ResolveVersion(bazeliskHome, fork, version string, config

if vi.IsFork {
return r.resolveFork(bazeliskHome, vi, config)
} else if vi.IsRelease {
return r.resolveRelease(bazeliskHome, vi, config)
} else if vi.IsCandidate {
return r.resolveCandidate(bazeliskHome, vi, config)
} else if vi.IsLTS {
return r.resolveLTS(bazeliskHome, vi, config)
} else if vi.IsCommit {
return r.resolveCommit(bazeliskHome, vi, config)
} else if vi.IsRolling {
return r.resolveRolling(bazeliskHome, vi, config)
}

return "", nil, fmt.Errorf("Unsupported version identifier '%s'", version)
return "", nil, fmt.Errorf("unsupported version identifier '%s'", version)
}

func (r *Repositories) resolveFork(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) {
Expand All @@ -135,35 +116,36 @@ func (r *Repositories) resolveFork(bazeliskHome string, vi *versions.Info, confi
return version, downloader, nil
}

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 {
// Optimization: only fetch matching releases if an LTS track is specified.
filter = filterReleasesByTrack(vi.TrackRestriction)
} else {
// Optimization: only fetch last (x+1) releases if the version is "latest-x".
filter = lastNReleases(vi.LatestOffset + 1)
}
return r.Releases.GetReleaseVersions(bazeliskHome, filter)
}
version, err := resolvePotentiallyRelativeVersion(bazeliskHome, lister, vi)
if err != nil {
return "", nil, err
var IsRelease = func(version string) bool {
return !strings.Contains(version, "rc")
}

var IsCandidate = func(version string) bool {
return strings.Contains(version, "rc")
}

func (r *Repositories) resolveLTS(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) {
opts := &FilterOpts{
// Optimization: only fetch last (x+1) releases if the version is "latest-x".
MaxResults: vi.LatestOffset + 1,
Track: vi.TrackRestriction,
}
downloader := func(destDir, destFile string) (string, error) {
return r.Releases.DownloadRelease(version, destDir, destFile, config)

if vi.IsRelease {
opts.Filter = IsRelease
} else {
opts.Filter = IsCandidate
}
return version, downloader, nil
}

func (r *Repositories) resolveCandidate(bazeliskHome string, vi *versions.Info, config config.Config) (string, DownloadFunc, error) {
version, err := resolvePotentiallyRelativeVersion(bazeliskHome, r.Candidates.GetCandidateVersions, vi)
lister := func(bazeliskHome string) ([]string, error) {
return r.LTS.GetLTSVersions(bazeliskHome, opts)
}
version, err := resolvePotentiallyRelativeVersion(bazeliskHome, lister, vi)
if err != nil {
return "", nil, err
}
downloader := func(destDir, destFile string) (string, error) {
return r.Candidates.DownloadCandidate(version, destDir, destFile, config)
return r.LTS.DownloadLTS(version, destDir, destFile, config)
}
return version, downloader, nil
}
Expand Down Expand Up @@ -295,19 +277,13 @@ func (r *Repositories) DownloadFromFormatURL(config config.Config, formatURL, ve
}

// 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 {
func CreateRepositories(lts LTSRepo, fork ForkRepo, commits CommitRepo, rolling RollingRepo, supportsBaseURL bool) *Repositories {
repos := &Repositories{supportsBaseURL: supportsBaseURL}

if releases == nil {
repos.Releases = &noReleaseRepo{err: errors.New("Bazel LTS releases are not supported")}
} else {
repos.Releases = releases
}

if candidates == nil {
repos.Candidates = &noCandidateRepo{err: errors.New("Bazel release candidates are not supported")}
if lts == nil {
repos.LTS = &noLTSRepo{err: errors.New("Bazel LTS releases & candidates are not supported")}
} else {
repos.Candidates = candidates
repos.LTS = lts
}

if fork == nil {
Expand All @@ -331,31 +307,19 @@ func CreateRepositories(releases ReleaseRepo, candidates CandidateRepo, fork For
return repos
}

// The whole point of the structs below this line is that users can simply call repos.Releases.GetReleaseVersions()
// (etc) without having to worry whether `Releases` points at an actual repo.

type noReleaseRepo struct {
err error
}

func (nrr *noReleaseRepo) GetReleaseVersions(bazeliskHome string, filter ReleaseFilter) ([]string, error) {
return nil, nrr.err
}

func (nrr *noReleaseRepo) DownloadRelease(version, destDir, destFile string, config config.Config) (string, error) {
return "", nrr.err
}
// The whole point of the structs below this line is that users can simply call repos.LTS.GetLTSVersions()
// (etc) without having to worry whether `LTS` points at an actual repo.

type noCandidateRepo struct {
type noLTSRepo struct {
err error
}

func (ncc *noCandidateRepo) GetCandidateVersions(bazeliskHome string) ([]string, error) {
return nil, ncc.err
func (nolts *noLTSRepo) GetLTSVersions(bazeliskHome string, opts *FilterOpts) ([]string, error) {
return nil, nolts.err
}

func (ncc *noCandidateRepo) DownloadCandidate(version, destDir, destFile string, config config.Config) (string, error) {
return "", ncc.err
func (nolts *noLTSRepo) DownloadLTS(version, destDir, destFile string, config config.Config) (string, error) {
return "", nolts.err
}

type noForkRepo struct {
Expand Down
Loading

0 comments on commit e81f342

Please sign in to comment.