The go generate command looks for //go:generate some-command line comments in your source files and executes the commands.

Sometime though these commands are programs you have written and would like to keep along in the same project. If you have written everything in go this may raise some issues because you may have two distinct main packages in the same directory.

As an example consider captured. captured is a program that captures some network traffic and stores it in a ringbuffer for you to replay it later when you have to debug other network programs.

To capture DHCPv4/6 traffic captured uses an eBPF filter and receives only the matching packets from the Linux kernel. Because of how eBPF works you need to convert the filter from its text representation (icmp6 or (udp and (port 67 or port 68 or port 546 or port 547))) to eBPF bytecode.

This conversion is done by the “github.com/google/gopacket/pcap” library using the CompileBPFFilter function so that in the end you receive a slice of BPFInstruction.

Instead of computing these instructions at runtime you could save them to a go file as a variable and use that in your program.

package main

import "golang.org/x/net/bpf"

var instructions = []bpf.RawInstructions{
    ...
}

In the captured package this is exactly how it works. There is a program for runnig the packet capture daemon and one program to generate the file containing the BPF instructions. This means that there are two main programs in the same package! How can that be?

The truth is that you can exclude package files from the build system using go’s build constraints AKA build tags. Build tags are used to decide which files are included in a package when compiling. This is useful when the package is using some platform specific feature that are not portable, e.g. //go:build (linux && 386) || (darwin && !cgo) signals to only include the file when compiling for linux 386 or mac without using CGO.

Go also recognizes the //go:build ignore directive that never includes the current file so you should see where we are going. We can ignore one of the two files containing main and we would still be able to run the BPF generating program if we place a //go:generate go run ... directive somewhere else.

In the end this is what the whole contraption looks like.

// genfilterexpr.go
package main
//go:generate go run filterexpr.go

// filterexpr.go
//go:build ignore
package main

const expression = `icmp6 or (udp and (port 67 or port 68 or port 546 or port 547))`

func main() {
	// here we can generate the BPF instructions
	// and save them to a file in the current package
}

// captured.go
package main

func main() {
	// here we can use the generated instructions
}

By using this go:generate and go:build trick captured removes a dependency as it never needs to manipulate BPF programs at runtime.