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

x/tools/gopls: use cases for build tags #33389

Open
stamblerre opened this issue Jul 31, 2019 · 19 comments

Comments

@stamblerre
Copy link
Contributor

commented Jul 31, 2019

What use cases do users have for build tags? Please post your own examples.

To start:

  • OS-specific
  • Certain package trees need specific build tags (e.g. WASM)
  • Some companies use build tags for things like integration testing, different environments etc.
  • +build ignore file main files

@gopherbot gopherbot added this to the Unreleased milestone Jul 31, 2019

@gopherbot gopherbot added the gopls label Jul 31, 2019

@vcabbage

This comment has been minimized.

Copy link
Member

commented Jul 31, 2019

I have used build tags in a library so that it only depends on stdlib by default but usage of 3rd party libraries like github.com/pkg/errors can be enabled via build tags.

@aykevl

This comment has been minimized.

Copy link

commented Jul 31, 2019

In TinyGo (which should eventually support IDEs), we use build tags to select various things:

  • chip vendor (stm32, nrf), chip (stm32f103xx, nrf52840), and board names (bluepill, pca10056)
  • which GC style is being used (currently gc.none, gc.leaking, and gc.conservative)

To manage all this, special JSON files are used that can be selected with a single compiler flag (similar to GCC spec files).

More uses may be added in the future.

@jadr2ddude

This comment has been minimized.

Copy link

commented Jul 31, 2019

Build tags are commonly used for go generate - so that generators can be stored alongside the package in which they are used

@muirrn

This comment has been minimized.

Copy link

commented Jul 31, 2019

We have used build tags mainly in two places:

  • OS specific system calls
  • New Go language or standard library features (e.g. // +build go1.13 in one file that implements something using features/code only available in 1.13, and // +build !go1.13 in the other file with the 1.12 implementation).

That being said, we don't use build tags very much.

@seebs

This comment has been minimized.

Copy link
Contributor

commented Jul 31, 2019

Two files, named something like paranoia.go and nop_paranoia.go, which both define the same const bool, one setting it to true and one to false. Then elsewhere in the code:

if paranoia {
        // extra testing
}

This way, we can run with the extra testing sometimes, but omit expensive tests entirely at compile time. (Some of the tests are inside hot loops, so being able to get rid of even the cost of the branch is actually relevant.)

Have also used them for what would typically be #defines in C; functionality changes which are innately program-wide and must be resolved at compile time because array sizes (for instance) depend on them.

I've seen a package (hajimehoshi/ebiten) ship with a number of examples in subdirectories, marked with +build example so they don't get built by default but are available for users to experiment with.

@gdey

This comment has been minimized.

Copy link

commented Jul 31, 2019

I used build tags to add enhanced debugging that goes into tight loops. Using an if statement much like @seebs .

One file id debug.go and the other debug_flg.go

They each define a debug constant.

then if I need to debug tight loops I'll eighter flip the const in debug.go from false to true (this is what I've mostly been doing this, because usually just want a single package) or build with the debug flag, which will cause debug_flg to compile and not debug.go to compile.

This being said, this is probably not the best way to do this.

@neild

This comment has been minimized.

Copy link
Contributor

commented Jul 31, 2019

In google.golang.org/protobuf:

  • // +build ignore to exclude an expensive integration test from go test ./....
  • // +build purego to select a slower implementation of various operations that doesn't depend on the unsafe package.
  • // +build go1.12 to use (*reflect.Value).MapRange when available.
  • // +build protoreflect to disable fast-path implementations of various operations to allow testing the reflection-based fallbacks.
  • // +build proto1_legacy to enable support for some ancient features nobody should use. Used so we can run tests for that support.
@abhinav

This comment has been minimized.

Copy link
Contributor

commented Aug 1, 2019

We use build tags to separate the almost identical input and output files of
our code generator.

Specifically, we have an input file that is valid Go code, and has a build tag
to exclude it from the build.

$ cat foo.go
// +build magic
package foo

func Bar() {
    ...
}

The code generator takes this file and spits out a nearly identical file with
the build tag inverted and some changes to the function bodies.

$ magic ./...
$ cat foo_gen.go
// +build !magic
package foo

func Bar() {
    ...
}

The code generator (magic) only considers files with the magic build tag.
This excludes the generated foo_gen.go from consideration when the tool is
run again. The go tool is invoked without the build tag so only the
generated file is compiled in the final binary.

magic go build // +build
foo.go Use Ignore magic
foo_gen.go Ignore Use !magic

The desired experience here is for users to code in the input file like it's a
regular Go file (with auto-completion and other editor features) without
caring that it'll get substituted for the generated file at build time.

@AndersonQ

This comment has been minimized.

Copy link

commented Aug 1, 2019

I use build tags to separate integration tests and examples (depending on external resources such as database) from unit tests (no external dependencies).

  • // +build integration
  • // +build example
    so go test won't run them by default
@gdey

This comment has been minimized.

Copy link

commented Aug 1, 2019

In the Tegola project we also use it select which features are enabled or not.

  • atlas/cache_s3.go:// +build !noS3Cache
  • atlas/provider_gpkg.go:// +build !noGpkgProvider
  • atlas/cache_redis.go:// +build !noRedisCache
  • atlas/cache_azblob.go:// +build !noAzblobCache
  • atlas/provider_postgis.go:// +build !noPostgisProvider
  • cmd/tegola/pprof.go:// +build pprof
  • provider/gpkg/gpkg.go:// +build cgo
  • server/viewer_disabled.go:// +build noViewer
  • server/viewer.go:// +build !noViewer

This is important as some environments that Tegola is deployed into they do not want all the features, and turning them off or not configuring it is not good enough. So, having the ability to compile them out is important.

@JAicewizard

This comment has been minimized.

Copy link

commented Aug 1, 2019

for building part of a program as a seperate binary. eg:part of a program parses some file, this would be used as an API by default, but with an alternate main file you can use the same codebase to build a parser(or any other modular thing) that would output to stdout or another file.

I used something like this in a "bigger" project where the program also had to use some modules(not go modules) as external binary and communicate over std in/out.
We could have a main wrapper that would usually not build, but with build tags we could build specific modules and use them in testing(or for example as seperate downloads)

@eliasnaur

This comment has been minimized.

Copy link
Contributor

commented Aug 3, 2019

The gioui.org/ui/app package is like package os, but for GUI programs: it uses build tags to distinguish between GOOS. Some examples:

// +build !js
// +build android
// +build darwin,ios
// +build linux windows

I also use the standard GOOS build tags in file names: os_js.go, gl_android.go, egl_windows.go etc.

@myitcv

This comment has been minimized.

Copy link
Member

commented Aug 5, 2019

Just to expand on the WASM example from my persective. The typical setup is that a subpackage/subtree of packages within a module (a module that has both backend and frontend code) has a preferred build configuration, specifically GOOS=js GOARCH=wasm. The key here being that whilst within the module, GOOS=js GOARCH=wasm is one such build, for this subpackage/subtree of packages, GOOS=js GOARCH=wasm is the default/preferred. i.e. when editing a file in the subpackage/subtree of packages, the implied build constraint is GOOS=js GOARCH=wasm.

@myitcv

This comment has been minimized.

Copy link
Member

commented Aug 5, 2019

Adding a separate comment about where this support for understanding the universe of build configurations for a module is required:

  • in editor; the UI/UX of how to choose the build is perhaps one of the trickiest points. @eliasnaur would prefer (in the context of his setup) automatic "selection" of the relevant build based on the file being edited. It's unclear whether this will work in all situations but sounds like an obviously beneficial and sensible default
  • (static analysis) tools, e.g. staticcheck (cc @dominikh) and apidiff (cc @jba)
  • CI systems
  • ...

This list is likely incomplete

@jba

This comment has been minimized.

Copy link
Contributor

commented Aug 6, 2019

The wire dependency injection tool uses

// +build wireinject

to prevent Go code used for configuration from being included in the actual build.
More at https://github.com/google/wire/blob/master/docs/guide.md and from @zombiezen.

@breml

This comment has been minimized.

Copy link

commented Aug 7, 2019

Use of // +build tools to add tool dependencies to go.mod, as suggested in How can I track tool dependencies for a module?.

@myitcv

This comment has been minimized.

Copy link
Member

commented Aug 8, 2019

@breml - that's a particularly interesting example because such a file will never compile.

@zombiezen

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2019

I have used build tags to enforce that a package is being built with a particular version of Go: https://github.com/zombiezen/gg/blob/13b5a059c5addfa7543b2b572650d9544bfcb729/internal/git/force_go19.go This use case is likely obsolete now that the go.mod file includes language version information, but such code may exist out in the wild.

Anecdotally, I most often use build tag (or the filename equivalent thereof) mechanisms in order to provide Unix-y and Windows equivalent functions.

Happy to answer specifics about Wire's usage of build tags if needed, but the docs are a pretty good place to start understanding. From the perspective of gopls-like static analysis, it should be pretty similar to the OS-specific code use case in that the same symbols get redefined.

@stamblerre

This comment has been minimized.

Copy link
Contributor Author

commented Aug 13, 2019

Thanks all for your detailed replies! I will be working on compiling your answers into a document, and hopefully soon we'll be able to make some progress on supporting build tags in gopls.

@ianthehat ianthehat added Thinking and removed WaitingForInfo labels Aug 19, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.