From 91525b486c23d2b3d5755a88d3b51f37ecba0597 Mon Sep 17 00:00:00 2001 From: Leo Famulari Date: Tue, 19 Sep 2017 21:08:42 -0400 Subject: [PATCH] build: Add the Go build system. * guix/build-system/go.scm, guix/build/go-build-system.scm: New files. * Makefile.am (MODULES): Add new files. * doc/guix.texi (Build Systems): Document the go-build-system. --- Makefile.am | 2 + doc/guix.texi | 18 +++ guix/build-system/go.scm | 132 ++++++++++++++++++++ guix/build/go-build-system.scm | 217 +++++++++++++++++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 guix/build-system/go.scm create mode 100644 guix/build/go-build-system.scm diff --git a/Makefile.am b/Makefile.am index d054f78791..efbd07a351 100644 --- a/Makefile.am +++ b/Makefile.am @@ -80,6 +80,7 @@ MODULES = \ guix/build-system/dub.scm \ guix/build-system/emacs.scm \ guix/build-system/font.scm \ + guix/build-system/go.scm \ guix/build-system/meson.scm \ guix/build-system/minify.scm \ guix/build-system/asdf.scm \ @@ -111,6 +112,7 @@ MODULES = \ guix/build/meson-build-system.scm \ guix/build/minify-build-system.scm \ guix/build/font-build-system.scm \ + guix/build/go-build-system.scm \ guix/build/asdf-build-system.scm \ guix/build/git.scm \ guix/build/hg.scm \ diff --git a/doc/guix.texi b/doc/guix.texi index 0940ea4e71..6018198567 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -3576,6 +3576,24 @@ debugging information''), which roughly means that code is compiled with @code{-O2 -g}, as is the case for Autoconf-based packages by default. @end defvr +@defvr {Scheme Variable} go-build-system +This variable is exported by @code{(guix build-system go)}. It +implements a build procedure for Go packages using the standard +@url{https://golang.org/cmd/go/#hdr-Compile_packages_and_dependencies, +Go build mechanisms}. + +The user is expected to provide a value for the key @code{#:import-path} +and, in some cases, @code{#:unpack-path}. The +@url{https://golang.org/doc/code.html#ImportPaths, import path} +corresponds to the filesystem path expected by the package's build +scripts and any referring packages, and provides a unique way to +refer to a Go package. It is typically based on a combination of the +package source code's remote URI and filesystem hierarchy structure. In +some cases, you will need to unpack the package's source code to a +different directory structure than the one indicated by the import path, +and @code{#:unpack-path} should be used in such cases. +@end defvr + @defvr {Scheme Variable} glib-or-gtk-build-system This variable is exported by @code{(guix build-system glib-or-gtk)}. It is intended for use with packages making use of GLib or GTK+. diff --git a/guix/build-system/go.scm b/guix/build-system/go.scm new file mode 100644 index 0000000000..43599df6f4 --- /dev/null +++ b/guix/build-system/go.scm @@ -0,0 +1,132 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016 Petter +;;; Copyright © 2017 Leo Famulari +;;; +;;; 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 . + +(define-module (guix build-system go) + #:use-module (guix utils) + #:use-module (guix derivations) + #:use-module (guix search-paths) + #:use-module (guix build-system) + #:use-module (guix build-system gnu) + #:use-module (guix packages) + #:use-module (ice-9 match) + #:export (%go-build-system-modules + go-build + go-build-system)) + +;; Commentary: +;; +;; Standard build procedure for packages using the Go build system. It is +;; implemented as an extension of 'gnu-build-system'. +;; +;; Code: + +(define %go-build-system-modules + ;; Build-side modules imported and used by default. + `((guix build go-build-system) + ,@%gnu-build-system-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* (lower name + #:key source inputs native-inputs outputs system target + (go (default-go)) + #:allow-other-keys + #:rest arguments) + "Return a bag for NAME." + (define private-keywords + '(#:source #:target #:go #:inputs #:native-inputs)) + + (and (not target) ;XXX: no cross-compilation + (bag + (name name) + (system system) + (host-inputs `(,@(if source + `(("source" ,source)) + '()) + ,@inputs + + ;; Keep the standard inputs of 'gnu-build-system'. + ,@(standard-packages))) + (build-inputs `(("go" ,go) + ,@native-inputs)) + (outputs outputs) + (build go-build) + (arguments (strip-keyword-arguments private-keywords arguments))))) + +(define* (go-build store name inputs + #:key + (phases '(@ (guix build go-build-system) + %standard-phases)) + (outputs '("out")) + (search-paths '()) + (import-path "") + (unpack-path "") + (tests? #t) + (system (%current-system)) + (guile #f) + (imported-modules %go-build-system-modules) + (modules '((guix build go-build-system) + (guix build utils)))) + (define builder + `(begin + (use-modules ,@modules) + (go-build #:name ,name + #:source ,(match (assoc-ref inputs "source") + (((? derivation? source)) + (derivation->output-path source)) + ((source) + source) + (source + source)) + #:system ,system + #:phases ,phases + #:outputs %outputs + #:search-paths ',(map search-path-specification->sexp + search-paths) + #:import-path ,import-path + #:unpack-path ,unpack-path + #:tests? ,tests? + #:inputs %build-inputs))) + + (define guile-for-build + (match guile + ((? package?) + (package-derivation store guile system #:graft? #f)) + (#f ; the default + (let* ((distro (resolve-interface '(gnu packages commencement))) + (guile (module-ref distro 'guile-final))) + (package-derivation store guile system + #:graft? #f))))) + + (build-expression->derivation store name builder + #:inputs inputs + #:system system + #:modules imported-modules + #:outputs outputs + #:guile-for-build guile-for-build)) + +(define go-build-system + (build-system + (name 'go) + (description + "Build system for Go programs") + (lower lower))) diff --git a/guix/build/go-build-system.scm b/guix/build/go-build-system.scm new file mode 100644 index 0000000000..7f04e3db8c --- /dev/null +++ b/guix/build/go-build-system.scm @@ -0,0 +1,217 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016 Petter +;;; Copyright © 2017 Leo Famulari +;;; +;;; 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 . + +(define-module (guix build go-build-system) + #:use-module ((guix build gnu-build-system) #:prefix gnu:) + #:use-module (guix build utils) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:export (%standard-phases + go-build)) + +;; Commentary: +;; +;; Build procedures for Go packages. This is the builder-side code. +;; +;; Software written in Go is either a 'package' (i.e. library) or 'command' +;; (i.e. executable). Both types can be built with either the `go build` or `go +;; install` commands. However, `go build` discards the result of the build +;; process for Go libraries, so we use `go install`, which preserves the +;; results. [0] + +;; Go software is developed and built within a particular filesystem hierarchy +;; structure called a 'workspace' [1]. This workspace is found by Go +;; via the GOPATH environment variable. Typically, all Go source code +;; and compiled objects are kept in a single workspace, but it is +;; possible for GOPATH to contain a list of directories, and that is +;; what we do in this go-build-system. [2] +;; +;; Go software, whether a package or a command, is uniquely named using +;; an 'import path'. The import path is based on the URL of the +;; software's source. Since most source code is provided over the +;; internet, the import path is typically a combination of the remote +;; URL and the source repository's filesystem structure. For example, +;; the Go port of the common `du` command is hosted on github.com, at +;; . Thus, the import path is +;; . [3] +;; +;; It may be possible to programatically guess a package's import path +;; based on the source URL, but we don't try that in this revision of +;; the go-build-system. +;; +;; Modules of modular Go libraries are named uniquely with their +;; filesystem paths. For example, the supplemental but "standardized" +;; libraries developed by the Go upstream developers are available at +;; . The Go IPv4 +;; library's import path is . The source of +;; such modular libraries must be unpacked at the top-level of the +;; filesystem structure of the library. So the IPv4 library should be +;; unpacked to . This is handled in the +;; go-build-system with the optional #:unpack-path key. +;; +;; In general, Go software is built using a standardized build mechanism +;; that does not require any build scripts like Makefiles. This means +;; that all modules of modular libraries cannot be built with a single +;; command. Each module must be built individually. This complicates +;; certain cases, and these issues are currently resolved by creating a +;; filesystem union of the required modules of such libraries. I think +;; this could be improved in future revisions of the go-build-system. +;; +;; [0] `go build`: +;; https://golang.org/cmd/go/#hdr-Compile_packages_and_dependencies +;; `go install`: +;; https://golang.org/cmd/go/#hdr-Compile_and_install_packages_and_dependencies +;; [1] Go workspace example, from : +;; bin/ +;; hello # command executable +;; outyet # command executable +;; pkg/ +;; linux_amd64/ +;; github.com/golang/example/ +;; stringutil.a # package object +;; src/ +;; github.com/golang/example/ +;; .git/ # Git repository metadata +;; hello/ +;; hello.go # command source +;; outyet/ +;; main.go # command source +;; main_test.go # test source +;; stringutil/ +;; reverse.go # package source +;; reverse_test.go # test source +;; golang.org/x/image/ +;; .git/ # Git repository metadata +;; bmp/ +;; reader.go # package source +;; writer.go # package source +;; ... (many more repositories and packages omitted) ... +;; +;; [2] https://golang.org/doc/code.html#GOPATH +;; [3] https://golang.org/doc/code.html#ImportPaths +;; +;; Code: + +(define* (unpack #:key source import-path unpack-path #:allow-other-keys) + "Unpack SOURCE in the UNPACK-PATH, or the IMPORT-PATH is the UNPACK-PATH is +unset. When SOURCE is a directory, copy it instead of unpacking." + (if (string-null? import-path) + ((display "WARNING: The Go import path is unset.\n"))) + (if (string-null? unpack-path) + (set! unpack-path import-path)) + (mkdir "src") + (let ((dest (string-append "src/" unpack-path))) + (mkdir-p dest) + (if (file-is-directory? source) + (begin + (copy-recursively source dest #:keep-mtime? #t) + #t) + (if (string-suffix? ".zip" source) + (zero? (system* "unzip" "-d" dest source)) + (zero? (system* "tar" "-C" dest "-xvf" source)))))) + +(define* (install-source #:key outputs #:allow-other-keys) + "Install the source code to the output directory." + (let* ((out (assoc-ref outputs "out")) + (source "src") + (dest (string-append out "/" source))) + (copy-recursively source dest #:keep-mtime? #t) + #t)) + +(define (go-package? name) + (string-prefix? "go-" name)) + +(define (go-inputs inputs) + "Return the alist of INPUTS that are Go software." + ;; XXX This should not check the file name of the store item. Instead we + ;; should pass, from the host side, the list of inputs that are packages using + ;; the go-build-system. + (alist-delete "go" ; Exclude the Go compiler + (alist-delete "source" ; Exclude the source code of the package being built + (filter (match-lambda + ((label . directory) + (go-package? ((compose package-name->name+version + strip-store-file-name) + directory))) + (_ #f)) + inputs)))) + +(define* (setup-environment #:key inputs outputs #:allow-other-keys) + "Export the variables GOPATH and GOBIN, which are based on INPUTS and OUTPUTS, +respectively." + (let ((out (assoc-ref outputs "out"))) + ;; GOPATH is where Go looks for the source code of the build's dependencies. + (set-path-environment-variable "GOPATH" + ;; XXX Matching "." hints that we could do + ;; something simpler here... + (list ".") + (match (go-inputs inputs) + (((_ . dir) ...) + dir))) + + ;; Add the source code of the package being built to GOPATH. + (if (getenv "GOPATH") + (setenv "GOPATH" (string-append (getcwd) ":" (getenv "GOPATH"))) + (setenv "GOPATH" (getcwd))) + ;; Where to install compiled executable files ('commands' in Go parlance'). + (setenv "GOBIN" out) + #t)) + +(define* (build #:key import-path #:allow-other-keys) + "Build the package named by IMPORT-PATH." + (or + (zero? (system* "go" "install" + "-v" ; print the name of packages as they are compiled + "-x" ; print each command as it is invoked + import-path)) + (begin + (display (string-append "Building '" import-path "' failed.\n" + "Here are the results of `go env`:\n")) + (system* "go" "env") + #f))) + +(define* (check #:key tests? import-path #:allow-other-keys) + "Run the tests for the package named by IMPORT-PATH." + (if tests? + (zero? (system* "go" "test" import-path)))) + +(define* (install #:key outputs #:allow-other-keys) + "Install the compiled libraries. `go install` installs these files to +$GOPATH/pkg, so we have to copy them into the output direcotry manually. +Compiled executable files should have already been installed to the store based +on $GOBIN in the build phase." + (when (file-exists? "pkg") + (copy-recursively "pkg" (string-append (assoc-ref outputs "out") "/pkg"))) + #t) + +(define %standard-phases + (modify-phases gnu:%standard-phases + (delete 'configure) + (delete 'patch-generated-file-shebangs) + (replace 'unpack unpack) + (add-after 'unpack 'install-source install-source) + (add-before 'build 'setup-environment setup-environment) + (replace 'build build) + (replace 'check check) + (replace 'install install))) + +(define* (go-build #:key inputs (phases %standard-phases) + #:allow-other-keys #:rest args) + "Build the given Go package, applying all of PHASES in order." + (apply gnu:gnu-build #:inputs inputs #:phases phases args))