Skip to content

Commit

Permalink
Automatically run bazel mod tidy in @rules_go//go (#3927)
Browse files Browse the repository at this point in the history
`bazel mod tidy` is run if supported and if relevant files change due to the command.
  • Loading branch information
fmeum authored Jun 11, 2024
1 parent b2b5dbc commit a24a63e
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
/examples/*/bazel-*
/.ijwb/
/tests/bcr/.ijwb/

MODULE.bazel.lock
4 changes: 2 additions & 2 deletions docs/go/core/bzlmod.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ use_repo(
)
```

Bazel emits a warning if the `use_repo` statement is out of date or missing entirely (requires Bazel 6.2.0 or higher).
The warning contains a `buildozer` command to automatically fix the `MODULE.bazel` file (requires buildozer 6.1.1 or higher).
When using Bazel 7.1.1 or higher, the [`@rules_go//go` target](#using-a-go-sdk) automatically updates the `use_repo` call whenever the `go.mod` file changes, using `bazel mod tidy`.
With older versions of Bazel, a warning with a fixup command will be emitted during a build if the `use_repo` call is out of date or missing.

Alternatively, you can specify a module extension tag to add an individual dependency:

Expand Down
6 changes: 5 additions & 1 deletion go/private/polyfill_bazel_features.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
_POLYFILL_BAZEL_FEATURES = """bazel_features = struct(
cc = struct(
find_cpp_toolchain_has_mandatory_param = {find_cpp_toolchain_has_mandatory_param},
)
),
external_deps = struct(
# WORKSPACE users have no use for bazel mod tidy.
bazel_mod_tidy = False,
),
)
"""

Expand Down
2 changes: 2 additions & 0 deletions go/tools/go_bin_runner/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# gazelle:exclude

load("@io_bazel_rules_go_bazel_features//:features.bzl", "bazel_features")
load("//go:def.bzl", "go_binary", "go_library")
load("//go/private:common.bzl", "RULES_GO_IS_BZLMOD_REPO")
load("//go/private/rules:go_bin_for_host.bzl", "go_bin_for_host")
Expand Down Expand Up @@ -36,6 +37,7 @@ go_binary(
x_defs = {
"GoBinRlocationPath": "$(rlocationpath :go_bin_for_host)",
"ConfigRlocationPath": "$(rlocationpath @bazel_gazelle_go_repository_config//:config.json)" if RULES_GO_IS_BZLMOD_REPO else "WORKSPACE",
"HasBazelModTidy": str(bazel_features.external_deps.bazel_mod_tidy),
},
)

Expand Down
84 changes: 80 additions & 4 deletions go/tools/go_bin_runner/main.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package main

import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"

"github.com/bazelbuild/rules_go/go/runfiles"
)

var GoBinRlocationPath = "not set"
var ConfigRlocationPath = "not set"
var HasBazelModTidy = "not set"

// Produced by gazelle's go_deps extension.
type Config struct {
GoEnv map[string]string `json:"go_env"`
GoEnv map[string]string `json:"go_env"`
DepsFiles []string `json:"dep_files"`
}

func main() {
Expand All @@ -34,8 +42,39 @@ func main() {
log.Fatal(err)
}

hashesBefore, err := hashWorkspaceRelativeFiles(cfg.DepsFiles)
if err != nil {
log.Fatal(err)
}

args := append([]string{goBin}, os.Args[1:]...)
log.Fatal(runProcess(args, env, os.Getenv("BUILD_WORKING_DIRECTORY")))
cwd := os.Getenv("BUILD_WORKING_DIRECTORY")
err = runProcess(args, env, cwd)
if err != nil {
log.Fatal(err)
}

hashesAfter, err := hashWorkspaceRelativeFiles(cfg.DepsFiles)
if err != nil {
log.Fatal(err)
}

diff := diffMaps(hashesBefore, hashesAfter)
if len(diff) > 0 {
if HasBazelModTidy == "True" {
bazel := os.Getenv("BAZEL")
if bazel == "" {
bazel = "bazel"
}
_, _ = fmt.Fprintf(os.Stderr, "\nrules_go: Running '%s mod tidy' since %s changed...\n", bazel, strings.Join(diff, ", "))
err = runProcess([]string{bazel, "mod", "tidy"}, os.Environ(), cwd)
if err != nil {
log.Fatal(err)
}
} else {
_, _ = fmt.Fprintf(os.Stderr, "\nrules_go: %s changed, please apply any buildozer fixes suggested by Bazel\n", strings.Join(diff, ", "))
}
}
}

func parseConfig() (Config, error) {
Expand Down Expand Up @@ -76,6 +115,45 @@ func getGoEnv(goBin string, cfg Config) ([]string, error) {
return append(env, "GOROOT="+goRoot), nil
}

func hashWorkspaceRelativeFiles(relativePaths []string) (map[string]string, error) {
workspace := os.Getenv("BUILD_WORKSPACE_DIRECTORY")

hashes := make(map[string]string)
for _, p := range relativePaths {
h, err := hashFile(filepath.Join(workspace, p))
if err != nil {
return nil, err
}
hashes[p] = h
}
return hashes, nil
}

// diffMaps returns the keys that have different values in a and b.
func diffMaps(a, b map[string]string) []string {
var diff []string
for k, v := range a {
if b[k] != v {
diff = append(diff, k)
}
}
sort.Strings(diff)
return diff
}

func hashFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

func runProcess(args, env []string, dir string) error {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = dir
Expand All @@ -85,8 +163,6 @@ func runProcess(args, env []string, dir string) error {
err := cmd.Run()
if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
} else if err == nil {
os.Exit(0)
}
return err
}

0 comments on commit a24a63e

Please sign in to comment.