Go generate patterns
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.
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.
By using this go:generate
and go:build
trick captured
removes a dependency as it never
needs to manipulate BPF programs at runtime.