Skip to content

Commit a24a63e

Browse files
authored
Automatically run bazel mod tidy in @rules_go//go (#3927)
`bazel mod tidy` is run if supported and if relevant files change due to the command.
1 parent b2b5dbc commit a24a63e

File tree

5 files changed

+91
-7
lines changed

5 files changed

+91
-7
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
/examples/*/bazel-*
66
/.ijwb/
77
/tests/bcr/.ijwb/
8+
9+
MODULE.bazel.lock

docs/go/core/bzlmod.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ use_repo(
147147
)
148148
```
149149

150-
Bazel emits a warning if the `use_repo` statement is out of date or missing entirely (requires Bazel 6.2.0 or higher).
151-
The warning contains a `buildozer` command to automatically fix the `MODULE.bazel` file (requires buildozer 6.1.1 or higher).
150+
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`.
151+
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.
152152

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

go/private/polyfill_bazel_features.bzl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
_POLYFILL_BAZEL_FEATURES = """bazel_features = struct(
88
cc = struct(
99
find_cpp_toolchain_has_mandatory_param = {find_cpp_toolchain_has_mandatory_param},
10-
)
10+
),
11+
external_deps = struct(
12+
# WORKSPACE users have no use for bazel mod tidy.
13+
bazel_mod_tidy = False,
14+
),
1115
)
1216
"""
1317

go/tools/go_bin_runner/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# gazelle:exclude
22

3+
load("@io_bazel_rules_go_bazel_features//:features.bzl", "bazel_features")
34
load("//go:def.bzl", "go_binary", "go_library")
45
load("//go/private:common.bzl", "RULES_GO_IS_BZLMOD_REPO")
56
load("//go/private/rules:go_bin_for_host.bzl", "go_bin_for_host")
@@ -36,6 +37,7 @@ go_binary(
3637
x_defs = {
3738
"GoBinRlocationPath": "$(rlocationpath :go_bin_for_host)",
3839
"ConfigRlocationPath": "$(rlocationpath @bazel_gazelle_go_repository_config//:config.json)" if RULES_GO_IS_BZLMOD_REPO else "WORKSPACE",
40+
"HasBazelModTidy": str(bazel_features.external_deps.bazel_mod_tidy),
3941
},
4042
)
4143

go/tools/go_bin_runner/main.go

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package main
22

33
import (
4+
"crypto/sha256"
5+
"encoding/hex"
46
"encoding/json"
7+
"fmt"
8+
"io"
59
"log"
610
"os"
711
"os/exec"
812
"path/filepath"
13+
"sort"
14+
"strings"
915

1016
"github.com/bazelbuild/rules_go/go/runfiles"
1117
)
1218

1319
var GoBinRlocationPath = "not set"
1420
var ConfigRlocationPath = "not set"
21+
var HasBazelModTidy = "not set"
1522

1623
// Produced by gazelle's go_deps extension.
1724
type Config struct {
18-
GoEnv map[string]string `json:"go_env"`
25+
GoEnv map[string]string `json:"go_env"`
26+
DepsFiles []string `json:"dep_files"`
1927
}
2028

2129
func main() {
@@ -34,8 +42,39 @@ func main() {
3442
log.Fatal(err)
3543
}
3644

45+
hashesBefore, err := hashWorkspaceRelativeFiles(cfg.DepsFiles)
46+
if err != nil {
47+
log.Fatal(err)
48+
}
49+
3750
args := append([]string{goBin}, os.Args[1:]...)
38-
log.Fatal(runProcess(args, env, os.Getenv("BUILD_WORKING_DIRECTORY")))
51+
cwd := os.Getenv("BUILD_WORKING_DIRECTORY")
52+
err = runProcess(args, env, cwd)
53+
if err != nil {
54+
log.Fatal(err)
55+
}
56+
57+
hashesAfter, err := hashWorkspaceRelativeFiles(cfg.DepsFiles)
58+
if err != nil {
59+
log.Fatal(err)
60+
}
61+
62+
diff := diffMaps(hashesBefore, hashesAfter)
63+
if len(diff) > 0 {
64+
if HasBazelModTidy == "True" {
65+
bazel := os.Getenv("BAZEL")
66+
if bazel == "" {
67+
bazel = "bazel"
68+
}
69+
_, _ = fmt.Fprintf(os.Stderr, "\nrules_go: Running '%s mod tidy' since %s changed...\n", bazel, strings.Join(diff, ", "))
70+
err = runProcess([]string{bazel, "mod", "tidy"}, os.Environ(), cwd)
71+
if err != nil {
72+
log.Fatal(err)
73+
}
74+
} else {
75+
_, _ = fmt.Fprintf(os.Stderr, "\nrules_go: %s changed, please apply any buildozer fixes suggested by Bazel\n", strings.Join(diff, ", "))
76+
}
77+
}
3978
}
4079

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

118+
func hashWorkspaceRelativeFiles(relativePaths []string) (map[string]string, error) {
119+
workspace := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
120+
121+
hashes := make(map[string]string)
122+
for _, p := range relativePaths {
123+
h, err := hashFile(filepath.Join(workspace, p))
124+
if err != nil {
125+
return nil, err
126+
}
127+
hashes[p] = h
128+
}
129+
return hashes, nil
130+
}
131+
132+
// diffMaps returns the keys that have different values in a and b.
133+
func diffMaps(a, b map[string]string) []string {
134+
var diff []string
135+
for k, v := range a {
136+
if b[k] != v {
137+
diff = append(diff, k)
138+
}
139+
}
140+
sort.Strings(diff)
141+
return diff
142+
}
143+
144+
func hashFile(path string) (string, error) {
145+
f, err := os.Open(path)
146+
if err != nil {
147+
return "", err
148+
}
149+
defer f.Close()
150+
h := sha256.New()
151+
if _, err := io.Copy(h, f); err != nil {
152+
return "", err
153+
}
154+
return hex.EncodeToString(h.Sum(nil)), nil
155+
}
156+
79157
func runProcess(args, env []string, dir string) error {
80158
cmd := exec.Command(args[0], args[1:]...)
81159
cmd.Dir = dir
@@ -85,8 +163,6 @@ func runProcess(args, env []string, dir string) error {
85163
err := cmd.Run()
86164
if exitErr, ok := err.(*exec.ExitError); ok {
87165
os.Exit(exitErr.ExitCode())
88-
} else if err == nil {
89-
os.Exit(0)
90166
}
91167
return err
92168
}

0 commit comments

Comments
 (0)