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

Closed
stamblerre opened this issue Jul 31, 2019 · 24 comments
Closed

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

stamblerre opened this issue Jul 31, 2019 · 24 comments
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. Thinking

Comments

@stamblerre
Copy link
Contributor

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 Issues related to the Go language server, gopls. label Jul 31, 2019
@stamblerre stamblerre added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Jul 31, 2019
@vcabbage
Copy link
Member

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
Copy link

aykevl 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.

@niaow
Copy link

niaow 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

@muirdm
Copy link

muirdm 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
Copy link
Contributor

seebs 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
Copy link

gdey 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
Copy link
Contributor

neild 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
Copy link
Contributor

abhinav 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
Copy link
Contributor

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
Copy link

gdey 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
Copy link

JAicewizard 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
Copy link
Contributor

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
Copy link
Member

myitcv 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
Copy link
Member

myitcv 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
Copy link
Contributor

jba 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
Copy link
Contributor

breml 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
Copy link
Member

myitcv commented Aug 8, 2019

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

@zombiezen
Copy link
Contributor

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
Copy link
Contributor Author

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 Issue is not actionable because of missing required information, which needs to be provided. labels Aug 19, 2019
@stamblerre stamblerre modified the milestones: Unreleased, gopls v1.0 Aug 26, 2019
@meling
Copy link

meling commented Sep 19, 2019

I teach and use Go for programming labs, and keep solutions and skeleton code for the students in the same folder to ensure consistency with common files and to remember to change both if necessary.

I use // +build solution in solution files and // +build !solution in skeleton code, since they have overlapping type/func definitions. (This of course results in numerous compile errors when using gopls in VSCode.)

I run tests using go test -tags solution.
Running go test without the solution tag should fail the tests.

I then copy the skeleton code to a separate repo to share with the students.

@jefferai
Copy link

Build tags are crucial for projects that have open source and enterprise variants.

@rythmkraze
Copy link

gobbi, a package that provides bindings to GTK GUI toolkit, uses Go build tags to allow targeting of specific library versions. See here for more details.

@stamblerre stamblerre modified the milestones: gopls v1.0, gopls unplanned Dec 4, 2019
@stamblerre
Copy link
Contributor Author

stamblerre commented Jan 29, 2020

Thanks all for the detailed replies! This information will be very valuable as we continue work on #29202. Closing this issue in favor of that one. Please feel free to comment here if you have a different use case for build tags.

@KBassford
Copy link

I have one file with '// +build debug', and an almost identical file in the same folder with '// +build !debug'. gopls flagged every constant, variable and function as being redefined between the two files. The debug file just prints log messages to replace functions that make major changes to the running state of the system.

@golang golang locked and limited conversation to collaborators Mar 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. Thinking
Projects
None yet
Development

No branches or pull requests