When developing Go package or application that depends on specific features of the underlying platform or architecture it is often necessary to use a specialised implementation.
There are two parts of Go conditional compilation system, which we will now explore in more detail.
Build constraints
A build constraints (known as build tags) is an optional top line comment that starts with
// +build
package api
Declaration of build constraints follows the following rules:
- Each tag is alphanumeric word
- Tag preceded by
!
defines its negation. - A build constraint is evaluated as the OR of space-separated options.
- Each option evaluates as the AND of its comma-separated terms.
That means if we have the following build constraint:
// +build linux,386 darwin,!cgo
It will be evaluated by the compilation system as:
(linux AND 386) OR (darwin AND (NOT cgo))
A file can have multiple build constraints:
// +build linux freebsd
// +build 386
package api
The evaluated constraint is a logical AND
of the individual build tags:
(linux OR freebsd) AND 386
Note that the build tags do not have strict validation. Be aware that they
should be formatted as the provided samples. In addition the last build tags should
be associated with a trailing new line. That makes it non-associated with any declaration.
Fortunately you can verify them by using go vet
tool.
File suffixes
The second option for conditional compilation is the name of the source file itself. This approach is simpler than build tags, and allows the Go build system to exclude files without having to process the file.
We should add one of the following suffixes to desired files:
*_GOOS // operation system
*_GOARCH // platform architecture
*_GOOS_GOARCH // both combined
These are all available architectures and operation system supported by Go build system:
GOOS | GOARCH |
---|---|
darwin | 386 |
darwin | amd64 |
dragonfly | 386 |
dragonfly | amd64 |
freebsd | 386 |
freebsd | amd64 |
freebsd | arm |
linux | 386 |
linux | amd64 |
linux | arm |
netbsd | 386 |
netbsd | amd64 |
netbsd | arm |
openbsd | 386 |
openbsd | amd64 |
plan9 | 386 |
plan9 | amd64 |
solaris | amd64 |
windows | 386 |
windows | amd64 |
Examples for such a files:
container_windows.go // only builds on windows system
container_linux.go // only builds on linux system
container_freebsd_386.go // only builds on FreeBSD system with 386 architecture
If you want to exclude a file from compilation, you should use ignore
build
constraint:
// +build ignore
package api
Test files
Test files also support build constraints and file suffixes. They behave in the same manner as other Go source files.
container_windows_test.go // windows specific container tests
container_linux_test.go // linux specific container tests
Experimenting with C/C++ preprocessor and Go
Go does not have a preprocessor to control the inclusion of platform specific code. Even though C preprocessor is intended to be used only with C, C++, and Objective-C source code, we will use it as a general text processor of Go source code.
Lets have the following code snippet:
// filename: app.pgo
package main
import "fmt"
#ifdef PRINT_DATE
import "time"
#endif
func main() {
fmt.Println("Application is executed.")
#ifdef PRINT_DATE
fmt.Printf("Current Date: %s\n", time.Now().String())
#endif
}
If we execute the C preprocessor on app.pgo
file:
// -P disable linemaker output.
$ cpp -P app.pgo app.go
We will produce a new file app.go
:
// filename: app.go
package main
import "fmt"
func main() {
fmt.Println("Application is executed.")
}
If we define PRINT_DATE
variable for the preprocessor by using -D
flag:
cpp -DPRINT_DATE -P app.pgo app.go
We will produce a new file that includes additional print statment:
// filename: app.go
package main
import "fmt"
import "time"
func main() {
fmt.Println("Application is executed.")
fmt.Printf("Current Date: %s\n", time.Now().String())
}
We can combine the preprocessor operation with go build step:
$ cpp -DPRINT_DATE -P app.pgo app.go | go build app.go
Verdict
We should aim to develop and build our Go applications by following Go idioms. If the source file targets a specific platform, we should choose file suffix approach. Otherwise, if the source file is applicable for multiple platforms and we want to exclude a specific feature or platform, we should use build constraints instead.