Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically run bazel mod tidy in @rules_go//go #3927

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}