Please 17.0.0

This document describes the developments and breaking changes in v17, and how to migrate. You can find the complete changelog here.

Plugins

The plugin API is a powerful new way to extend Please to support new languages and technologies. It offers rule authors greater flexibility in how they configure, distribute, and version their rules. In v17, Plugins are becoming the default, replacing all the built-in language rules.

The language plugins are hosted in the please-build github organisation, where we better collaborate with external contributors. If you have a plugin you'd like to contribute, please get in touch!

To migrate from the built-in rules, simply run plz init plugin [plugin name].

Golang

The Golang plugin has remained mostly compatible with the builtin rules, however there are a lot of new features available!

Minor changes to import paths

Previously, the Go rules would let you import a package using two different import paths, example.com/owner/repo/package, and example.com/owner/repo/package/package. This was a bug that has now been resolved. To help migration, use the LegacyImports config option, under [Plugin "go"] in your .plzconfig file.

Improvements to third party modules

One of the major shortcomings of using Please when compared to the standard go tooling is the toil involved in managing third party dependencies. The go_module() rule has served us well, but it has some major shortcomings.

Cyclic dependencies between modules are hard to represent as we compile the entire module in one go. It's possible to work around this by downloading the module with one rule and compiling it in parts, however this requires a lot of toil, and can get complicated quickly:

      
      
    go_mod_download(
        name = "go-opentelemetry_download",
        module = "go.opentelemetry.io/otel",
        version = "v1.11.1",
    )

    go_module(
        name = "go-opentelemetry",
        download = ":go-opentelemetry_download",
        install = [
            ".",
            "baggage",
            "internal",
            "internal/baggage",
            "internal/attribute",
            "internal/global",
            "propagation",
            "semconv/internal",
            "semconv/v1.10.0",
        ],
        module = "go.opentelemetry.io/otel",
        deps = [
            ":go-opentelemetry.trace",
            ":logr",
            ":stdr",
        ],
    )

    # split off due to go-opentelemetry's circular dependency with go-opentelemetry.trace
    go_module(
        name = "go-opentelemetry_1",
        download = ":go-opentelemetry_download",
        install = [
            "attribute",
            "codes",
        ],
        module = "go.opentelemetry.io/otel",
    )
      
    

Additionally, dependencies between modules must be explicitly defined, along with the packages that we need from that module. This results in a slow, iterative cycle of building, and waiting for the next compiler error to figure out which package or module we're missing. We also often end up relying on wildcards, compiling more of t he module than we actually need because figuring out the set of packages we actually need is so hard.

Compiling large modules with a single rule like this can also take a long time. Some modules such and the AWS SDK can take upwards for 4 minutes to compile. This happens in a single build task so often can't take advantage of multicore systems.

In the Go plugin, we have introduced a brand new build rule called go_repo(), which uses a wildly different paradigm:

      
      
    go_repo(module="github.com/google/go-cmp", version="v0.5.9")
    go_repo(module="github.com/stretchr/testify", version="v1.8.0")
    go_repo(module="go.opentelemetry.io/otel/trace", version="v1.11.1")
    go_repo(module="go.opentelemetry.io/otel", version="v1.11.1")
    go_repo(module="github.com/go-logr/stdr", version="v1.2.2")
    go_repo(module="github.com/pmezard/go-difflib", version="v1.0.0")
    go_repo(module="github.com/stretchr/objx", version="v0.4.0")
    go_repo(module="gopkg.in/yaml.v3", version="v3.0.1")
    go_repo(module="gopkg.in/check.v1", version="v0.0.0-20161208181325-20d25e280405")
    go_repo(module="github.com/go-logr/logr", version="v1.2.3")
    go_repo(module="github.com/davecgh/go-spew", version="v1.1.1")
      
    

By treating third party modules as subrepos, we can generate individual build targets for each package! This eliminates all these problems with the existing go_module() rules:

Labels follow the following format: ///third_party/go/github.com_module_name//package/name. To depend on github.com/stretchr/testify/assert, use ///third_party/go/github.com_stretchr_testify//assert.

As these build labels are quite cumbersome, you may pass a name and install list to go_repo():

      
      
    go_repo(
        name = "testify",
        install = ["assert", "require"],
        module = "github.com/stretchr/testify",
        version = "v1.8.0",
    )
      
    

You may then use //third_party/go:testify as an alias for ///third_party/go/github.com_stretchr_testify//assert and ///third_party/go/github.com_stretchr_testify//require.

Improvements to the toolchain and standard library

In go v1.20, they stopped distributing the SDK in binary form with the SDK. The Go rules depend on these binaries. The simplest way to work with go v1.20 is to use the go_toolchain() rule, which now compiles the SDK for you.

Under the hood, the go_toolchain() rule is using go_stdlib(), which compiles the standard library as a normal build target. This detail lets us support additional features such as cross compilation, build modes, and race detection by allowing Please to re-build the standard library as needed. For example, plz build -o plugin.go.race:true //src:main, will build that target with race detection enabled. There's also go.buildmode for different build modes. Both of these can also be configured in [Plugin "go"] in your .plzconfig.

Go package driver

The Go packages driver is an experimental tool that provides a bridge between go tooling and build systems like Please. Tools like gopls, gosec, and many of the linters included in golangci-lint will use this.

Proto

In v17 we have implemented a new approach to the proto rules. The original proto rules supported a fixed set of languages, and extending them with new plugins proved quite difficult. The new proto rules provide an sdk for adding new protoc plugins for new languages and targets e.g. gRPC gateways.

So far, Golang and Python are supported, however there are plans to support C++ and Java in the future.

The new proto rules don't currently support the same set of languages. For a drop in replacement, you may use ///proto//build_defs:legacy. These rules use the original configuration under [Proto], so there's no need to update your build file.

Persistent workers

Due to poor adoption, persistent workers have been removed as of v17. Users migrating to the Java plugin should configure JavacTool instead of JavacWorker.

Changes to glob()

The plz generate subcommand can be used to link generated sources into the source tree which can help with integrating with tooling and IDEs, however if you're using glob in that directory, this may cause build failures. In v17, glob will omit symlinks by default. If you want to include symlinks, pass glob(include_symlinks = True, ...).

Glob is often used to find files on disk to reduce toil in keeping build rules up to date, however it's often easy to get the glob pattern wrong. In this case, it used to silently return an empty list which can lead to confusing errors. In v17, glob will now throw an exception. If you want to allow glob to match empty files, pass glob(allow_empty = True, ...).