diff --git a/README.md b/README.md index e2229db..722633a 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,22 @@ Examples: | Command | Description | | --- | --- | | `$ bazel install` | List all available releases | -| `$ bazel install 0.8.0` | Install bazel release 0.8.0 | -| `$ bazel install --list 0.8.0` | Show the assets bundled in install 0.8.0 | +| `$ bazel install 0.24.1` | Install bazel release 0.24.1 | +| `$ bazel install --assets 0.24.1` | Show the assets bundled in install 0.24.1 | + +To build bazel from source, specify a commit-id: + +``` +$ bazel install 9a32b861799278217c37eb32f864a951ca99617e +(( bzl )) Install successful. Remember to 'export BAZEL_VERSION=9a32b861799278217c37eb32f864a951ca99617e' +``` + +> The command above will clone bazel to +> `$HOME/.cache/bzl/github.com/bazebuild/bazel`. If you already have a version +> of bazel installed that you'd rather use instead, specify +> `--bazel_source_dir`. If you want to build & install from `bazel_source_dir` +> directly (without changing to a specific commit), use `bazel install snapshot +> --bazel_source_dir=/path/to/bazel` (). ### `$ bazel use` @@ -87,6 +101,31 @@ $ bazel use rules_go ... ``` +You can also specify a commit-id: + +``` +$ bazel use rules_go ef7cca8857f2f3a905b86c264737d0043c6a766e + +http_archive( + name = "io_bazel_rules_go", + urls = ["https://github.com/bazelbuild/rules_go/archive/ef7cca8857f2f3a905b86c264737d0043c6a766e.tar.gz"], + strip_prefix = "rules_go-ef7cca8857f2f3a905b86c264737d0043c6a766e", + sha256 = "1a400dc2f69971e3ebce29f7950dc38f8bb7e41c727258b6d2fb70060a4ce429", +) +``` + +To list recent commits on a particular branch: + +``` +$ bazel use rules_go --history=master +(( bzl )) 2019/04/29 06:36:48 Listing recent commit history for bazelbuild/rules_go... +ef7cca8857f2f3a905b86c264737d0043c6a766e Wed Apr 24 2019 bazel_test: fix load of generate_toolchain_names (#2040) +56ecf4406adfd8917ac59ddd0ea008ff1d8f722a Wed Apr 24 2019 Ensure bazel paths don't leak in stdlib builds (#1945) +df1bb575ad1b35703e5b62fd0455907896c9eb32 Tue Apr 23 2019 update pin to bazel toolchains repo (#2038) +528f6faf83f85c23da367d61f784893d1b3bd72b Sat Apr 20 2019 GoCompilePkg: unified action for building a Go package (#2027) +... +``` + ### `$ bazel target` Pretty-print available targets in the current workspace. diff --git a/WORKSPACE b/WORKSPACE index 1e4df60..2a929ed 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -54,11 +54,11 @@ go_repository( importpath = "github.com/davecgh/go-spew", ) -go_repository( - name = "com_golang_google_genproto", - commit = "3273178ea4684acc4f512f7bef7349dd72db88f6", - importpath = "google.golang.org/genproto", -) +# go_repository( +# name = "com_golang_google_genproto", +# commit = "3273178ea4684acc4f512f7bef7349dd72db88f6", +# importpath = "google.golang.org/genproto", +# ) go_repository( name = "com_github_gregjones_httpcache", diff --git a/app.go b/app.go index a0f9c75..4821e7d 100644 --- a/app.go +++ b/app.go @@ -76,7 +76,7 @@ func NewApp() *App { if err != nil { log.Fatalf("Invalid bazel version %s, aborting: %v", version, err) } - err, exitCode := bazelutil.New().Invoke(args) + err, exitCode := bazelutil.New().Invoke(args, "") if exitCode != 0 { log.Printf("bazel exited with exitCode %d: %v", exitCode, err) os.Exit(exitCode) @@ -85,7 +85,7 @@ func NewApp() *App { } else { log.Println("BAZEL_VERSION not set, falling back to bazel on your PATH") args = append(args, c.Args().Tail()...) - err, exitCode := bazelutil.New().Invoke(args) + err, exitCode := bazelutil.New().Invoke(args, "") if exitCode != 0 { log.Printf("bazel exited with exitCode %d: %v", exitCode, err) os.Exit(exitCode) diff --git a/bazelutil/bazelutil.go b/bazelutil/bazelutil.go index 2dfe7cb..edbfc8e 100644 --- a/bazelutil/bazelutil.go +++ b/bazelutil/bazelutil.go @@ -2,6 +2,7 @@ package bazelutil import ( "fmt" + "io" "io/ioutil" "log" "os" @@ -49,12 +50,13 @@ func SetVersion(version string) error { } // Make Generic invocation to bazel -func (b *Bazel) Invoke(args []string) (error, int) { +func (b *Bazel) Invoke(args []string, dir string) (error, int) { + //fmt.Printf("\n%s %v\n", b.Name, args) cmd := exec.Command(b.Name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - cmd.Dir = "" + cmd.Dir = dir err := cmd.Run() @@ -171,3 +173,23 @@ func FirstTargetComplete(events []*bes.BuildEvent) *bes.TargetComplete { } return nil } + +// https://gist.github.com/elazarl/5507969 +func CopyFile(src, dst string) error { + s, err := os.Open(src) + if err != nil { + return err + } + // no need to check errors on read only file, we already got everything + // we need from the filesystem, so nothing can go wrong now. + defer s.Close() + d, err := os.Create(dst) + if err != nil { + return err + } + if _, err := io.Copy(d, s); err != nil { + d.Close() + return err + } + return d.Close() +} diff --git a/command/install/BUILD.bazel b/command/install/BUILD.bazel index f1f9b84..76da8da 100644 --- a/command/install/BUILD.bazel +++ b/command/install/BUILD.bazel @@ -6,6 +6,7 @@ go_library( importpath = "github.com/bzl-io/bzl/command/install", visibility = ["//visibility:public"], deps = [ + "//bazelutil:go_default_library", "//gh:go_default_library", "@com_github_dustin_go_humanize//:go_default_library", "@com_github_google_go_github//github:go_default_library", diff --git a/command/install/install.go b/command/install/install.go index 92a567f..e6cfa10 100644 --- a/command/install/install.go +++ b/command/install/install.go @@ -16,6 +16,7 @@ import ( "strings" "text/tabwriter" + "github.com/bzl-io/bzl/bazelutil" "github.com/bzl-io/bzl/gh" "github.com/google/go-github/github" "github.com/mitchellh/go-homedir" @@ -31,16 +32,56 @@ var Command = &cli.Command{ Name: "install", Usage: "Install a bazel release (or list release assets)", Flags: []cli.Flag{ - cli.BoolFlag{Name: "list"}, - cli.BoolFlag{Name: "force"}, + cli.BoolFlag{ + Name: "assets", + Usage: "List assets for a particular release (example: bazel install 0.24.1 --assets)", + }, + cli.BoolFlag{ + Name: "force", + Usage: "Force install", + }, cli.BoolFlag{Name: "without_jdk"}, + cli.StringFlag{ + Name: "bazel_source_dir", + Usage: "Location of the bazelbuild/bazel git repository (if building from source)", + Value: "$HOME/.cache/bzl/github.com/bazelbuild/bazel", + }, + }, + Action: func(c *cli.Context) error { + if err := execute(c); err != nil { + return cli.NewExitError(fmt.Sprintf("Install aborted: %v", err), 1) + } + return nil }, - Action: execute, } func execute(c *cli.Context) error { version := c.Args().First() + // + // Check if already installed + // + if version != "" && !c.Bool("assets") { + homeDir, err := homedir.Dir() + if err != nil { + return fmt.Errorf("Failed to get home directory: %v", err) + } + releaseDir := path.Join(homeDir, ".cache", "bzl", "release", version) + if FileExists(releaseDir) && !c.Bool("force") && version != "snapshot" { + return fmt.Errorf("%v is already installed (use --force to re-install it)", version) + } + } + + // + // if the version looks like a sha1 value, build from source instead. + // + if len(version) == 40 || version == "snapshot" { + return installFromSource(c, version) + } + + // + // List available releases + // listOptions := &github.ListOptions{} client := gh.Client() releases, _, err := client.Repositories.ListReleases(context.Background(), "bazelbuild", "bazel", listOptions) @@ -86,7 +127,7 @@ func listReleases(c *cli.Context, releases []*github.RepositoryRelease) error { func processRelease(c *cli.Context, release *github.RepositoryRelease) error { command := "install" - if c.Bool("list") { + if c.Bool("assets") { command = "assets" } @@ -120,8 +161,6 @@ func installRelease(c *cli.Context, release *github.RepositoryRelease) error { goarch = "x86_64" // is this right? } - //fmt.Printf("INSTALL %s %s %s: %s\n", goos, goarch, version, *release.Assets[0].BrowserDownloadURL) - // https://github.com/bazelbuild/bazel/releases/download/0.7.0/bazel-0.7.0-installer-darwin-x86_64.sh.sha256 baseDir := "/tmp" homeDir, err := homedir.Dir() if err == nil { @@ -184,28 +223,23 @@ func installRelease(c *cli.Context, release *github.RepositoryRelease) error { } if actual != expected[0] { - if c.Bool("force") { - log.Printf( - "%s: got sha256 '%s' but expected '%s' (from %s). Proceeding with install due to --force\n", - exe, - actual, - expected, - sha) - } else { - return cli.NewExitError( - fmt.Sprintf( - "%s: got sha256 '%s' but expected '%s' (from %s)\n", - exe, - actual, - expected, - sha), - 2) - } + return fmt.Errorf( + "%s: got sha256 '%s' but expected '%s' (from %s)\n", + exe, + actual, + expected, + sha, + ) } - log.Printf("Sha256 match %s: %s, proceeding with install\n", exe, sha) + log.Printf("Sha256 match %s: %s, proceeding with install...\n", exe, sha) + + if err := install(c, version, exe); err != nil { + return err + } + + log.Printf("Install was successful ('export BAZEL_VERSION=%s' to use it)", version) - install(c, version, exe) return nil } @@ -355,3 +389,107 @@ func install(c *cli.Context, version, filename string) error { cmd.Stderr = os.Stderr return cmd.Run() } + +func installFromSource(c *cli.Context, version string) error { + + // make sure bazel is checked out at the approprite location + bazelDir := os.ExpandEnv(c.String("bazel_source_dir")) + + // If the directory does not exist, create it and clone bazel. + if !FileExists(bazelDir) { + parentDir := filepath.Dir(bazelDir) + if err := os.MkdirAll(parentDir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to prepare bazel source directory: %v", err) + } + + // Execute a git clone + cloneArgs := []string{ + "clone", + "https://github.com/bazelbuild/bazel.git", + } + cmd := exec.Command("git", cloneArgs...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = parentDir + cmd.Env = os.Environ() + + if err := cmd.Run(); err != nil { + return fmt.Errorf("Failed to clone bazel: %v", err) + } + + } + + // + // Make sure we have the correct version checked out. + // + if version != "snapshot" { + // Pull most recent stuff + cmd := exec.Command("git", []string{ + "fetch", + // version, + }...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = bazelDir + cmd.Env = os.Environ() + + if err := cmd.Run(); err != nil { + return fmt.Errorf("Failed to fetch %q: %v", version, err) + } + + // Checkout to the requested commit + cmd = exec.Command("git", []string{ + "checkout", + version, + }...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = bazelDir + cmd.Env = os.Environ() + + if err := cmd.Run(); err != nil { + return fmt.Errorf("Failed to checkout %q: %v", version, err) + } + } + + // + // And run the build... + // + if err, exitCode := bazelutil.New().Invoke([]string{ + "build", + "//src:bazel", + }, bazelDir); err != nil { + return fmt.Errorf("Failed to build bazel: %v (exit %d)", err, exitCode) + } + + // + // Make the release dir + // + var releaseDir string + homeDir, err := homedir.Dir() + if err != nil { + return fmt.Errorf("Failed to read home dir: %v", err) + } + releaseDir = filepath.Join(homeDir, ".cache", "bzl", "release", version, "bin") + if err := os.MkdirAll(releaseDir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to prepare bazel release directory: %v", err) + } + + // + // Copy the binary + // + srcFile := filepath.Join(bazelDir, "bazel-bin", "src", "bazel") + dstFile := filepath.Join(releaseDir, "bazel") + if err := bazelutil.CopyFile(srcFile, dstFile); err != nil { + return fmt.Errorf("Failed to copy bazel binary to release directory: %v", err) + } + + // + // Set executable permisssions + // + os.Chmod(dstFile, 0755) + + log.Printf("Build+Install was successful ('export BAZEL_VERSION=%s' to use it)", version) + + return nil +} diff --git a/command/release/release.go b/command/release/release.go index 1c724e2..f8cb424 100644 --- a/command/release/release.go +++ b/command/release/release.go @@ -2,7 +2,6 @@ package release import ( "fmt" - "io" "io/ioutil" "log" "net/http" @@ -183,7 +182,7 @@ func copyFileToPlatformDir(c *cli.Context, assetDir string, platform string, fil } name += "-" + platformName platformFile := path.Join(platformDir, name) - err = CopyFile(filename, platformFile) + err = bazelutil.CopyFile(filename, platformFile) if err != nil { return "", err } @@ -235,26 +234,6 @@ func processBuildEvent(event *bes.BuildEvent) error { return nil } -// https://gist.github.com/elazarl/5507969 -func CopyFile(src, dst string) error { - s, err := os.Open(src) - if err != nil { - return err - } - // no need to check errors on read only file, we already got everything - // we need from the filesystem, so nothing can go wrong now. - defer s.Close() - d, err := os.Create(dst) - if err != nil { - return err - } - if _, err := io.Copy(d, s); err != nil { - d.Close() - return err - } - return d.Close() -} - func uploadRelease(c *cli.Context, files []string) (*github.RepositoryRelease, error) { owner := c.String("owner") if owner == "" { diff --git a/command/use/use.go b/command/use/use.go index 3940630..cb3606b 100644 --- a/command/use/use.go +++ b/command/use/use.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io/ioutil" + "log" "net/http" "net/http/httputil" "os" @@ -24,14 +25,21 @@ var Command = &cli.Command{ Usage: "Output a workspace rule for a given github repository", Flags: []cli.Flag{ cli.StringFlag{ - Name: "rule", - Usage: "Name of the rule to output", - Value: "http_archive", + Name: "rule", + Usage: "Name of the rule to output", + Value: "http_archive", + EnvVar: "BZL_USE_RULE_OUTPUT", }, cli.StringFlag{ - Name: "type", - Usage: "Type of asset to download (zip, tar)", - Value: "tar", + Name: "type", + Usage: "Type of asset to download (zip, tar)", + Value: "tar", + EnvVar: "BZL_USE_ASSET_TYPE", + }, + cli.StringFlag{ + Name: "history", + Usage: "Query recent commit log for the given branch (example: --history=master)", + EnvVar: "BZL_USE_COMMIT_HISTORY", }, }, Action: func(c *cli.Context) error { @@ -47,9 +55,6 @@ func execute(c *cli.Context) error { // Yes, it's one function. - var owner string - var repo string - arg1 := c.Args().First() if arg1 == "" { return fmt.Errorf("usage: bazel use [OWNER/]REPO TAG (example: bazel use rules_go 0.18.3)") @@ -64,14 +69,22 @@ func execute(c *cli.Context) error { tag := c.Args().Get(1) archiveType := c.String("type") - switch len(parts) { - case 1: + var owner string + var repo string + + if len(parts) == 1 { + // case like 'bazel use rules_go' owner = "bazelbuild" repo = parts[0] - case 2: + } else if len(parts) == 2 && len(parts[1]) == 40 { + // case like 'bazel use rules_go ef7cca8857f2f3a905b86c264737d0043c6a766e' + owner = "bazelbuild" + repo = parts[0] + } else if len(parts) == 2 { + // case like 'bazel use bazelbuild/rules_go ef7cca8857f2f3a905b86c264737d0043c6a766e' owner = parts[0] repo = parts[1] - default: + } else { return fmt.Errorf("want OWNER/REPO, got %q", c.Args().First()) } @@ -80,35 +93,60 @@ func execute(c *cli.Context) error { // client := gh.Client() - releases, _, err := client.Repositories.ListReleases(context.Background(), owner, repo, nil) - if err != nil { - return fmt.Errorf("Failed to list releases: %v", err) - } + var commits []*github.RepositoryCommit + var err error - if len(releases) == 0 { - return fmt.Errorf("No releases found for %s/%s", owner, repo) - } + if c.String("history") != "" { + log.Printf("Listing recent commit history for %s/%s...", owner, repo) + commits, _, err = client.Repositories.ListCommits(context.Background(), owner, repo, &github.CommitsListOptions{ + SHA: c.String("history"), + }) + if err != nil { + return fmt.Errorf("Failed to list commits for ref %q: %v", c.String("history"), err) + } - var release *github.RepositoryRelease - if tag == "" { - listReleases(releases) - return fmt.Errorf("please specify release tag (example: %s)", releases[0].GetTagName()) - } + if tag == "" { + listCommits(commits) + return fmt.Errorf("please specify a commit ID (example: %s)", commits[0].GetSHA()) + } + } else if len(tag) != 40 && !strings.HasPrefix(tag, "refs/") { - // Try and match desired release - // - for _, r := range releases { - if r.GetTagName() == tag { - release = r - break + releases, _, err := client.Repositories.ListReleases(context.Background(), owner, repo, nil) + if err != nil { + return fmt.Errorf("Failed to list releases: %v", err) } - } - if release == nil { - return fmt.Errorf("release %q not found in %s/%s", tag, owner, repo) + if len(releases) == 0 { + return fmt.Errorf("No releases found for %s/%s", owner, repo) + } + + var release *github.RepositoryRelease + + if tag == "" { + listReleases(releases) + return fmt.Errorf("please specify release tag (example: %s)", releases[0].GetTagName()) + } + + // Try and match desired release + // + for _, r := range releases { + if r.GetTagName() == tag { + release = r + break + } + } + + if release == nil { + return fmt.Errorf("release %q not found in %s/%s", tag, owner, repo) + } + + tag = release.GetTagName() } - tag = release.GetTagName() + if strings.HasPrefix(tag, "refs/heads") { + parts = strings.Split(tag, "/") + tag = parts[len(parts)-1] + } // // Normalize the archiveType @@ -219,6 +257,25 @@ func listReleases(releases []*github.RepositoryRelease) error { return nil } +func listCommits(commits []*github.RepositoryCommit) error { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + for _, commit := range commits { + date := (*commit.Commit.Author.Date).Format("Mon Jan 02 2006") + msg := scanFirstLine(commit.Commit.GetMessage()) + fmt.Fprintln(w, *commit.SHA, "\t", date, "\t", msg) + } + w.Flush() + return nil +} + +func scanFirstLine(in string) string { + scanner := bufio.NewScanner(strings.NewReader(in)) + for scanner.Scan() { + return scanner.Text() + } + return in +} + func printHttpArchive(wsName, owner, repo, tag, sha256, archiveType string) { stripVersion := tag if strings.HasPrefix(tag, "v") {