go-build-system.scm 17 KB

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