From 63d933173b20b535420f6fc421d479860e81c973 Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Sat, 29 Aug 2026 09:10:14 +0100 Subject: [PATCH 1/5] Update codelab a bit --- docs/codelabs/puku.md | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/docs/codelabs/puku.md b/docs/codelabs/puku.md index 2670c8a6f..e12ce18b4 100644 --- a/docs/codelabs/puku.md +++ b/docs/codelabs/puku.md @@ -54,17 +54,10 @@ Define a valid Puku version number as a build configuration string in `.plzconfi ``` [BuildConfig] -puku-version = "1.17.0" +puku-version = "1.17.1" ``` -Uncomment and edit the following lines in your `.plzconfig` to set up `please` version: - -``` -[please] -version = 17.22.0 -``` - -Configure a Please alias for Puku (optional but convenient): +Configure a Please alias for Puku: ``` [Alias "puku"] @@ -135,14 +128,6 @@ Path = /usr/local/go/bin:/usr/local/bin:/usr/bin:/bin **Note:** On Windows, use `where.exe go` to find the Go installation path. -### Installing the Go standard library (Go 1.20+) - -From Go version 1.20 onwards, the standard library is no longer included by default with the Go distribution. You must install it manually: - -```bash -GODEBUG="installgoroot=all" go install std -``` - ## Adding and updating modules Duration: 5 @@ -172,14 +157,6 @@ Now add the dependency with `go get`: go get github.com/google/uuid ``` -Sync the changes to `third_party/go/BUILD`: - -```bash -plz puku sync -w -``` - -This creates a `go_repo()` rule in `third_party/go/BUILD` for the `uuid` module. You may need to create the `third_party/go/BUILD` file if it doesn't exist. - ### Creating the BUILD file Create `src/hello/BUILD`: @@ -210,7 +187,7 @@ plz run //src/hello To update a module to a specific version: ```bash -GOTOOLCHAIN=local go get github.com/google/uuid@v1.6.0 +GOTOOLCHAIN=local go get github.com/google/uuid@v1.5.0 plz puku sync -w ``` From d65802b659d4c145954b84b8a8daa100ae738ed5 Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Sat, 29 Aug 2026 09:20:54 +0100 Subject: [PATCH 2/5] Have the thing just write the dratted filegroup --- docs/codelabs/puku.md | 11 ++++-- src/plzinit/init_test.go | 52 ++++++++++++++++++++++++++ src/plzinit/plugin_go.go | 79 ++++++++++++++++++++++++++++++++++------ test/plugins/BUILD | 12 ++++++ 4 files changed, 139 insertions(+), 15 deletions(-) diff --git a/docs/codelabs/puku.md b/docs/codelabs/puku.md index e12ce18b4..7da277992 100644 --- a/docs/codelabs/puku.md +++ b/docs/codelabs/puku.md @@ -78,9 +78,10 @@ remote_file( ) ``` -Configure the Go plugin to point at your go.mod (recommended). Create a repo-root `BUILD` with a filegroup for go.mod: +Because you ran `plz init plugin go` in a directory that already had a `go.mod`, the Go plugin +has been pointed at it for you. `plz init` will have written a filegroup exporting `go.mod` into +your repo-root `BUILD` file: -1) Add a filegroup for go.mod at `BUILD` in repo root: ```python filegroup( name = "gomod", @@ -89,14 +90,16 @@ filegroup( ) ``` -2) Update your `.plzconfig`: +along with the corresponding `ModFile` setting in your `.plzconfig`: + ``` [Plugin "go"] -Target = //plugins:go ModFile = //:gomod ``` This lets Puku use standard `go get` to resolve modules, then sync them into `third_party/go/BUILD`. +If you're adding Please to a repo that didn't have a `go.mod` at the time you ran `plz init plugin go`, +you'll need to add those two pieces yourself. ### Configuring the PATH for Go diff --git a/src/plzinit/init_test.go b/src/plzinit/init_test.go index 7c74e35ba..b253449a6 100644 --- a/src/plzinit/init_test.go +++ b/src/plzinit/init_test.go @@ -2,6 +2,7 @@ package plzinit import ( "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -32,3 +33,54 @@ func TestInitPleasings(t *testing.T) { assert.Equal(t, expectedRule, string(b)) } + +const expectedGoModFilegroup = `filegroup( + name = "gomod", + srcs = ["go.mod"], + visibility = ["PUBLIC"], +) +` + +func TestInitGoMod(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example_module\n"), 0644)) + + label, err := initGoMod(dir) + require.NoError(t, err) + assert.Equal(t, "//:gomod", label) + + b, err := os.ReadFile(filepath.Join(dir, "BUILD")) + require.NoError(t, err) + assert.Equal(t, expectedGoModFilegroup, string(b)) +} + +func TestInitGoModWithoutGoMod(t *testing.T) { + dir := t.TempDir() + + label, err := initGoMod(dir) + require.NoError(t, err) + assert.Equal(t, "", label) + assert.False(t, fs.FileExists(filepath.Join(dir, "BUILD"))) +} + +func TestInitGoModReusesExistingFilegroup(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example_module\n"), 0644)) + + existing := `filegroup( + name = "modfile", + srcs = ["go.mod"], + visibility = ["PUBLIC"], +) +` + require.NoError(t, os.WriteFile(filepath.Join(dir, "BUILD"), []byte(existing), 0644)) + + label, err := initGoMod(dir) + require.NoError(t, err) + assert.Equal(t, "//:modfile", label) + + // The existing build file should be left alone. + b, err := os.ReadFile(filepath.Join(dir, "BUILD")) + require.NoError(t, err) + assert.Equal(t, existing, string(b)) +} diff --git a/src/plzinit/plugin_go.go b/src/plzinit/plugin_go.go index 2f7eb67ce..413934bd0 100644 --- a/src/plzinit/plugin_go.go +++ b/src/plzinit/plugin_go.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "path/filepath" "runtime" "strings" @@ -13,7 +14,12 @@ import ( "github.com/thought-machine/please/src/fs" ) -const buildFilePath = "third_party/go/BUILD" +const ( + buildFilePath = "third_party/go/BUILD" + rootBuildFileName = "BUILD" + goModFileName = "go.mod" + goModFilegroup = "gomod" +) type goVersionResp = []struct { Version string `json:"version"` @@ -44,18 +50,18 @@ func getLatestGoVersion() (string, error) { return runtime.Version(), nil } -func parseBuildFile() (*build.File, error) { - bs, _ := os.ReadFile(buildFilePath) - return build.Parse(buildFilePath, bs) +func parseBuildFile(path string) (*build.File, error) { + bs, _ := os.ReadFile(path) + return build.Parse(path, bs) } -func saveFile(buildFile *build.File) error { +func saveFile(buildFile *build.File, path string) error { bs := build.Format(buildFile) - if err := fs.EnsureDir(buildFilePath); err != nil { + if err := fs.EnsureDir(path); err != nil { return err } - return os.WriteFile(buildFilePath, bs, 0666) + return os.WriteFile(path, bs, 0666) } func initGo() (map[string]string, error) { @@ -69,7 +75,7 @@ func initGo() (map[string]string, error) { return nil, err } - buildFile, err := parseBuildFile() + buildFile, err := parseBuildFile(buildFilePath) if err != nil { return nil, fmt.Errorf("failed to parse %s: %w", buildFilePath, err) } @@ -88,14 +94,65 @@ func initGo() (map[string]string, error) { buildFile.Stmt = append(buildFile.Stmt, stdLib("std")) } - if err := saveFile(buildFile); err != nil { + if err := saveFile(buildFile, buildFilePath); err != nil { return nil, err } - return map[string]string{ + config := map[string]string{ "GoTool": toolchainRule, "STDLib": stdRule, - }, nil + } + + // Point the plugin at the repo's go.mod, if it has one. Without this, tools like puku + // can't reconcile the module requirements against the third party build file. + modFile, err := initGoMod(".") + if err != nil { + return nil, err + } + if modFile != "" { + config["ModFile"] = modFile + } + + return config, nil +} + +// initGoMod exports the repo's go.mod via a filegroup in the root build file, returning the +// label of that filegroup. It returns an empty label if the repo doesn't have a go.mod. +func initGoMod(dir string) (string, error) { + if _, ok := findGoModule(dir); !ok { + return "", nil + } + + path := filepath.Join(dir, rootBuildFileName) + buildFile, err := parseBuildFile(path) + if err != nil { + return "", fmt.Errorf("failed to parse %s: %w", path, err) + } + + // Reuse an existing filegroup if the repo already exports go.mod under another name. + for _, rule := range buildFile.Rules("filegroup") { + for _, src := range rule.AttrStrings("srcs") { + if src == goModFileName { + return "//:" + rule.Name(), nil + } + } + } + + buildFile.Stmt = append(buildFile.Stmt, modFilegroup(goModFilegroup)) + if err := saveFile(buildFile, path); err != nil { + return "", err + } + + return "//:" + goModFilegroup, nil +} + +func modFilegroup(name string) *build.CallExpr { + r := build.NewRule(&build.CallExpr{}) + r.SetKind("filegroup") + r.SetAttr("name", &build.StringExpr{Value: name}) + r.SetAttr("srcs", &build.ListExpr{List: []build.Expr{&build.StringExpr{Value: goModFileName}}}) + r.SetAttr("visibility", &build.ListExpr{List: []build.Expr{&build.StringExpr{Value: "PUBLIC"}}}) + return r.Call } func goToolchain(name, version string) *build.CallExpr { diff --git a/test/plugins/BUILD b/test/plugins/BUILD index 8654a905e..7fde5fd42 100644 --- a/test/plugins/BUILD +++ b/test/plugins/BUILD @@ -94,6 +94,18 @@ plugin_e2e_test( plz_command = "cp .plzconfig .foo; mv .foo .plzconfig; plz init plugin go && plz query config plugin.go.gotool > go.conf && plz query config plugin.go.stdlib >> go.conf", ) +# If the repo has a go.mod, init should export it and point the plugin at it, so that puku +# has something to sync the third party build file against. +plugin_e2e_test( + name = "init_go_modfile_test", + expect_output_contains = { + "go.conf": "//:gomod", + "BUILD": "filegroup*gomod*go.mod*PUBLIC", + }, + # Need to break hard link here so init_plugin_test's actual .plzconfig doesn't get updated. + plz_command = "cp .plzconfig .foo; mv .foo .plzconfig; echo 'module example_module' > go.mod; plz init plugin go && plz query config plugin.go.modfile > go.conf", +) + # Test that the target is created in plugins/BUILD plugin_e2e_test( name = "init_create_target_test", From df020445332eff7a0eca39cc108caeaa62b5d56f Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Sat, 29 Aug 2026 09:24:16 +0100 Subject: [PATCH 3/5] rm GOTOOLCHAIN=local --- docs/codelabs/puku.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/codelabs/puku.md b/docs/codelabs/puku.md index 7da277992..72031f31b 100644 --- a/docs/codelabs/puku.md +++ b/docs/codelabs/puku.md @@ -190,14 +190,14 @@ plz run //src/hello To update a module to a specific version: ```bash -GOTOOLCHAIN=local go get github.com/google/uuid@v1.5.0 +go get github.com/google/uuid@v1.5.0 plz puku sync -w ``` To update to the latest version: ```bash -GOTOOLCHAIN=local go get -u github.com/google/uuid +go get -u github.com/google/uuid plz puku sync -w ``` From a662ae62cafd357c7a033da1b8ebe4477be8ff0f Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Sat, 29 Aug 2026 09:27:15 +0100 Subject: [PATCH 4/5] Version bump --- ChangeLog | 4 ++++ VERSION | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 5fdc08341..d16b69561 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Version 17.32.1 +--------------- + * `plz init plugin go` now writes a filegroup for `go.mod` and sets `modfile`. + Version 17.32.0 --------------- * `plz_sandbox`: Add UID/GID range mapping, file mounts, configurable diff --git a/VERSION b/VERSION index 0e1b06b1b..7404713aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -17.32.0 +17.32.1 From 7172f5334d2bddeccdf132e8f9f76cc656441238 Mon Sep 17 00:00:00 2001 From: Peter Ebden Date: Sat, 29 Aug 2026 09:49:59 +0100 Subject: [PATCH 5/5] Add --wd=. bit which affects how plz puku fmt works --- docs/codelabs/puku.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/codelabs/puku.md b/docs/codelabs/puku.md index 72031f31b..34234fe21 100644 --- a/docs/codelabs/puku.md +++ b/docs/codelabs/puku.md @@ -61,7 +61,7 @@ Configure a Please alias for Puku: ``` [Alias "puku"] -Cmd = run //third_party/binary:puku -- +Cmd = run --wd=. //third_party/binary:puku -- PositionalLabels = true Desc = A tool to update BUILD files in Go packages ```