Skip to content
Open
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
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17.32.0
17.32.1
44 changes: 12 additions & 32 deletions docs/codelabs/puku.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,14 @@ 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"]
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
```
Expand All @@ -85,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",
Expand All @@ -96,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

Expand Down Expand Up @@ -135,14 +131,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

Expand Down Expand Up @@ -172,14 +160,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`:
Expand Down Expand Up @@ -210,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.6.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
```

Expand Down
52 changes: 52 additions & 0 deletions src/plzinit/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package plzinit

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -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))
}
79 changes: 68 additions & 11 deletions src/plzinit/plugin_go.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"

Expand All @@ -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"`
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
Expand All @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions test/plugins/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading