Description
(this is a joint proposal by @dominikh and @mvdan)
Abstract
We describe a file format for specifying a list of build configurations, where build configurations are characterized by environment variables and command-line arguments for the build system.
Background
Go has the notion of build tags, which control the set of files that make up a package under a given configuration. Tags can be user-defined and specified with the -tags
flag, or they may be defined by the build system itself, bound to parameters such as the operating system and CPU architecture, overridable with environment variables such as GOOS
and GOARCH
.
Due to these tags, a single import path may effectively refer to a set of packages, each package differentiated by the active tags. While referring to a single build configuration is straightforward (by specifying the correct tags and environment variables), it is much more difficult to explore all relevant build configurations.
Many tools, however, would like to know the list of relevant build configurations, either for correctness reasons (static analysis) or for UI reasons (IDEs, …). A CI pipeline should execute the tests of all relevant build configurations, not just one. Static analysis tools such as staticcheck should analyze all relevant build configurations to detect issues under all viable code paths. Detecting unused functions needs to observe function calls under all relevant build configurations, not just one. A language server such as gopls needs to be able to provide accurate code intelligence and offer the user a list of build configurations to choose from. The list goes on.
Naively iterating through all unique combinations of tags quickly leads to combinatorial explosion. Go supports a dozen operating systems on a dozen CPU architectures, can be used with and without cgo support, and makes use of tags such as netgo
and timetzdata
to affect how the standard library gets built. On top of this, users define their own tags, for example for debug-only code. This results in thousands of possible build configurations, most of them unique due to a transitive dependency on the runtime package.
In practice, however, only a small fraction of possible build configurations are actually relevant to the user. For example, a project may only be interested in actively supporting Linux and Windows on amd64, never use any of the standard library's tags, and only differentiate their build based on whether it is a debug build or not. This reduces thousands of build configurations down to four.
Since many tools would benefit from knowing the list of relevant build configurations, and because it cannot be determined automatically, it is desirable to be able to explicitly list relevant build configurations in a format that can be shared between different tools.
Proposal
We propose a file format, as well as best practices for using files in this format.
File format
The format is line-based, with each non-empty line describing a build configuration. A build configuration consists of a name, a (possibly empty) set of environment variable assignments, followed by a (possibly empty) set of command-line arguments.
Names are separated from environment variables and command-line arguments by a colon followed by a space. Names can consist of Unicode letters, Unicode numbers, dashes (-
) and underscores (_
). Names must begin with a Unicode letter or Unicode number.
Quoted strings may be used for elements containing whitespace. The specific format for quoted strings will match that of GOFLAGS, which is currently TBD (see #26849.) Names must not use quoted strings.
Syntactically valid examples include
windows-release: GOOS=windows GOARCH=amd64 -tags=debug,feature1 -gcflags=-N
b1: GOOS=windows GOARCH=amd64
debug-feature: -tags=debug,feature1
debug: -tags=debug
A line is split into environment variables and command-line arguments at the first element that is not a valid environment variable assignment. Usually, this would be an element that begins with a dash, or one that does not contain an equal sign.
The process environment described in a build configuration is merged with the existing environment, with the existing environment taking precedence. Command-line arguments will be passed to the build system verbatim, but tools are free to add additional arguments, and it is not specified whether tools pass their own arguments before or after the arguments specified in a build configuration.
The format itself puts no restrictions on allowed environment variables or command-line arguments. However, it is strongly advised not to modify the workspace itself. That is, variables such as GOPATH
or GO111MODULE
should not be modified. It is assumed that build configurations are executed in the context of an already configured workspace. Furthermore, command-line arguments should only be used for passing flags and their values and not, for example, to specify additional packages.
Tools
Different tools have different requirements and may make use of files in this format in different ways, but they should keep the following points in mind.
Tools should allow specifying a file, but they may look for a default file name.
There are various reasons why a project may use more than one build configuration file. For example, it may want to build binary releases for only a small set of first class platforms, while still running static analysis for more platforms, to future-proof their code. Or, a parameter that meaningfully differentiates binary builds does not contribute anything to static analysis: compiling with and without -gcflags=-N
will produce meaningfully different binaries, but statically analysing both versions would be a waste of time. Or it may have different lists of platforms to execute tests on for CI and local development. Many other reasons exist.
Therefore, tools should allow selection of the file to use.
It is, however, desirable to agree on a default file name to look for, so that every tool needn't be configured manually, especially for projects that can make do with a single file, and so that tools can use build configuration files by default. The default file is located at the top of the project, for example the top of a Go module. For build systems that do not have a notion of projects, such as Go in GOPATH mode, we don't define a default location at this moment.
Most tools should deduplicate build configurations to avoid unnecessary work
For most tools, it makes no sense to execute duplicate configurations. However, duplicate configurations may occur from concatenating files, or from on-the-fly generators that do not deduplicate configurations themselves. Therefore, tools should only execute unique configurations.
Tools should allow using the current build configuration
While tools may use existing build configuration files by default, they should also allow executing the active build configuration as specified by the user's current environment. In its simplest form this would be by ignoring build configuration files and operating as tools did before implementing this proposal. It may also take the form of manually or automatically appending the current configuration to the list of configurations to execute. For example, when executing staticcheck, the user would assume that their active configuration will be used, regardless of other configurations that may be used as well.
Tools may allow using specific build configurations
Depending on the tool, it may be useful to allow selection of individual build configurations, for example by their name.