{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/go/omit-dev-dependencies-in-binaries/",
  "description": "Track dev dependencies like golangci-lint in go.mod with a tools.go file and build tags to exclude them from production binaries.",
  "path": "/go/omit-dev-dependencies-in-binaries/",
  "publishedAt": "2024-01-21T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Go",
    "TIL",
    "DevOps"
  ],
  "textContent": "As of now, unlike Python or NodeJS, Go doesn't allow you to specify your development\ndependencies separately from those of the application. However, I like to specify the dev\ndependencies explicitly for better reproducibility.\n\nWhile working on [link-patrol, a CLI for checking dead URLs] in Markdown files, I came\nacross this neat convention: you can specify dev dependencies in a tools.go file and then\nexclude them while building the binary using a build tag.\n\nHere's how it works. Let's say our project foo currently has the following structure:\n\nThe main.go file contains a simple hello-world function that uses a 3rd party dependency\njust to make a point:\n\nHere, Neo-cowsay is our app dependency. To initialize the project, we run the following\ncommands serially:\n\nNow, let's say we want to add the following dev dependencies: [golangci-lint] to lint the\nproject in the CI and [gofumpt] as a stricter gofmt. Since we don't import these tools\ndirectly anywhere, they aren't tracked by the build toolchain.\n\nBut we can leverage the following workflow:\n\n- Place a tools.go file in the root directory.\n- Import the dev dependencies in that file.\n- Run go mod tidy to track both app and dev dependencies via go.mod and go.sum.\n- Specify a build tag in tools.go to exclude the dev dependencies from the binary.\n\nIn this case, tools.go looks as follows:\n\nAbove, we're importing the dev dependencies and assigning them to underscores since we won't\nbe using them directly. However, now if you run go mod tidy, Go toolchain will track the\ndependencies via the go.mod and go.sum files. You can inspect the dependencies in\ngo.mod:\n\nAlthough we're tracking the dev dependencies along with the app ones, the build tag\n// go:build tools at the beginning of tools.go file will instruct the build toolchain to\nignore them while creating the binary.\n\nFrom the root directory of foo, you can build the project by running:\n\nThis will create a binary called main in the root directory. To ensure that the binary\ndoesn't contain the dev dependencies, run:\n\nThis won't return anything if the dev dependencies aren't packed into the binary.\n\nBut if you do that for the app dependency, it'll print the artifacts:\n\nThis prints:\n\nFor some weird reason, if you want to include the dev dependencies in your binary, you can\npass the tools tag while building the binary:\n\nHowever, this will most likely fail if any of your dev dependencies aren't importable.\n\nHere's an example of [Kubernetes's tools.go pattern] in the wild.\n\nWhile it works, I'd still prefer to have a proper solution instead of a hack. Fin!\n\n\n\n\n[link-patrol, a CLI for checking dead URLs]:\n    https://github.com/rednafi/link-patrol\n\n[golangci-lint]:\n    https://github.com/golangci/golangci-lint\n\n[gofumpt]:\n    https://github.com/mvdan/gofumpt\n\n[kubernetes's tools.go pattern]:\n    https://github.com/kubernetes/kubernetes/blob/master/hack/tools/tools.go",
  "title": "Omitting dev dependencies in Go binaries"
}