1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
.Dd August 16, 2026
.Dt BUILDGO 7
.Os
.Sh NAME
.Nm buildGo
.Nd build Go libraries and programs by invoking the compiler directly
.Sh DESCRIPTION
.Nm
is a Nix build system for Go that calls
.Xr go 1 Ns 's
underlying tools
.Pq Cm go tool compile , Cm go tool link
directly, rather than delegating to
.Cm go build
and its module resolution.
Composition of packages and programs therefore stays inside Nix, in the style
of Bazel's
.Li rules_go .
Every dependency is an explicit Nix value; there is no
.Pa go.mod
resolution, no network access and no vendor directory at build time.
.Pp
.Nm
is a
.Em function ,
not a package: it is imported by
.Pa //nix/packages.nix
and reached as
.Va depot.nix.buildGo .
It produces no
.Pa bin/
output of its own and cannot be built or installed.
.Pp
Any
.Pa go.mod
in the tree exists only for editor tooling and
.Cm go test ;
it has no effect on the Nix build.
Dependency versions come from
.Pa go-deps.nix
files, and the two can disagree without any warning \(en see
.Sx CAVEATS .
.Sh FUNCTIONS
.Bl -tag -width Ds
.It Fn program "{ name, srcs, deps ? [], x_defs ? {} }"
Build an executable.
.Bl -tag -width "x_defs" -compact
.It Va name
Name of the program and of the resulting executable.
Required.
.It Va srcs
List of paths to source files.
Required.
All sources are compiled into package
.Li main .
.It Va deps
List of Go libraries to link against.
.It Va x_defs
Attribute set of linker variable definitions, passed as
.Fl X
flags.
Used to stamp values such as a version into the binary at link time.
.El
.It Fn package "{ name, srcs, deps ? [], path ? name, sfiles ? [] }"
Build an importable library.
The result carries both the compiled archive and the sources, as downstream
consumers need both.
.Bl -tag -width "sfiles" -compact
.It Va name
Name of the library.
Required.
.It Va srcs
List of paths to source files.
Required.
.It Va deps
List of Go libraries to link against.
.It Va path
Go import path of the result.
Defaults to
.Va name .
.It Va sfiles
List of Go assembly
.Pq Pa *.s
files, assembled and packed into the archive.
Required by several packages under
.Li golang.org/x .
.El
.It Fn external "{ path, src, deps ? [], tags ? [] }"
Build a Go repository that was not written for
.Nm ,
laid out in the standard Go tooling style.
.Bl -tag -width "deps" -compact
.It Va path
Go import path of the repository, e.g.
.Li github.com/emersion/go-imap .
Required.
.It Va src
Path to the source
.Em directory ,
typically a
.Fn fetchFromGitHub
result.
Required.
.It Va deps
List of other
.Fn external
results this repository imports.
.It Va tags
Build tags to set while analysing the repository.
See
.Sx BUILD TAGS .
.El
.El
.Sh EXTERNAL PACKAGES
.Fn external
analyses the repository and returns a nested attribute set mirroring its
directory structure.
Each directory that contains a Go package gains a
.Va gopkg
attribute holding the compiled library.
A dependency must always be referred to by an attribute that has a
.Va gopkg ,
which is not necessarily the root.
Three shapes occur:
.Bl -tag -width Ds
.It Single-package repository
.Va gopkg
sits at the root, so the result is used directly:
.Bd -literal -offset indent
deps = [ goDeps.creack-pty ];
.Ed
.It Repository with subpackages
Navigate to the ones actually imported:
.Bd -literal -offset indent
deps = [ goDeps.golang-x-net.html ];
.Ed
.It Repository with no root package
.Li golang.org/x/sys
has code only in subdirectories:
.Bd -literal -offset indent
deps = [ goDeps.golang-x-sys.unix ];
.Ed
.El
.Pp
Passing an attribute without a
.Va gopkg
is an error, and the resulting message enumerates the valid paths within that
dependency.
.Pp
Dependencies between packages
.Em inside
one repository are resolved automatically from the import statements.
Only imports of
.Em other
repositories need to be listed in
.Va deps ,
where they are matched by their
.Va goImportPath .
.Sh BUILD TAGS
.Va tags
sets the build tags used when the analyser decides which files a package
consists of.
It selects
.Em sources ,
not compiler behaviour: a file excluded by a tag is never handed to
.Li go tool compile
at all.
.Pp
The case that motivates it is assembly.
Several cryptographic repositories ship hand-written amd64 assembly next to a
pure-Go implementation of the same routines, chosen by the
.Li purego
tag.
.Nm
stages each package into a flat directory and copies only
.Pa *.go
and
.Pa *.s
files, so an assembly file that
.Li #include Ns s
a header fails to assemble:
.Bd -literal -offset indent
fp_amd64.s:6: #include: open .../pkg/include/fp_amd64.h:
    no such file or directory
.Ed
.Pp
Selecting the pure-Go sources avoids the problem at its root rather than
patching around it:
.Bd -literal -offset indent
circl = depot.nix.buildGo.external {
  path = "github.com/cloudflare/circl";
  src = pkgs.fetchFromGitHub { /* ... */ };
  tags = [ "purego" ];
};
.Ed
.Pp
Copying the headers alongside the assembly would not be enough in general,
because such headers are routinely included by
.Em relative
path across package boundaries \(en
.Li circl/dh/x25519
includes
.Pa ../../math/fp25519/fp_amd64.h
\(en which presumes the original repository tree that the flat staging has
already flattened away.
.Pp
The cost is performance: the pure-Go path is slower than the assembly it
replaces.
For work measured in a handful of operations per request this is not
observable, but a tag should not be reached for merely to silence a build
error in a hot path.
.Sh EMBEDDING FILES
.Nm
supports
.Li //go:embed .
The compiler cannot glob the filesystem itself, so
.Nm
runs
.Cm go list
over the source directory to discover which files each pattern matches, and
converts the result into the
.Fl embedcfg
JSON the compiler expects.
.Pp
This is where the one genuinely surprising constraint of
.Nm
lives.
.Ss The staging requirement
The source directory handed to
.Cm go list
is computed as the directory of the
.Em first
entry of
.Va srcs .
When sources are given as ordinary path literals:
.Bd -literal -offset indent
srcs = [ ./main.go ./serve.go ];
.Ed
.Pp
each path is copied into the store
.Em individually ,
becoming its own
.Pa /nix/store/<hash>-main.go .
Their common directory is therefore
.Pa /nix/store
itself, which contains no embeddable files.
.Cm go list
finds nothing, the embed configuration comes out empty, and the embedded
variables are silently empty at runtime.
The build does not fail.
.Pp
The remedy is to stage the sources and the files they embed into a single
directory first, and pass
.Em strings
pointing into it rather than path literals:
.Bd -literal -offset indent
let
  src = pkgs.runCommandLocal "myprog-src" {} \'\'
    mkdir -p $out/templates
    cp ${./main.go} $out/main.go
    cp ${./templates}/*.html $out/templates/
  \'\';
in depot.nix.buildGo.program {
  name = "myprog";
  srcs = [ "${src}/main.go" ];
}
.Ed
.Pp
A path literal
.Pq Pa ./main.go
is copied to the store on its own; a string
.Pq Li \&"${src}/main.go\&"
names a file
.Em inside
an existing store directory and keeps its siblings reachable.
The distinction is invisible at the call site and matters only here.
.Pp
Note that the staging derivation copies a fixed set of files.
Adding a template with a new extension means widening the
.Cm cp
glob as well as the
.Li //go:embed
pattern; forgetting the former leaves the file out of the build entirely.
.Ss Pattern matching
Patterns are matched with
.Fn filepath.Match ,
so
.Li templates/*.html
and plain paths both work, and a pattern naming a directory matches the files
directly below it.
.Pp
A pattern that matches
.Em nothing
falls back to embedding
.Em every
file
.Cm go list
reported, rather than failing.
A typo in a pattern thus yields a working build with the wrong contents.
.Sh EXAMPLES
A program with one local library:
.Bd -literal -offset indent
let
  lib = depot.nix.buildGo.package {
    name = "somelib";
    srcs = [ ./lib/foo.go ./lib/bar.go ];
  };
in depot.nix.buildGo.program {
  name = "my-program";
  srcs = [ ./main.go ];
  deps = [ lib ];
}
.Ed
.Pp
External dependencies are conventionally pinned in a
.Pa go-deps.nix
beside the package, or taken from the shared one:
.Bd -literal -offset indent
{ depot, pkgs, ... }:
{
  creack-pty = depot.nix.buildGo.external {
    path = "github.com/creack/pty";
    src = pkgs.fetchFromGitHub {
      owner = "creack";
      repo = "pty";
      rev = "edfbf75025b0ba4ee17c19f52d9b600fad80a787";
      sha256 = "0yy4zhfb7vrrbwd13rcw0zzcq0ami3zv3hp0x7g7il6mrbadcf25";
    };
  };
}
.Ed
.Pp
Rather than reproducing every pattern here, the following files in this
repository are worth reading as worked examples:
.Bl -tag -width Ds
.It Pa //nix/buildGo/example
A minimal
.Fn package
plus
.Fn program ,
including
.Va x_defs .
.It Pa //users/Profpatsch/link-check/default.nix
The smallest realistic case: one source file, one external subpackage
dependency.
.It Pa //users/Profpatsch/go-deps.nix
The shared dependency pin set, and the idiom of navigating to subpackages.
.It Pa //users/Profpatsch/mailweb/default.nix
Embed staging with a subdirectory of assets, as described above.
.It Pa //users/Profpatsch/source-forge/default.nix
A program with a separate
.Va man
output, via
.Fn symlinkJoin .
.It Pa //users/Profpatsch/maildir-varlink/default.nix
Wrapping the result with
.Fn wrapProgram
to put runtime dependencies on
.Ev PATH .
.El
.Sh SEE ALSO
.Xr go 1
.Pp
.Pa //README.md
for the
.Pa default.nix
versus
.Pa packages.nix
rules that govern how a package is named and found.
.Pp
.Lk https://github.com/tazjin/nix-1p "nix-1p" ,
a brief introduction to the Nix language.
.Sh HISTORY
.Nm
originates from the
.Lk https://cs.tvl.fyi/depot "TVL depot" ,
where it was written by Vincent Ambo, and is licensed Apache 2.0.
It has since gained
.Li //go:embed
support, assembly support and static linking.
.Sh CAVEATS
.Ss Tests are never run
.Nm
has no test support.
.Pa *_test.go
files are simply left out of
.Va srcs ,
where they are inert.
Run them with the ordinary Go tooling instead, which the
.Pa go.work
workspace at the repository root makes possible:
.Bd -literal -offset indent
$ go test ./users/Profpatsch/mailweb/
.Ed
.Pp
Consequently a package can build under Nix while its tests do not compile.
.Ss Versions can drift
The Nix build ignores
.Pa go.mod
entirely, so the version pinned in
.Pa go-deps.nix
is what is actually compiled.
When the two disagree, editor tooling and
.Cm go test
see one version and the built artefact another, with no diagnostic.
.Ss cgo is unavailable
The standard library is built with
.Ev CGO_ENABLED Ns = Ns 0
so that everything links statically and no
.Pa libc
or dynamic loader becomes a runtime dependency.
Packages that require cgo cannot be built with
.Nm ;
use
.Fn pkgs.buildGoModule
for those.
.Ss No documentation building
Manual pages and other documentation are installed by hand, typically by
wrapping the result in
.Fn symlinkJoin
with an added
.Va man
output.