= Writing Go packages directly in Zilch :page-pagination: It's possible to use Zilch as generic build system for Go. This page serves to document that, as well as the internals of the Go build library in Zilch. == Go packages in Zilch Each Go package is represented in Zilch by a xref:generated:zilch.lang.go.adoc#++_go-package_++[``], which represents the compiled API and ABI as separate store paths, plus the name of the package, and its dependencies. The simplest possible Go package consists of a single file: [,scheme,line-comment=;] ---- (define example (go-package-compile "example.com/package" ; <1> '() ; <2> `(("example.go" . ,(zfile "package example"))))) ; <3> ;; # ;; code: # ;; deps: ()> ---- <1> Name of the package <2> List of dependency packages (in this case, none) <3> Mapping of filename to in-store files To create an executable, though, a bit more work is necessary, which is abstracted by the Go build system by default. The "main" package, which contains the `func main()`, always has the package name "main". However, this is undesirable for the name in panics. For this case, the extended `go-package-compile` procedure allows setting both: [,scheme,line-comment=;] ---- (define binary (go-package-compile "main" ; <1> "example.com/binary" ; <2> (list example) ; <3> `(("main.go" . ,(zfile "package main\nfunc main() { }"))) ; <4> '() '() ; <5> '() '())) ; <6> ;; # ;; code: # ;; deps: ("example.com/package")> ---- <1> The package name, as provided to the compiler (must be `main` for binaries) <2> The package name as shown in stacktraces <3> Direct dependencies for this package <4> The source file for this package <5> Assembly-related files <6> ``go:embed``-related files To use this binary, it still needs to be linked. This can be done with `go-package-link`. [IMPORTANT] ==== If you're linking a Go binary, make sure to add the `runtime` package to your dependencies, either directly or indirectly. Otherwise, you will end up with an error like so: [,scheme,line-comment=;] ---- (store-path-realised (go-package-link binary)) ;> [..building "/nix/store/jr17baky22c0zzpjfi97cw16k5wmqqc3-example.com_binary.drv"] ;> [0/1 builds, 1 running] ;> loadinternal: cannot find runtime ;> panic: could not look up runtime.mapinitnoop ---- This is caused by Zilch not adding any standard library code by default, while the Go compiler depends on structures in the standard library to provide runtime support for the garbage collector and goroutines. ==== == Using the Go standard library The Go standard library, unlike most other languages, is shipped as source code. When you compile Go code, the compiler will also compile the parts of the standard library you use. Zilch does the same. `(zilch lang go stdlib)` contains a single procedure, `go-stdlib-ref`, which handles this for you: [,scheme,line-comment=;] ---- (go-stdlib-ref "fmt") ;; # ;; code: # ;; deps: ("errors" "internal/fmtsort" "io" "math" "os" "reflect" ;; "slices" "strconv" "sync" "unicode/utf8")> (define hello-world (go-package-compile "main" "example.com/binary" (list example (go-stdlib-ref "fmt")) `(("main.go" . ,(zfile "package main import \"fmt\" func main() { fmt.Printf(\"Hello, world!\\n\") }"))) '() '() '() '())) (define linked (go-package-link hello-world)) ;; # (store-path-realised linked) ;> [..building "/nix/store/5bgrfjgnvzr8wqksl5j23xr5wm7rahnw-zilchfile.drv"] ;> … ;> [0/2 builds, 0 running] ;> [..building "/nix/store/jdk0wbnrz16sbn41r0y6f82hxl93q87r-example.com_binary.drv"] ;; "/nix/store/p71p43d3cv48rgi4yh9dvlzssgfkb5vf-example.com_binary" ---- The binary that is output by this code is incrementally built, one package at a time, including the standard library. [#vfs] == Creating virtual filesystems for Go modules Go's `go.sum` files contain enough information to recover the full file structure of the module, through a hash format called `dirhash`. Zilch implements this, and uses it to guide its VFS generating. Once you have a `go.sum` line, e.g. through `parse-go-sum-line`, passing it to `vfs-from-dirhash` will have it generate a VFS from a dirhash, plus the Go module proxy to fetch the zip: [,scheme,line-comment=;] ---- (define sum-line (parse-go-sum-line "golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=")) ;; # (define x-net-vfs (vfs-from-dirhash sum-line)) ;> (pre)fetching "module.zip" "https://proxy.golang.org/golang.org/x/net/@v/v0.41.0.zip" ;> … ;; #> (store-path-realised (vfs-to-store x-net-vfs)) ;> [..building "/nix/store/vvs2xsgjpab4gwm530dvzcfzfcs5ak4m-zilchfile.drv"] ;> … ;; "/nix/store/r1p1y6bnmddmcrl2avsygp659d8iiih1-zilchfile/-" ---- == Dynamically generating ``go-package``s from packages Zilch supports generating `go-package` records dynamically, from `go.mod` files and `go.sum` files recursively. To do this, get a VFS for the primary module you want to work with, and any module you want to resolve automatically (Zilch will automatically crawl `go.sum` files to resolve dependencies, where needed.) [,scheme,line-comment=;] ---- (define-values (root-module-path requirements) (collect-requirements-for-module x-net-vfs '())) ;> Collecting required modules ;> [..building "/nix/store/s9fmg08x56px39hjkwlba2pra5g1abgr-go.mod.json.drv"] ;> - found "golang.org/x/net" (requires 4 modules) ;> - found "golang.org/x/crypto" (requires 4 modules) ;> … ;; root-module-path -> "golang.org/x/net" ;; requirements ;; -> # (#f . #>) ;; "github.com/google/go-cmp" -> ("v0.6.0" . #>) ;; "golang.org/x/crypto" -> ("v0.39.0" . #>) ;; … ;; "golang.org/x/mod" -> ("v0.25.0" . #>)> ---- The resulting values are a comprehensive set of modules mapped to their final versions and the VFS of that module. This information can then be used to create a procedure that returns the `go-package` for any package in this set of modules, similar to how `go-stdlib-ref` works: [,scheme,line-comment=;] ---- (define x-net-ref (collect-packages-from-requires requirements)) (x-net-ref "golang.org/x/net/idna") ;; # ;; code: # ;; deps: ("fmt" "bidirule" "bidi" "norm" "math" "strings" "unicode/utf8")> (x-net-ref "golang.org/x/text/runes") ;; # <1> ---- <1> Even packages defined in a dependency can be looked up using this method.