[PATCH 0/2] Go: Module aware build system

  • Done
  • quality assurance status badge
Details
2 participants
  • Jørgen Kvalsvik
  • Sharlatan Hellseher
Owner
unassigned
Submitted by
Jørgen Kvalsvik
Severity
normal

Debbugs page

J
J
Jørgen Kvalsvik wrote on 13 May 02:40 -0700
(address . guix-patches@gnu.org)
20250513094023.348947-1-j@lambda.is
Hi,

This is a new build system for go, based on go modules. I'm submitting
this now to get an issue number, collect some feedback, and iron out the
last few wrinkles, but we have used an internal version of it for a
while and it works well for us. I have tweaked it a bit in preparation
for this patch series, and I do expect it to take a couple of
iterations.

The new build system can use packages built with go-build-system as
inputs, which I think is a prerequisite. There is already a large
collection of go libraries in guix, and we want to use that work
well. That being said, this build system should graudually replace the
go-build-system. Because they are (largely) compatible as inputs,
packages can be migrated gradually. The library or program being built does
not have to be "module aware" (have a go.mod file) to use this build system, a
new go.mod will be created unconditionally.

It addresses most of the shortcomings of the current go build
system. Here's the list from go.scm and how this system is different:

* Avoid copying dependencies into the build environment and / or avoid
using a tmpdir when creating the inputs union.
We still copy build dependencies into the build dir, so that go
build can "fetch" dependencies as it likes. There is a path where
this can be avoided by using replace directives in go.mod, but this
is a significant complication and requires build inputs to be proper
modules, which is not guaranteed.

* Use Go modules
Yes.

* Re-use compiled packages
Yes. We install the go build cache under $out/var, and seed
downstream builds with this cache.

* Avoid the go-inputs hack
Sort-of? We look at package name, but also the "shape" of the
package, namely the presence of the src/ directory. This seems to
work ok.

* Remove module packages, only offering the full Git repos? This is more
idiomatic, I think, because Go downloads Git repos, not modules.
There is the go-mod-fetch which downloads modules (as zips). The
build system does not particularly care about the source, and works
well both with git clone, hg clone, and url fetch.

* Figurie out how to passthrough --verbosity option to "build" and
"check"
Not addressed yet.

* Implement test-backend option, which would be similar to pyproject's
one, allowing to provide custom test runner.
Not really, but most go projects are just tested with `go test
./...` or `go test ./dir1 ./dir2 ...`. There are options for both
test flags and test targets. Anything else probably warrants a
custom check phase.

I have a prototype of guix import go-module ..., too, but it's not quite
ready yet. I will add onto this series a few packages to demonstrate the
build system, and port a few packages from go-build-system to
go-module-build-system.

Jørgen Kvalsvik (2):
guix: Add downloader for go modules from GOPROXY
guix: Add module-aware build system for go

Makefile.am | 3 +
guix/build-system/go-module.scm | 268 +++++++++++++++
guix/build/go-module-build-system.scm | 457 ++++++++++++++++++++++++++
guix/go-mod-download.scm | 126 +++++++
4 files changed, 854 insertions(+)
create mode 100644 guix/build-system/go-module.scm
create mode 100644 guix/build/go-module-build-system.scm
create mode 100644 guix/go-mod-download.scm

--
2.39.5
J
J
Jørgen Kvalsvik wrote on 13 May 02:55 -0700
[PATCH 2/2] guix: Add module-aware build system for go
(address . 78404@debbugs.gnu.org)
20250513095522.4313-2-j@lambda.is
Add a new build system for go, using go modules. This build system is partly
compatible with go-build-system; they can both be used as build inputs to each
other, but their options are incompatible.

The main departure from go-build-system is that go-build-system tries to build
a workspace [1], where go-module-build-system builds a goproxy + go.mod file
and lets `go build` do what it wants to.

Most go libraries should be straight forward to build. For example, this is
the package definition for golang.org/x/sync@0.12:

(define-public go-golang-org-x-sync
(package
(name "go-golang-org-x-sync")
(version "0.12.0")
(source
(origin
(method go-mod-fetch)
(uri (go-mod-reference
(path "golang.org/x/sync")
(version version)))
(sha256
(base32 "00pd84ah4xd5sjax8rxv98xbnwrvkk8clazl3kq1xrbkmvjq2m53"))))
(build-system go-module-build-system)
(synopsis "Go Sync")
(description "This repository provides Go concurrency primitives
in addition to the ones provided by the language and \"sync\" and
\"sync/atomic\" packages.")
(license license:bsd-3)))

The build system also supports higher resolution build-, test-, and install
targets, re-use of compiled files, and options for common build tweaks.


* guix/build/go-module-build-system.scm: New file.
* guix/build-system/go-module.scm: New file.
* Makefile.am (MODULES): Add them.

Change-Id: I47a028ab8f95fd3a338036480dbad6677e9c50a5
---
Makefile.am | 2 +
guix/build-system/go-module.scm | 268 +++++++++++++++
guix/build/go-module-build-system.scm | 459 ++++++++++++++++++++++++++
3 files changed, 729 insertions(+)
create mode 100644 guix/build-system/go-module.scm
create mode 100644 guix/build/go-module-build-system.scm

Toggle diff (408 lines)
diff --git a/Makefile.am b/Makefile.am
index b5fb81f412..12446e6bb4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -168,6 +168,7 @@ MODULES = \
guix/build-system/glib-or-gtk.scm \
guix/build-system/gnu.scm \
guix/build-system/go.scm \
+ guix/build-system/go-module.scm \
guix/build-system/guile.scm \
guix/build-system/haskell.scm \
guix/build-system/julia.scm \
@@ -227,6 +228,7 @@ MODULES = \
guix/build/minify-build-system.scm \
guix/build/font-build-system.scm \
guix/build/go-build-system.scm \
+ guix/build/go-module-build-system.scm \
guix/build/android-repo.scm \
guix/build/asdf-build-system.scm \
guix/build/bzr.scm \
diff --git a/guix/build-system/go-module.scm b/guix/build-system/go-module.scm
new file mode 100644
index 0000000000..5692e318d3
--- /dev/null
+++ b/guix/build-system/go-module.scm
@@ -0,0 +1,268 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Jørgen Kvalsvik <j@lambda.is>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system go-module)
+ #:use-module (guix gexp)
+ #:use-module (guix monads)
+ #:use-module (guix packages)
+ #:use-module (guix store)
+ #:use-module (guix utils)
+ #:use-module (guix search-paths)
+ #:use-module (guix build-system)
+ #:use-module (guix build-system gnu)
+ #:use-module ((guix build-system go) #:prefix go-build:)
+ #:use-module (srfi srfi-1)
+ #:export (%go-module-build-system-modules
+ go-module-build
+ go-module-build-system))
+
+;;; Commentary:
+;;;
+;;; Build procedure for packages using the module aware Go build system.
+;;;
+;;; Code:
+
+(define %go-module-build-system-modules
+ ;; Build-side modules imported by default.
+ `((guix build go-module-build-system)
+ (guix build union)
+ ,@%default-gnu-imported-modules))
+
+(define (default-go)
+ ;; Lazily resolve the binding to avoid a circular dependency.
+ (let ((go (resolve-interface '(gnu packages golang))))
+ (module-ref go 'go)))
+
+(define (default-gccgo)
+ ;; Lazily resolve the binding to avoid a circular dependency.
+ (let ((gcc (resolve-interface '(gnu packages gcc))))
+ (module-ref gcc 'gccgo-12)))
+
+(define (default-zip)
+ "Return the 'zip' package. This is a lazy reference so that we don't
+depend on (gnu packages compression)."
+ (let ((distro (resolve-interface '(gnu packages compression))))
+ (module-ref distro 'zip)))
+
+(define* (lower name
+ #:key source inputs native-inputs outputs system target
+ (go (if (supported-package? (default-go))
+ (default-go)
+ (default-gccgo)))
+ (zip (default-zip))
+ #:allow-other-keys
+ #:rest arguments)
+ "Return a bag for NAME."
+ (define private-keywords
+ '(#:target #:inputs #:native-inputs #:go #:zip))
+
+ (bag
+ (name name)
+ (system system)
+ (target target)
+ (build-inputs `(,@(if source
+ `(("source" ,source))
+ '())
+ ,@`(("go" ,go) ("zip" ,zip))
+ ,@inputs
+ ,@native-inputs
+ ,@(if target (standard-cross-packages target 'host) '())
+ ;; Keep the standard inputs of 'gnu-build-system'.
+ ,@(standard-packages)))
+ (target-inputs (if target (standard-cross-packages target 'target) '()))
+ (outputs outputs)
+ (build (if target go-cross-module-build go-module-build))
+ (arguments (strip-keyword-arguments private-keywords arguments))))
+
+(define* (go-module-build name inputs
+ #:key
+ source
+ (phases '%standard-phases)
+ (outputs '("out"))
+ (search-paths '())
+ (go-flags '())
+ (ld-flags '("-s" "-w"))
+ (tags '())
+ (build-targets '("./..."))
+ (test-targets '("./..."))
+ (install-targets '())
+ (test-flags '())
+ (module-path #f)
+ (trimpath? #t)
+ (cgo? #f)
+ (tests? #t)
+ (build-output-dir? #f)
+ (skip-build? #f)
+ (install-source? #t)
+ (install-cache? #t)
+ (parallel-build? #t)
+ (parallel-tests? #t)
+ (environment-variables '())
+ (system (%current-system))
+ (goarch #f)
+ (goos #f)
+ (guile #f)
+ (substitutable? #t)
+ (imported-modules %go-module-build-system-modules)
+ (modules '((guix build go-module-build-system)
+ (guix build utils))))
+
+ (define builder
+ (with-imported-modules
+ imported-modules
+ #~(begin
+ (use-modules #$@(sexp->gexp modules))
+ (go-module-build #:name #$name
+ #:source #+source
+ #:system #$system
+ #:go-flags '#$go-flags
+ #:ld-flags '#$ld-flags
+ #:tags '#$tags
+ #:build-targets '#$build-targets
+ #:test-targets '#$test-targets
+ #:install-targets '#$install-targets
+ #:test-flags '#$test-flags
+ #:module-path '#$module-path
+ #:trimpath? #$trimpath?
+ #:cgo? '#$cgo?
+ #:tests? #$tests?
+ #:build-output-dir? #$build-output-dir?
+ #:skip-build? #$skip-build?
+ #:install-source? #$install-source?
+ #:install-cache? #$install-cache?
+ #:parallel-build? #$parallel-build?
+ #:parallel-tests? #$parallel-tests?
+ #:environment-variables '#$environment-variables
+ #:goarch #$goarch
+ #:goos #$goos
+ #:phases #$phases
+ #:outputs #$(outputs->gexp outputs)
+ #:search-paths '#$(map
+ search-path-specification->sexp
+ search-paths)
+ #:inputs #$(input-tuples->gexp inputs)))))
+
+ (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+ system #:graft? #f)))
+ (gexp->derivation name builder
+ #:system system
+ #:guile-for-build guile)))
+
+(define* (go-cross-module-build name
+ #:key
+ source target
+ build-inputs target-inputs host-inputs
+ (phases '%standard-phases)
+ (outputs '("out"))
+ (search-paths '())
+ (native-search-paths '())
+ (go-flags '())
+ (ld-flags '("-s" "-w"))
+ (tags '())
+ (build-targets '("./..."))
+ (test-targets '())
+ (install-targets '())
+ (tests? #f) ; nothing can be done
+ (test-flags '())
+ (module-path #f)
+ (trimpath? #t)
+ (cgo? #f)
+ (build-output-dir? #f)
+ (skip-build? #f)
+ (install-source? #t)
+ (install-cache? #t)
+ (parallel-build? #t)
+ (parallel-tests? #f)
+ (environment-variables '())
+ (system (%current-system))
+ (goarch (if target (first (go-build:go-target target)) #f))
+ (goos (if target (last (go-build:go-target target)) #f))
+ (guile #f)
+ (imported-modules %go-module-build-system-modules)
+ (modules '((guix build go-module-build-system)
+ (guix build utils)))
+ (substitutable? #t))
+
+ (define builder
+ (with-imported-modules
+ imported-modules
+ #~(begin
+ (use-modules #$@(sexp->gexp modules))
+
+ (define %build-host-inputs
+ #+(input-tuples->gexp build-inputs))
+
+ (define %build-target-inputs
+ (append #$(input-tuples->gexp host-inputs)
+ #+(input-tuples->gexp target-inputs)))
+
+ (define %build-inputs
+ (append %build-host-inputs %build-target-inputs))
+
+ (go-module-build #:name #$name
+ #:source #+source
+ #:system #$system
+ #:go-flags '#$go-flags
+ #:ld-flags '#$ld-flags
+ #:tags '#$tags
+ #:build-targets '#$build-targets
+ #:test-targets '#$test-targets
+ #:install-targets '#$install-targets
+ #:test-flags '#$test-flags
+ #:module-path '#$module-path
+ #:trimpath? #$trimpath?
+ #:cgo? '#$cgo?
+ #:tests? #$tests?
+ #:build-output-dir? #$build-output-dir?
+ #:skip-build? #$skip-build?
+ #:install-source? #$install-source?
+ #:install-cache? #$install-cache?
+ #:parallel-build? #$parallel-build?
+ #:parallel-tests? #$parallel-tests?
+ #:environment-variables '#$environment-variables
+ #:target #$target
+ #:goarch #$goarch
+ #:goos #$goos
+ #:phases #$phases
+ #:outputs #$(outputs->gexp outputs)
+ #:make-dynamic-linker-cache? #f
+ #:search-paths '#$(map
+ search-path-specification->sexp
+ search-paths)
+ #:native-search-paths '#$(map
+ search-path-specification->sexp
+ native-search-paths)
+ #:native-inputs %build-host-inputs
+ #:inputs %build-inputs))))
+
+ (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+ system #:graft? #f)))
+ (gexp->derivation name builder
+ #:system system
+ #:target target
+ #:graft? #f
+ #:substitutable? substitutable?
+ #:guile-for-build guile)))
+
+(define go-module-build-system
+ (build-system
+ (name 'go-module)
+ (description "Go Module Build System")
+ (lower lower)))
+
+;;; go-module.scm ends here
diff --git a/guix/build/go-module-build-system.scm b/guix/build/go-module-build-system.scm
new file mode 100644
index 0000000000..8eeaac426c
--- /dev/null
+++ b/guix/build/go-module-build-system.scm
@@ -0,0 +1,459 @@
+(define-module (guix build go-module-build-system)
+ #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+ #:use-module (guix build union)
+ #:use-module (guix build utils)
+ #:use-module (srfi srfi-71)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (ice-9 match)
+ #:export (%standard-phases
+ go-module-build))
+
+;;; Commentary:
+;;;
+;;; Build procedure for packages using the module aware Go build
+;;; system. The go build system aggressively tries to fetch dependencies
+;;; or even compiler toolchains. While it may be possible to convince it to
+;;; not do that, we opt for not fighting it, and instead let it fetch
+;;; everything it wants to, served from the local filesystem in directories we
+;;; populate.
+;;;
+;;; The GOPROXY protocol [1] permits using file:// urls. From the manual:
+;;;
+;;; A module proxy is an HTTP server that can respond to GET requests for
+;;; paths specified below. The requests have no query parameters, and no
+;;; specific headers are required, so even a site serving from a fixed file
+;;; system (including a file:// URL) can be a module proxy.
+;;;
+;;; Go dependencies tend to be rigidly specified to very specific versions,
+;;; with hashes, which the go build tooling will figure out. This does not
+;;; work too well with guix' model, where we want to specify dependencies more
+;;; fludily (e.g. with input substitutions). Go modules also tend to specify
+;;; (minimum) toolchains which is not strictly necessary from a language
+;;; feature perspective, which breaks builds with older compilers.
+;;;
+;;; To address these problems, we always write a fresh go.mod file based on
+;;; the build-inputs. There is no guarantee that there even is a go.mod file
+;;; in the source, especially for older projects. Go build uses this file to
+;;; "download" from our just-assembled goproxy, which makes it happy. This
+;;; also clears any toolchain directive which makes the build accept the go
+;;; compiler through build-inputs. We populate the goproxy with just-in-time
+;;; built zips, version, and info files. This is a separate phase so that
+;;; additional build steps can be added between building the proxy and running go
+;;; build.
+;;;
+;;; The build system is compatible with go-build-system, in the sense that
+;;; go-build-system can be used as build-inputs, and vice versa, because they
+;;; both use the same $out/source/.
+;;;
+;;; We re-used compiled packages. The Go build system creates a
+;;; content-addressable build cache, which we install into build output, and
+;;; use to seed downstream builds. Go programs are (mostly) statically
+;;; linked, so this is roughly equivalent of installing lib.a. Note that this
+;;; only works when the build-input is built with go-module-build-system.
+;;;
+;;; [1] https://go.dev/ref/mod#goproxy-protocol
+;;;
+;;; Code:
+
+(define (find-single-file dir regex)
+ "Find the file in DIR matching the REGEX, and fail unless there is
+exactly one match."
+ (let ((files (find-files dir regex #:directories? #f)))
+ (unless (eq? 1 (length files))
+ (error "Expected exactly one file matching pattern, found:" files))
+ (car files)))
+
+(define (go-path-escape path)
+ "Escape a module path by replacing every uppercase letter with an
+exclamation mark followed with its lowercase equivalent, as per the module
+Escaped Paths specification (see:
+https://godoc.org/golang.org/x/mod/module#hdr-Escaped_Paths)."
+ (define (escape occurrence)
+ (string-append "!" (string-downcase (match:substring occurrence))))
+ (regexp-substitute/global #f "[A-Z]" path 'pre escape 'post))
+
+(define (call-with-append-file path f)
+ "call-with-output-file, but appends to the file if it exists rather
+than truncating it"
+ (let ((file (open-file path "a")))
+ (f file)
+ (close file)))
+
+(define (set-cache-action-epoch f)
+ "Set go build cache action entry timestamp to 0
+
+The go build cache action entries (xxxx-a) record a timestamp, which
+would break reproducibility of the build cache. Set it to all-zeros."
+ ;; The file has 5 columns, tand the timestamp is the rightmost one
+ ;; <version> <action-id> <output-id> <size> <timestamp>
+ ;;
+ ;; The timestamp seems to be in nanoseconds since epoch. We use
+ ;; replacement to avoid potential problems with whitespace
+ ;; sensitivity.
+ (let* ((file-line (read-first-line f))
+ (start-timestamp (string-skip-right file-line char-set:digit))
+ (end-timestamp (string-length file-line))
+ (base-line (substring file-line 0 start-timestamp))
+ (zero-timestamp (make-string (- end-timestamp start-timestamp) #\0)))
+ (call-with-output-file f
+ (lambda (port)
+ (format port "~a~a~%" base-line zero-timestamp)))))
+
+(define (copy-nonlink-recursively src-dir dst-dir)
+ "Recursively install files from src/ to dst/, skipping symlinks"
+ (copy-recursively src-dir dst-dir
+ #:log #f
+ #:copy-file
+
This message was truncated. Download the full message here.
J
J
Jørgen Kvalsvik wrote on 13 May 02:55 -0700
[PATCH 1/2] guix: Add downloader for go modules from GOPROXY
(address . 78404@debbugs.gnu.org)
20250513095522.4313-1-j@lambda.is
Add a new downloader which implements, approximately, the download step of go
get $module. This is a convenient way of downloading zips with go modules by
just specifying the version and import path, as an alternative to git clone,
or awkward https://fetches. This is particularly useful for sources that are
processed before release (like autotools generated files in tarballs) or
generated modules.

* guix/go-mod-download.scm: New file.
* Makefile.am (MODULES): Add it.

Change-Id: Ibb3b3ee70833fd0ea0c64278c95b8cb96a0be639
---
Makefile.am | 1 +
guix/go-mod-download.scm | 126 +++++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 guix/go-mod-download.scm

Toggle diff (146 lines)
diff --git a/Makefile.am b/Makefile.am
index ec5220333e..b5fb81f412 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -102,6 +102,7 @@ MODULES = \
guix/android-repo-download.scm \
guix/bzr-download.scm \
guix/git-download.scm \
+ guix/go-mod-download.scm \
guix/hg-download.scm \
guix/hash.scm \
guix/swh.scm \
diff --git a/guix/go-mod-download.scm b/guix/go-mod-download.scm
new file mode 100644
index 0000000000..7024362318
--- /dev/null
+++ b/guix/go-mod-download.scm
@@ -0,0 +1,126 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Jørgen Kvalsvik <j@lambda.is>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix go-mod-download)
+ #:use-module (guix gexp)
+ #:use-module (guix store)
+ #:use-module (guix packages)
+ #:use-module (guix monads)
+ #:use-module (guix records)
+ #:use-module (guix modules)
+ #:use-module (guix download)
+ #:use-module (ice-9 string-fun)
+ #:use-module (ice-9 regex)
+ #:autoload (guix build-system gnu) (standard-packages)
+ #:export (go-mod-reference
+ go-mod-reference?
+ go-mod-reference-path
+ go-mod-reference-version
+ go-mod-fetch))
+
+;;; Commentary:
+;;;
+;;; An <origin> method that fetches a go module [1] from a GOPROXY. A go
+;;; module is usually identified as a vcs (usually git) repository,
+;;; e.g. github.com/calmh/du or golang.org/x/net.
+;;;
+;;; This is mostly a regular http(s) fetch some custom url building. Unless
+;;; goproxy is specified, it fetches from the default goproxy
+;;; https://proxy.golang.org. This is mostly just a convenience -- the same
+;;; code could be fetched directly, but sometimes libraries are only
+;;; practically available through a goproxy. Such a module would be
+;;; https://pkg.go.dev/buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go
+;;;
+;;; [1] https://go.dev/ref/mod
+;;;
+;;; Code:
+
+(define-record-type* <go-mod-reference>
+ go-mod-reference make-go-mod-reference
+ go-mod-reference?
+ (path go-mod-reference-path)
+ (version go-mod-reference-version)
+ (goproxy go-mod-reference-goproxy (default "https://proxy.golang.org")))
+
+(define (default-unzip)
+ "Return the 'unzip' package. This is a lazy reference so that we don't
+depend on (gnu packages compression)."
+ (module-ref (resolve-interface '(gnu packages compression)) 'unzip))
+
+;; Fetch a go module e.g. golang.org/x/net from a goproxy.
+(define* (go-mod-fetch ref hash-algo hash
+ #:optional name
+ #:key (system (%current-system))
+ (guile (default-guile))
+ (unzip (default-unzip)))
+ (define inputs
+ `(("unzip" ,unzip)
+ ,@(standard-packages)))
+
+ (define (go-path-escape path)
+ "Escape a module path by replacing every uppercase letter with an
+exclamation mark followed with its lowercase equivalent, as per the module
+Escaped Paths specification (see:
+https://godoc.org/golang.org/x/mod/module#hdr-Escaped_Paths)."
+ (define (escape occurrence)
+ (string-append "!" (string-downcase (match:substring occurrence))))
+ (regexp-substitute/global #f "[A-Z]" path 'pre escape 'post))
+
+ (define modules
+ (source-module-closure '((guix build utils))))
+
+ (define (build mod.zip)
+ (with-imported-modules modules
+ #~(begin
+ (use-modules (guix build utils))
+ (let* ((pkg-path (getenv "go-mod path"))
+ (pkg-version (getenv "go-mod version"))
+ (pkg-root (string-append pkg-path "@v" pkg-version))
+ (go.mod (string-append pkg-root "/go.mod")))
+
+ (invoke (string-append #+unzip "/bin/unzip") "-q" #$mod.zip)
+ ;; The sources in the zip are in the subdir
+ ;; $path@v$version/, but we want our sources at root.
+ (copy-recursively pkg-root #$output)))))
+
+ (define path-as-store-name
+ (string-append
+ (string-replace-substring (go-mod-reference-path ref) "/" "-")
+ "-" (go-mod-reference-version ref)))
+
+ (define url/zip
+ (format #f "~a/~a/@v/v~a.zip"
+ (go-mod-reference-goproxy ref)
+ (go-path-escape (go-mod-reference-path ref))
+ (go-mod-reference-version ref)))
+
+ (mlet* %store-monad ((guile-for-build (package->derivation guile system))
+ (mod (url-fetch url/zip hash-algo hash
+ (or name (string-append path-as-store-name ".zip"))
+ #:system system
+ #:guile guile)))
+ (gexp->derivation (or name path-as-store-name) (build mod)
+ #:script-name "go-mod-fetch"
+ #:env-vars
+ `(("go-mod path" . ,(go-mod-reference-path ref))
+ ("go-mod version" . ,(go-mod-reference-version ref)))
+ #:leaked-env-vars '("http_proxy" "https_proxy"
+ "LC_ALL" "LC_MESSAGES" "LANG"
+ "COLUMNS")
+ #:system system
+ #:local-build? #t)))
--
2.39.5
J
J
Jørgen Kvalsvik wrote on 15 May 01:16 -0700
[PATCH 1/4] guix: Add downloader for go modules from GOPROXY
(address . 78404@debbugs.gnu.org)(name . Jørgen Kvalsvik)(address . j@lambda.is)
20250515081635.575459-2-j@lambda.is
Add a new downloader which implements, approximately, the download step of go
get $module. This is a convenient way of downloading zips with go modules by
just specifying the version and import path, as an alternative to git clone,
or awkward https://fetches. This is particularly useful for sources that are
processed before release (like autotools generated files in tarballs) or
generated modules.

* guix/go-mod-download.scm: New file.
* Makefile.am (MODULES): Add it.

Change-Id: Ibb3b3ee70833fd0ea0c64278c95b8cb96a0be639
---
Makefile.am | 1 +
guix/go-mod-download.scm | 126 +++++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 guix/go-mod-download.scm

Toggle diff (146 lines)
diff --git a/Makefile.am b/Makefile.am
index ec5220333e..b5fb81f412 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -102,6 +102,7 @@ MODULES = \
guix/android-repo-download.scm \
guix/bzr-download.scm \
guix/git-download.scm \
+ guix/go-mod-download.scm \
guix/hg-download.scm \
guix/hash.scm \
guix/swh.scm \
diff --git a/guix/go-mod-download.scm b/guix/go-mod-download.scm
new file mode 100644
index 0000000000..7024362318
--- /dev/null
+++ b/guix/go-mod-download.scm
@@ -0,0 +1,126 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Jørgen Kvalsvik <j@lambda.is>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix go-mod-download)
+ #:use-module (guix gexp)
+ #:use-module (guix store)
+ #:use-module (guix packages)
+ #:use-module (guix monads)
+ #:use-module (guix records)
+ #:use-module (guix modules)
+ #:use-module (guix download)
+ #:use-module (ice-9 string-fun)
+ #:use-module (ice-9 regex)
+ #:autoload (guix build-system gnu) (standard-packages)
+ #:export (go-mod-reference
+ go-mod-reference?
+ go-mod-reference-path
+ go-mod-reference-version
+ go-mod-fetch))
+
+;;; Commentary:
+;;;
+;;; An <origin> method that fetches a go module [1] from a GOPROXY. A go
+;;; module is usually identified as a vcs (usually git) repository,
+;;; e.g. github.com/calmh/du or golang.org/x/net.
+;;;
+;;; This is mostly a regular http(s) fetch some custom url building. Unless
+;;; goproxy is specified, it fetches from the default goproxy
+;;; https://proxy.golang.org. This is mostly just a convenience -- the same
+;;; code could be fetched directly, but sometimes libraries are only
+;;; practically available through a goproxy. Such a module would be
+;;; https://pkg.go.dev/buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go
+;;;
+;;; [1] https://go.dev/ref/mod
+;;;
+;;; Code:
+
+(define-record-type* <go-mod-reference>
+ go-mod-reference make-go-mod-reference
+ go-mod-reference?
+ (path go-mod-reference-path)
+ (version go-mod-reference-version)
+ (goproxy go-mod-reference-goproxy (default "https://proxy.golang.org")))
+
+(define (default-unzip)
+ "Return the 'unzip' package. This is a lazy reference so that we don't
+depend on (gnu packages compression)."
+ (module-ref (resolve-interface '(gnu packages compression)) 'unzip))
+
+;; Fetch a go module e.g. golang.org/x/net from a goproxy.
+(define* (go-mod-fetch ref hash-algo hash
+ #:optional name
+ #:key (system (%current-system))
+ (guile (default-guile))
+ (unzip (default-unzip)))
+ (define inputs
+ `(("unzip" ,unzip)
+ ,@(standard-packages)))
+
+ (define (go-path-escape path)
+ "Escape a module path by replacing every uppercase letter with an
+exclamation mark followed with its lowercase equivalent, as per the module
+Escaped Paths specification (see:
+https://godoc.org/golang.org/x/mod/module#hdr-Escaped_Paths)."
+ (define (escape occurrence)
+ (string-append "!" (string-downcase (match:substring occurrence))))
+ (regexp-substitute/global #f "[A-Z]" path 'pre escape 'post))
+
+ (define modules
+ (source-module-closure '((guix build utils))))
+
+ (define (build mod.zip)
+ (with-imported-modules modules
+ #~(begin
+ (use-modules (guix build utils))
+ (let* ((pkg-path (getenv "go-mod path"))
+ (pkg-version (getenv "go-mod version"))
+ (pkg-root (string-append pkg-path "@v" pkg-version))
+ (go.mod (string-append pkg-root "/go.mod")))
+
+ (invoke (string-append #+unzip "/bin/unzip") "-q" #$mod.zip)
+ ;; The sources in the zip are in the subdir
+ ;; $path@v$version/, but we want our sources at root.
+ (copy-recursively pkg-root #$output)))))
+
+ (define path-as-store-name
+ (string-append
+ (string-replace-substring (go-mod-reference-path ref) "/" "-")
+ "-" (go-mod-reference-version ref)))
+
+ (define url/zip
+ (format #f "~a/~a/@v/v~a.zip"
+ (go-mod-reference-goproxy ref)
+ (go-path-escape (go-mod-reference-path ref))
+ (go-mod-reference-version ref)))
+
+ (mlet* %store-monad ((guile-for-build (package->derivation guile system))
+ (mod (url-fetch url/zip hash-algo hash
+ (or name (string-append path-as-store-name ".zip"))
+ #:system system
+ #:guile guile)))
+ (gexp->derivation (or name path-as-store-name) (build mod)
+ #:script-name "go-mod-fetch"
+ #:env-vars
+ `(("go-mod path" . ,(go-mod-reference-path ref))
+ ("go-mod version" . ,(go-mod-reference-version ref)))
+ #:leaked-env-vars '("http_proxy" "https_proxy"
+ "LC_ALL" "LC_MESSAGES" "LANG"
+ "COLUMNS")
+ #:system system
+ #:local-build? #t)))
--
2.39.5
J
J
Jørgen Kvalsvik wrote on 15 May 01:16 -0700
[PATCH 4/4] gnu: Build with go-module-build-system
(address . 78404@debbugs.gnu.org)(name . Jørgen Kvalsvik)(address . j@lambda.is)
20250515081635.575459-5-j@lambda.is
* gnu/packages/golang-xyz.scm (go-github-com-burntsushi-toml): Use
go-module-build-system

Change-Id: Ic9222044eecc53053898d978d9bc4f79280fec8b
---
gnu/packages/golang-xyz.scm | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)

Toggle diff (18 lines)
diff --git a/gnu/packages/golang-xyz.scm b/gnu/packages/golang-xyz.scm
index bcf758e8fe..0bae8577d8 100644
--- a/gnu/packages/golang-xyz.scm
+++ b/gnu/packages/golang-xyz.scm
@@ -2297,10 +2297,7 @@ (define-public go-github-com-burntsushi-toml
(file-name (git-file-name name version))
(sha256
(base32 "1vk0s7pcn80hkx0lcyws509gqs42c8y1rppv05zxiqj0yn2zrjnx"))))
- (build-system go-build-system)
- (arguments
- (list
- #:import-path "github.com/BurntSushi/toml"))
+ (build-system go-module-build-system)
(home-page "https://github.com/BurntSushi/toml")
(synopsis "Toml parser and encoder for Go")
(description
--
2.39.5
J
J
Jørgen Kvalsvik wrote on 15 May 01:16 -0700
[PATCH 3/4] gnu: Build go-ulid with go-module-build system
(address . 78404@debbugs.gnu.org)(name . Jørgen Kvalsvik)(address . j@lambda.is)
20250515081635.575459-4-j@lambda.is
* gnu/packages/golang-xyz.scm (go-ulid): Use go-module-build-system.

Change-Id: I4ee3ae8cdb5a17df1478aeef555ad8d6fcdbf437
---
gnu/packages/golang-xyz.scm | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)

Toggle diff (34 lines)
diff --git a/gnu/packages/golang-xyz.scm b/gnu/packages/golang-xyz.scm
index 21d2b05abc..bcf758e8fe 100644
--- a/gnu/packages/golang-xyz.scm
+++ b/gnu/packages/golang-xyz.scm
@@ -79,9 +79,11 @@
(define-module (gnu packages golang-xyz)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (guix build-system go)
+ #:use-module (guix build-system go-module)
#:use-module (guix build-system copy)
#:use-module (guix gexp)
#:use-module (guix git-download)
+ #:use-module (guix go-mod-download)
#:use-module (guix packages)
#:use-module (guix utils)
#:use-module (gnu packages)
@@ -18217,11 +18219,13 @@ (define-public go-tomlv
(define-public go-ulid
(package/inherit go-github-com-oklog-ulid-v2
(name "go-ulid")
+ (build-system go-module-build-system)
(arguments
(list
#:install-source? #f
- #:import-path "github.com/oklog/ulid/v2/cmd/ulid"
- #:unpack-path "github.com/oklog/ulid/v2"))
+ #:install-cache? #f
+ #:build-targets '()
+ #:install-targets '("./cmd/ulid")))
(description
(string-append (package-description go-github-com-oklog-ulid-v2)
"\nThis package provides a command line interface (CLI)
--
2.39.5
J
J
Jørgen Kvalsvik wrote on 15 May 01:16 -0700
[PATCH 0/4] Go module aware build system
(address . 78404@debbugs.gnu.org)(name . Jørgen Kvalsvik)(address . j@lambda.is)
20250515081635.575459-1-j@lambda.is
Hi,

Here's a slightly revised patch series which fixes a couple of issues.

`go build` is now invoked in a loop, which allows specifying both patterns and
specific binaries. I needed this for a test case where a project built
internal/echo-plugin for use in tests. There is no real downside to doing this
in a loop as far as I am aware, and install is uses a loop.

I also fixed cross compiling. I did not test it extensively, but cross
compiling using the go-build-system doesn't seem to work well, because it sets
the GOBIN env var, which makes the compiler error out.

From my x86_64-linux system:

~$ file $(guix build go-ulid --target=x86_64-linux-gnu)/bin/ulid
/gnu/store/i258kcbn5g8s97bz26q2rkic7ar5667d-go-ulid-2.1.0/bin/ulid:
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked,
Go BuildID=_VaiUxKWsiIAlkWq7ODq/4GeipEcLMBNQHuHSA3kh/kxL5wZXJ8V0hvXEoJaF4/hO3bq-ZKZ51CEH4uY7rc, stripped

~$ file $(guix build go-ulid --target=aarch64-linux-gnu)/bin/ulid
/gnu/store/f4yzrwd198xhk4194xrfvqdk5yqz0nac-go-ulid-2.1.0/bin/ulid:
ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked,
Go BuildID=Z_1o0etV-eeUchVexa3A/bUxxm0FmCf6iLc1enI_T/ET4MK0HAx8OyRYBFV73n/j-RubHuPPW1R02OqsNu9, stripped

~$ file $(guix build go-ulid --target=arm-linux-gnueabihf)/bin/ulid
/gnu/store/q6f1l82bl1lpvrciibkiffjfsxn8vz0x-go-ulid-2.1.0/bin/ulid: ELF
32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked,
Go BuildID=NVoNI56N4Ri60CnAR78t/9bQS_Y8jZabn5Y1v80w-/D1_8mW1gwE0Bdtx-heU-/aU1ZqDUxXoi_ToBSeHUL, stripped


I also added two demos, so it would be easier for you to try and to show the
compatibility between packages using the two build systems. One is an
executable, and one where is a library.

~$ guix build go-github-com-burntsushi-toml go-ulid

Jørgen Kvalsvik (4):
guix: Add downloader for go modules from GOPROXY
guix: Add module-aware build system for go
gnu: Build go-ulid with go-module-build system
gnu: Build with go-module-build-system

Makefile.am | 3 +
gnu/packages/golang-xyz.scm | 13 +-
guix/build-system/go-module.scm | 267 +++++++++++++++
guix/build-system/zig.scm | 4 +-
guix/build/go-module-build-system.scm | 473 ++++++++++++++++++++++++++
guix/go-mod-download.scm | 126 +++++++
6 files changed, 877 insertions(+), 9 deletions(-)
create mode 100644 guix/build-system/go-module.scm
create mode 100644 guix/build/go-module-build-system.scm
create mode 100644 guix/go-mod-download.scm

--
2.39.5
J
J
Jørgen Kvalsvik wrote on 15 May 01:16 -0700
[PATCH 2/4] guix: Add module-aware build system for go
(address . 78404@debbugs.gnu.org)(name . Jørgen Kvalsvik)(address . j@lambda.is)
20250515081635.575459-3-j@lambda.is
Add a new build system for go, using go modules. This build system is partly
compatible with go-build-system; they can both be used as build inputs to each
other, but their options are incompatible.

The main departure from go-build-system is that go-build-system tries to build
a workspace [1], where go-module-build-system builds a goproxy + go.mod file
and lets `go build` do what it wants to.

Most go libraries should be straight forward to build. For example, this is
the package definition for golang.org/x/sync@0.12:

(define-public go-golang-org-x-sync
(package
(name "go-golang-org-x-sync")
(version "0.12.0")
(source
(origin
(method go-mod-fetch)
(uri (go-mod-reference
(path "golang.org/x/sync")
(version version)))
(sha256
(base32 "00pd84ah4xd5sjax8rxv98xbnwrvkk8clazl3kq1xrbkmvjq2m53"))))
(build-system go-module-build-system)
(synopsis "Go Sync")
(description "This repository provides Go concurrency primitives
in addition to the ones provided by the language and \"sync\" and
\"sync/atomic\" packages.")
(license license:bsd-3)))

The build system also supports higher resolution build-, test-, and install
targets, re-use of compiled files, and options for common build tweaks.


* guix/build/go-module-build-system.scm: New file.
* guix/build-system/go-module.scm: New file.
* Makefile.am (MODULES): Add them.

Change-Id: I47a028ab8f95fd3a338036480dbad6677e9c50a5
---
Makefile.am | 2 +
guix/build-system/go-module.scm | 267 +++++++++++++++
guix/build-system/zig.scm | 4 +-
guix/build/go-module-build-system.scm | 473 ++++++++++++++++++++++++++
4 files changed, 743 insertions(+), 3 deletions(-)
create mode 100644 guix/build-system/go-module.scm
create mode 100644 guix/build/go-module-build-system.scm

Toggle diff (405 lines)
diff --git a/Makefile.am b/Makefile.am
index b5fb81f412..12446e6bb4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -168,6 +168,7 @@ MODULES = \
guix/build-system/glib-or-gtk.scm \
guix/build-system/gnu.scm \
guix/build-system/go.scm \
+ guix/build-system/go-module.scm \
guix/build-system/guile.scm \
guix/build-system/haskell.scm \
guix/build-system/julia.scm \
@@ -227,6 +228,7 @@ MODULES = \
guix/build/minify-build-system.scm \
guix/build/font-build-system.scm \
guix/build/go-build-system.scm \
+ guix/build/go-module-build-system.scm \
guix/build/android-repo.scm \
guix/build/asdf-build-system.scm \
guix/build/bzr.scm \
diff --git a/guix/build-system/go-module.scm b/guix/build-system/go-module.scm
new file mode 100644
index 0000000000..a61f591431
--- /dev/null
+++ b/guix/build-system/go-module.scm
@@ -0,0 +1,267 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Jørgen Kvalsvik <j@lambda.is>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system go-module)
+ #:use-module (guix gexp)
+ #:use-module (guix monads)
+ #:use-module (guix packages)
+ #:use-module (guix store)
+ #:use-module (guix utils)
+ #:use-module (guix search-paths)
+ #:use-module (guix build-system)
+ #:use-module (guix build-system gnu)
+ #:use-module ((guix build-system go) #:prefix go-build:)
+ #:use-module (srfi srfi-1)
+ #:export (%go-module-build-system-modules
+ go-module-build
+ go-module-build-system))
+
+;;; Commentary:
+;;;
+;;; Build procedure for packages using the module aware Go build system.
+;;;
+;;; Code:
+
+(define %go-module-build-system-modules
+ ;; Build-side modules imported by default.
+ `((guix build go-module-build-system)
+ (guix build union)
+ ,@%default-gnu-imported-modules))
+
+(define (default-go)
+ ;; Lazily resolve the binding to avoid a circular dependency.
+ (let ((go (resolve-interface '(gnu packages golang))))
+ (module-ref go 'go)))
+
+(define (default-gccgo)
+ ;; Lazily resolve the binding to avoid a circular dependency.
+ (let ((gcc (resolve-interface '(gnu packages gcc))))
+ (module-ref gcc 'gccgo-12)))
+
+(define (default-zip)
+ "Return the 'zip' package. This is a lazy reference so that we don't
+depend on (gnu packages compression)."
+ (let ((distro (resolve-interface '(gnu packages compression))))
+ (module-ref distro 'zip)))
+
+(define* (lower name
+ #:key source inputs native-inputs outputs system target
+ (go (if (supported-package? (default-go))
+ (default-go)
+ (default-gccgo)))
+ (zip (default-zip))
+ #:allow-other-keys
+ #:rest arguments)
+ "Return a bag for NAME."
+ (define private-keywords
+ '(#:target #:inputs #:native-inputs #:go #:zip))
+
+ (bag
+ (name name)
+ (system system)
+ (target target)
+ (build-inputs `(,@(if source `(("source" ,source)) '())
+ ,@`(("go" ,go) ("zip" ,zip))
+ ,@native-inputs
+ ,@(if target '() inputs)
+ ,@(if target (standard-cross-packages target 'host) '())
+ ;; Keep the standard inputs of 'gnu-build-system'.
+ ,@(standard-packages)))
+ (host-inputs (if target inputs '()))
+ (target-inputs (if target (standard-cross-packages target 'target) '()))
+ (outputs outputs)
+ (build (if target go-cross-module-build go-module-build))
+ (arguments (strip-keyword-arguments private-keywords arguments))))
+
+(define* (go-module-build name inputs
+ #:key
+ source
+ (phases '%standard-phases)
+ (outputs '("out"))
+ (search-paths '())
+ (go-flags '())
+ (ld-flags '("-s" "-w"))
+ (tags '())
+ (build-targets '("./..."))
+ (test-targets '("./..."))
+ (install-targets '())
+ (test-flags '())
+ (module-path #f)
+ (trimpath? #t)
+ (cgo? #f)
+ (tests? #t)
+ (build-output-dir? #f)
+ (skip-build? #f)
+ (install-source? #t)
+ (install-cache? #t)
+ (parallel-build? #t)
+ (parallel-tests? #t)
+ (environment-variables '())
+ (system (%current-system))
+ (goarch #f)
+ (goos #f)
+ (guile #f)
+ (substitutable? #t)
+ (imported-modules %go-module-build-system-modules)
+ (modules '((guix build go-module-build-system)
+ (guix build utils))))
+
+ (define builder
+ (with-imported-modules
+ imported-modules
+ #~(begin
+ (use-modules #$@(sexp->gexp modules))
+ (go-module-build #:name #$name
+ #:source #+source
+ #:system #$system
+ #:go-flags '#$go-flags
+ #:ld-flags '#$ld-flags
+ #:tags '#$tags
+ #:build-targets '#$build-targets
+ #:test-targets '#$test-targets
+ #:install-targets '#$install-targets
+ #:test-flags '#$test-flags
+ #:module-path '#$module-path
+ #:trimpath? #$trimpath?
+ #:cgo? '#$cgo?
+ #:tests? #$tests?
+ #:build-output-dir? #$build-output-dir?
+ #:skip-build? #$skip-build?
+ #:install-source? #$install-source?
+ #:install-cache? #$install-cache?
+ #:parallel-build? #$parallel-build?
+ #:parallel-tests? #$parallel-tests?
+ #:environment-variables '#$environment-variables
+ #:goarch #$goarch
+ #:goos #$goos
+ #:phases #$phases
+ #:outputs #$(outputs->gexp outputs)
+ #:search-paths '#$(map
+ search-path-specification->sexp
+ search-paths)
+ #:inputs #$(input-tuples->gexp inputs)))))
+
+ (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+ system #:graft? #f)))
+ (gexp->derivation name builder
+ #:system system
+ #:guile-for-build guile)))
+
+(define* (go-cross-module-build name
+ #:key
+ source target
+ build-inputs target-inputs host-inputs
+ (phases '%standard-phases)
+ (outputs '("out"))
+ (search-paths '())
+ (native-search-paths '())
+ (go-flags '())
+ (ld-flags '("-s" "-w"))
+ (tags '())
+ (build-targets '("./..."))
+ (test-targets '())
+ (install-targets '())
+ (tests? #f) ; nothing can be done
+ (test-flags '())
+ (module-path #f)
+ (trimpath? #t)
+ (cgo? #f)
+ (build-output-dir? #f)
+ (skip-build? #f)
+ (install-source? #t)
+ (install-cache? #t)
+ (parallel-build? #t)
+ (parallel-tests? #f)
+ (environment-variables '())
+ (system (%current-system))
+ (goarch (if target (first (go-build:go-target target)) #f))
+ (goos (if target (last (go-build:go-target target)) #f))
+ (guile #f)
+ (imported-modules %go-module-build-system-modules)
+ (modules '((guix build go-module-build-system)
+ (guix build utils)))
+ (substitutable? #t))
+
+ (define builder
+ (with-imported-modules
+ imported-modules
+ #~(begin
+ (use-modules #$@(sexp->gexp modules))
+
+ (define %build-host-inputs
+ #+(input-tuples->gexp build-inputs))
+
+ (define %build-target-inputs
+ (append #$(input-tuples->gexp host-inputs)
+ #+(input-tuples->gexp target-inputs)))
+
+ (define %build-inputs
+ (append %build-host-inputs %build-target-inputs))
+
+ (go-module-build #:name #$name
+ #:source #+source
+ #:system #$system
+ #:go-flags '#$go-flags
+ #:ld-flags '#$ld-flags
+ #:tags '#$tags
+ #:build-targets '#$build-targets
+ #:test-targets '#$test-targets
+ #:install-targets '#$install-targets
+ #:test-flags '#$test-flags
+ #:module-path '#$module-path
+ #:trimpath? #$trimpath?
+ #:cgo? '#$cgo?
+ #:tests? #$tests?
+ #:build-output-dir? #$build-output-dir?
+ #:skip-build? #$skip-build?
+ #:install-source? #$install-source?
+ #:install-cache? #$install-cache?
+ #:parallel-build? #$parallel-build?
+ #:parallel-tests? #$parallel-tests?
+ #:environment-variables '#$environment-variables
+ #:target #$target
+ #:goarch #$goarch
+ #:goos #$goos
+ #:phases #$phases
+ #:outputs #$(outputs->gexp outputs)
+ #:make-dynamic-linker-cache? #f
+ #:search-paths '#$(map
+ search-path-specification->sexp
+ search-paths)
+ #:native-search-paths '#$(map
+ search-path-specification->sexp
+ native-search-paths)
+ #:native-inputs %build-host-inputs
+ #:inputs %build-inputs))))
+
+ (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+ system #:graft? #f)))
+ (gexp->derivation name builder
+ #:system system
+ #:target target
+ #:graft? #f
+ #:substitutable? substitutable?
+ #:guile-for-build guile)))
+
+(define go-module-build-system
+ (build-system
+ (name 'go-module)
+ (description "Go Module Build System")
+ (lower lower)))
+
+;;; go-module.scm ends here
diff --git a/guix/build-system/zig.scm b/guix/build-system/zig.scm
index 43d6ee977c..238964eb22 100644
--- a/guix/build-system/zig.scm
+++ b/guix/build-system/zig.scm
@@ -206,9 +206,7 @@ (define private-keywords
;; Keep the standard inputs of 'gnu-build-system'.
,@(standard-packages)))
(host-inputs (if target inputs '()))
- (target-inputs (if target
- (standard-cross-packages target 'target)
- '()))
+ (target-inputs (if target (standard-cross-packages target 'target) '()))
(outputs outputs)
(build (if target zig-cross-build zig-build))
(arguments (strip-keyword-arguments private-keywords arguments))))
diff --git a/guix/build/go-module-build-system.scm b/guix/build/go-module-build-system.scm
new file mode 100644
index 0000000000..adc36df356
--- /dev/null
+++ b/guix/build/go-module-build-system.scm
@@ -0,0 +1,473 @@
+(define-module (guix build go-module-build-system)
+ #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+ #:use-module (guix build union)
+ #:use-module (guix build utils)
+ #:use-module (srfi srfi-71)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (ice-9 match)
+ #:export (%standard-phases
+ go-module-build))
+
+;;; Commentary:
+;;;
+;;; Build procedure for packages using the module aware Go build
+;;; system. The go build system aggressively tries to fetch dependencies
+;;; or even compiler toolchains. While it may be possible to convince it to
+;;; not do that, we opt for not fighting it, and instead let it fetch
+;;; everything it wants to, served from the local filesystem in directories we
+;;; populate.
+;;;
+;;; The GOPROXY protocol [1] permits using file:// urls. From the manual:
+;;;
+;;; A module proxy is an HTTP server that can respond to GET requests for
+;;; paths specified below. The requests have no query parameters, and no
+;;; specific headers are required, so even a site serving from a fixed file
+;;; system (including a file:// URL) can be a module proxy.
+;;;
+;;; Go dependencies tend to be rigidly specified to very specific versions,
+;;; with hashes, which the go build tooling will figure out. This does not
+;;; work too well with guix' model, where we want to specify dependencies more
+;;; fludily (e.g. with input substitutions). Go modules also tend to specify
+;;; (minimum) toolchains which is not strictly necessary from a language
+;;; feature perspective, which breaks builds with older compilers.
+;;;
+;;; To address these problems, we always write a fresh go.mod file based on
+;;; the build-inputs. There is no guarantee that there even is a go.mod file
+;;; in the source, especially for older projects. Go build uses this file to
+;;; "download" from our just-assembled goproxy, which makes it happy. This
+;;; also clears any toolchain directive which makes the build accept the go
+;;; compiler through build-inputs. We populate the goproxy with just-in-time
+;;; built zips, version, and info files. This is a separate phase so that
+;;; additional build steps can be added between building the proxy and running go
+;;; build.
+;;;
+;;; The build system is compatible with go-build-system, in the sense that
+;;; go-build-system can be used as build-inputs, and vice versa, because they
+;;; both use the same $out/source/.
+;;;
+;;; We re-used compiled packages. The Go build system creates a
+;;; content-addressable build cache, which we install into build output, and
+;;; use to seed downstream builds. Go programs are (mostly) statically
+;;; linked, so this is roughly equivalent of installing lib.a. Note that this
+;;; only works when the build-input is built with go-module-build-system.
+;;;
+;;; [1] https://go.dev/ref/mod#goproxy-protocol
+;;;
+;;; Code:
+
+(define (find-single-file dir regex)
+ "Find the file in DIR matching the REGEX, and fail unless there is
+exactly one match."
+ (let ((files (find-files dir regex #:directories? #f)))
+ (unless (eq? 1 (length files))
+ (error "Expected exactly one file matching pattern, found:" files))
+ (car files)))
+
+(define (go-path-escape path)
+ "Escape a module path by replacing every uppercase letter with an
+exclamation mark followed with its lowercase equivalent, as per the module
+Escaped Paths specification (see:
+https://godoc.org/golang.org/x/mod/module#hdr-Escaped_Paths)."
+ (define (escape occurrence)
+ (string-append "!" (string-downcase (match:substring occurrence))))
+ (regexp-substitute/global #f "[A-Z]" path 'pre escape 'post))
+
+(define (call-with-append-file path f)
+ "call-with-output-file, but appends to the file if it exists rather
+than truncating it"
+ (let ((file (open-file path "a")))
+ (f file)
+ (close file)))
+
+(define (set-cache-action-epoch f)
+ "Set go build cache action entry timestamp to 0
+
+The go build cache action entries (xxxx-a) record a timestamp, which
+would break reproducibility of the build cache. Set it to all-zeros."
+ ;; The file has 5 columns, tand the timestamp is the rightmost one
+ ;; <version> <action-id> <output-id> <size> <timestamp>
+ ;;
+ ;; The time
This message was truncated. Download the full message here.
S
S
Sharlatan Hellseher wrote on 18 Jul 15:12 -0700
[PATCH 0/2] Go: Module aware build system
(address . 78404-done@debbugs.gnu.org)
87ldolkv29.fsf@gmail.com
Closing as discssion moved to CodeBerge, see

--
Thanks,
Oleg
-----BEGIN PGP SIGNATURE-----

iQJKBAEBCgA0FiEEmEeB3micIcJkGAhndtcnv/Ys0rUFAmh6xr4WHHNoYXJsYXRh
bnVzQGdtYWlsLmNvbQAKCRB21ye/9izStdcwD/9GcN0fWHqb8wnY74NR0rn8xypP
QG+DOVYHMpaei4uATZltV2Vjn88xq6qnbPTPGw3ZVe53ByYv9a6kWYHlfheYP2g2
l19tGi5GG/pHQydBp3gh5FBd2rwMEqen0V1EINv0euG4ps1ElermEVeT2lSJaNW0
dEmodahnTFEcRdb53P8yDrtJCMqjr51fxLYyKOFVNLkOaKYZAKS6YQURm8BfUQwK
dyTWu74n5OAlOYaD4Gdd++y58i+YUc52CJ/PPgs/pStPzTGnJ9kwnovnrB+1OCua
DiGNe24HsHHuSz+SUna0UFw2pTknJPKLmY1VdENPTnpOk9hyZo9h8P+skK2KPOZH
FT9X/KzN9R/EiT0OYmpqtmMoVc22oqvsxaumhQ+ZB054B8h/Ypd9YF1CTMo20lUW
jei5AByEpk49cH6dx7Fm7yXeu6ymiSlCMoNpzhXHEAoOYmfCHb1GHXDx/Y+XR5qW
1JOBBurE5Sm3YqS7mgvPv4K6Tlon/Uh5TcHz9cVfGBNWdfsFVMET+uf4rkuuz4ko
rNtZMhOBlg+4i9r7JaJlt5HLtKryZgkdaMFQICdhpAY0wKHWOJQemHQbBC0uAQGp
ENrMMLByxwJPxsSYa6cN3qHJ0sCzi0w3vSUT2BL2cVkdMUFldTbJPd3wXsuc20pP
CA9Qhsg1UogBf4Qm7g==
=VEjU
-----END PGP SIGNATURE-----

Closed
?
Your comment

This issue is archived.

To comment on this conversation send an email to 78404@patchwise.org

To respond to this issue using the mumi CLI, first switch to it
mumi current 78404
Then, you may apply the latest patchset in this issue (with sign off)
mumi am -- -s
Or, compose a reply to this issue
mumi compose
Or, send patches to this issue
mumi send-email *.patch