gnu: jsoncpp: Update to 1.9.0.
[guix.git] / guix / build / go-build-system.scm
blob858068ba98be64e38cf045925d823a7234bfff07
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016 Petter <petter@mykolab.ch>
3 ;;; Copyright © 2017, 2019 Leo Famulari <leo@famulari.name>
4 ;;; Copyright © 2019 Maxim Cournoyer <maxim.cournoyer@gmail.com>
5 ;;;
6 ;;; This file is part of GNU Guix.
7 ;;;
8 ;;; GNU Guix is free software; you can redistribute it and/or modify it
9 ;;; under the terms of the GNU General Public License as published by
10 ;;; the Free Software Foundation; either version 3 of the License, or (at
11 ;;; your option) any later version.
12 ;;;
13 ;;; GNU Guix is distributed in the hope that it will be useful, but
14 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;;; GNU General Public License for more details.
17 ;;;
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
21 (define-module (guix build go-build-system)
22   #:use-module ((guix build gnu-build-system) #:prefix gnu:)
23   #:use-module (guix build union)
24   #:use-module (guix build utils)
25   #:use-module (ice-9 match)
26   #:use-module (ice-9 ftw)
27   #:use-module (srfi srfi-1)
28   #:use-module (rnrs io ports)
29   #:use-module (rnrs bytevectors)
30   #:export (%standard-phases
31             go-build))
33 ;; Commentary:
35 ;; Build procedures for Go packages.  This is the builder-side code.
37 ;; Software written in Go is either a 'package' (i.e. library) or 'command'
38 ;; (i.e. executable).  Both types can be built with either the `go build` or `go
39 ;; install` commands.  However, `go build` discards the result of the build
40 ;; process for Go libraries, so we use `go install`, which preserves the
41 ;; results. [0]
43 ;; Go software is developed and built within a particular file system hierarchy
44 ;; structure called a 'workspace' [1].  This workspace can be found by Go via
45 ;; the GOPATH environment variable.  Typically, all Go source code and compiled
46 ;; objects are kept in a single workspace, but GOPATH may be a list of
47 ;; directories [2].  In this go-build-system we create a file system union of
48 ;; the Go-language dependencies. Previously, we made GOPATH a list of store
49 ;; directories, but stopped because Go programs started keeping references to
50 ;; these directories in Go 1.11:
51 ;; <https://bugs.gnu.org/33620>.
53 ;; Go software, whether a package or a command, is uniquely named using an
54 ;; 'import path'.  The import path is based on the URL of the software's source.
55 ;; Because most source code is provided over the internet, the import path is
56 ;; typically a combination of the remote URL and the source repository's file
57 ;; system structure. For example, the Go port of the common `du` command is
58 ;; hosted on github.com, at <https://github.com/calmh/du>.  Thus, the import
59 ;; path is <github.com/calmh/du>. [3]
61 ;; It may be possible to automatically guess a package's import path based on
62 ;; the source URL, but we don't try that in this revision of the
63 ;; go-build-system.
65 ;; Modules of modular Go libraries are named uniquely with their
66 ;; file system paths.  For example, the supplemental but "standardized"
67 ;; libraries developed by the Go upstream developers are available at
68 ;; <https://golang.org/x/{net,text,crypto, et cetera}>.  The Go IPv4
69 ;; library's import path is <golang.org/x/net/ipv4>.  The source of
70 ;; such modular libraries must be unpacked at the top-level of the
71 ;; file system structure of the library.  So the IPv4 library should be
72 ;; unpacked to <golang.org/x/net>.  This is handled in the
73 ;; go-build-system with the optional #:unpack-path key.
75 ;; In general, Go software is built using a standardized build mechanism
76 ;; that does not require any build scripts like Makefiles.  This means
77 ;; that all modules of modular libraries cannot be built with a single
78 ;; command.  Each module must be built individually.  This complicates
79 ;; certain cases, and these issues are currently resolved by creating a
80 ;; file system union of the required modules of such libraries.  I think
81 ;; this could be improved in future revisions of the go-build-system.
83 ;; TODO:
84 ;; * Avoid copying dependencies into the build environment and / or avoid using
85 ;; a tmpdir when creating the inputs union.
86 ;; * Use Go modules [4]
87 ;; * Re-use compiled packages [5]
88 ;; * Avoid the go-inputs hack
89 ;; * Stop needing remove-go-references (-trimpath ? )
90 ;; * Remove module packages, only offering the full Git repos? This is
91 ;; more idiomatic, I think, because Go downloads Git repos, not modules.
92 ;; What are the trade-offs?
94 ;; [0] `go build`:
95 ;; https://golang.org/cmd/go/#hdr-Compile_packages_and_dependencies
96 ;; `go install`:
97 ;; https://golang.org/cmd/go/#hdr-Compile_and_install_packages_and_dependencies
98 ;; [1] Go workspace example, from <https://golang.org/doc/code.html#Workspaces>:
99 ;; bin/
100 ;;     hello                          # command executable
101 ;;     outyet                         # command executable
102 ;; pkg/
103 ;;     linux_amd64/
104 ;;         github.com/golang/example/
105 ;;             stringutil.a           # package object
106 ;; src/
107 ;;     github.com/golang/example/
108 ;;         .git/                      # Git repository metadata
109 ;;         hello/
110 ;;             hello.go               # command source
111 ;;         outyet/
112 ;;              main.go               # command source
113 ;;              main_test.go          # test source
114 ;;         stringutil/
115 ;;             reverse.go             # package source
116 ;;             reverse_test.go        # test source
117 ;;         golang.org/x/image/
118 ;;             .git/                  # Git repository metadata
119 ;;             bmp/
120 ;;                 reader.go          # package source
121 ;;                 writer.go          # package source
122 ;;     ... (many more repositories and packages omitted) ...
124 ;; [2] https://golang.org/doc/code.html#GOPATH
125 ;; [3] https://golang.org/doc/code.html#ImportPaths
126 ;; [4] https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more
127 ;; [5] https://bugs.gnu.org/32919
129 ;; Code:
131 (define* (setup-go-environment #:key inputs outputs #:allow-other-keys)
132   "Prepare a Go build environment for INPUTS and OUTPUTS.  Build a file system
133 union of INPUTS.  Export GOPATH, which helps the compiler find the source code
134 of the package being built and its dependencies, and GOBIN, which determines
135 where executables (\"commands\") are installed to.  This phase is sometimes used
136 by packages that use (guix build-system gnu) but have a handful of Go
137 dependencies, so it should be self-contained."
138   ;; Using the current working directory as GOPATH makes it easier for packagers
139   ;; who need to manipulate the unpacked source code.
140   (setenv "GOPATH" (getcwd))
141   (setenv "GOBIN" (string-append (assoc-ref outputs "out") "/bin"))
142   (let ((tmpdir (tmpnam)))
143     (match (go-inputs inputs)
144       (((names . directories) ...)
145        (union-build tmpdir (filter directory-exists? directories)
146                     #:create-all-directories? #t
147                     #:log-port (%make-void-port "w"))))
148     ;; XXX A little dance because (guix build union) doesn't use mkdir-p.
149     (copy-recursively tmpdir
150                       (string-append (getenv "GOPATH"))
151                       #:keep-mtime? #t)
152     (delete-file-recursively tmpdir))
153   #t)
155 (define* (unpack #:key source import-path unpack-path #:allow-other-keys)
156   "Relative to $GOPATH, unpack SOURCE in UNPACK-PATH, or IMPORT-PATH when
157 UNPACK-PATH is unset.  If the SOURCE archive has a single top level directory,
158 it is stripped so that the sources appear directly under UNPACK-PATH.  When
159 SOURCE is a directory, copy its content into UNPACK-PATH instead of
160 unpacking."
161   (define (unpack-maybe-strip source dest)
162     (let* ((scratch-dir (string-append (or (getenv "TMPDIR") "/tmp")
163                                        "/scratch-dir"))
164            (out (mkdir-p scratch-dir)))
165       (with-directory-excursion scratch-dir
166         (if (string-suffix? ".zip" source)
167             (invoke "unzip" source)
168             (invoke "tar" "-xvf" source))
169         (let ((top-level-files (remove (lambda (x)
170                                          (member x '("." "..")))
171                                        (scandir "."))))
172           (match top-level-files
173             ((top-level-file)
174              (when (file-is-directory? top-level-file)
175                (copy-recursively top-level-file dest #:keep-mtime? #t)))
176             (_
177              (copy-recursively "." dest #:keep-mtime? #t)))))
178       (delete-file-recursively scratch-dir)))
180   (when (string-null? import-path)
181     (display "WARNING: The Go import path is unset.\n"))
182   (when (string-null? unpack-path)
183     (set! unpack-path import-path))
184   (let ((dest (string-append (getenv "GOPATH") "/src/" unpack-path)))
185     (mkdir-p dest)
186     (if (file-is-directory? source)
187         (copy-recursively source dest #:keep-mtime? #t)
188         (unpack-maybe-strip source dest)))
189   #t)
191 (define (go-package? name)
192   (string-prefix? "go-" name))
194 (define (go-inputs inputs)
195   "Return the alist of INPUTS that are Go software."
196   ;; XXX This should not check the file name of the store item. Instead we
197   ;; should pass, from the host side, the list of inputs that are packages using
198   ;; the go-build-system.
199   (alist-delete "go" ; Exclude the Go compiler
200     (alist-delete "source" ; Exclude the source code of the package being built
201       (filter (match-lambda
202                 ((label . directory)
203                  (go-package? ((compose package-name->name+version
204                                         strip-store-file-name)
205                                directory)))
206                 (_ #f))
207               inputs))))
209 (define* (build #:key import-path #:allow-other-keys)
210   "Build the package named by IMPORT-PATH."
211   (with-throw-handler
212     #t
213     (lambda _
214       (invoke "go" "install"
215               "-v" ; print the name of packages as they are compiled
216               "-x" ; print each command as it is invoked
217               ;; Respectively, strip the symbol table and debug
218               ;; information, and the DWARF symbol table.
219               "-ldflags=-s -w"
220               import-path))
221     (lambda (key . args)
222       (display (string-append "Building '" import-path "' failed.\n"
223                               "Here are the results of `go env`:\n"))
224       (invoke "go" "env"))))
226 ;; Can this also install commands???
227 (define* (check #:key tests? import-path #:allow-other-keys)
228   "Run the tests for the package named by IMPORT-PATH."
229   (when tests?
230     (invoke "go" "test" import-path))
231   #t)
233 (define* (install #:key install-source? outputs import-path unpack-path #:allow-other-keys)
234   "Install the source code of IMPORT-PATH to the primary output directory.
235 Compiled executable files (Go \"commands\") should have already been installed
236 to the store based on $GOBIN in the build phase.
237 XXX We can't make us of compiled libraries (Go \"packages\")."
238   (when install-source?
239     (if (string-null? import-path)
240         ((display "WARNING: The Go import path is unset.\n")))
241     (let* ((out (assoc-ref outputs "out"))
242            (source (string-append (getenv "GOPATH") "/src/" import-path))
243            (dest (string-append out "/src/" import-path)))
244       (mkdir-p dest)
245       (copy-recursively source dest #:keep-mtime? #t)))
246   #t)
248 (define* (remove-store-reference file file-name
249                                   #:optional (store (%store-directory)))
250   "Remove from FILE occurrences of FILE-NAME in STORE; return #t when FILE-NAME
251 is encountered in FILE, #f otherwise. This implementation reads FILE one byte at
252 a time, which is slow. Instead, we should use the Boyer-Moore string search
253 algorithm; there is an example in (guix build grafts)."
254   (define pattern
255     (string-take file-name
256                  (+ 34 (string-length (%store-directory)))))
258   (with-fluids ((%default-port-encoding #f))
259     (with-atomic-file-replacement file
260       (lambda (in out)
261         ;; We cannot use `regexp-exec' here because it cannot deal with
262         ;; strings containing NUL characters.
263         (format #t "removing references to `~a' from `~a'...~%" file-name file)
264         (setvbuf in 'block 65536)
265         (setvbuf out 'block 65536)
266         (fold-port-matches (lambda (match result)
267                              (put-bytevector out (string->utf8 store))
268                              (put-u8 out (char->integer #\/))
269                              (put-bytevector out
270                                              (string->utf8
271                                               "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
272                              #t)
273                            #f
274                            pattern
275                            in
276                            (lambda (char result)
277                              (put-u8 out (char->integer char))
278                              result))))))
280 (define* (remove-go-references #:key allow-go-reference?
281                                inputs outputs #:allow-other-keys)
282   "Remove any references to the Go compiler from the compiled Go executable
283 files in OUTPUTS."
284 ;; We remove this spurious reference to save bandwidth when installing Go
285 ;; executables. It would be better to not embed the reference in the first
286 ;; place, but I'm not sure how to do that. The subject was discussed at:
287 ;; <https://lists.gnu.org/archive/html/guix-devel/2017-10/msg00207.html>
288   (if allow-go-reference?
289     #t
290     (let ((go (assoc-ref inputs "go"))
291           (bin "/bin"))
292       (for-each (lambda (output)
293                   (when (file-exists? (string-append (cdr output)
294                                                      bin))
295                     (for-each (lambda (file)
296                                 (remove-store-reference file go))
297                               (find-files (string-append (cdr output) bin)))))
298                 outputs)
299       #t)))
301 (define %standard-phases
302   (modify-phases gnu:%standard-phases
303     (delete 'bootstrap)
304     (delete 'configure)
305     (delete 'patch-generated-file-shebangs)
306     (add-before 'unpack 'setup-go-environment setup-go-environment)
307     (replace 'unpack unpack)
308     (replace 'build build)
309     (replace 'check check)
310     (replace 'install install)
311     (add-after 'install 'remove-go-references remove-go-references)))
313 (define* (go-build #:key inputs (phases %standard-phases)
314                       #:allow-other-keys #:rest args)
315   "Build the given Go package, applying all of PHASES in order."
316   (apply gnu:gnu-build #:inputs inputs #:phases phases args))