ci.scm 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2017, 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
  4. ;;; Copyright © 2018, 2019 Clément Lassieur <clement@lassieur.org>
  5. ;;; Copyright © 2020 Julien Lepiller <julien@lepiller.eu>
  6. ;;; Copyright © 2020 Mathieu Othacehe <othacehe@gnu.org>
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (gnu ci)
  23. #:use-module (guix config)
  24. #:use-module (guix store)
  25. #:use-module (guix grafts)
  26. #:use-module (guix profiles)
  27. #:use-module (guix packages)
  28. #:use-module (guix channels)
  29. #:use-module (guix config)
  30. #:use-module (guix derivations)
  31. #:use-module (guix build-system)
  32. #:use-module (guix monads)
  33. #:use-module (guix gexp)
  34. #:use-module (guix ui)
  35. #:use-module ((guix licenses)
  36. #:select (gpl3+ license? license-name))
  37. #:use-module ((guix utils) #:select (%current-system))
  38. #:use-module ((guix scripts system) #:select (read-operating-system))
  39. #:use-module ((guix scripts pack)
  40. #:select (lookup-compressor self-contained-tarball))
  41. #:use-module (gnu bootloader)
  42. #:use-module (gnu bootloader u-boot)
  43. #:use-module (gnu image)
  44. #:use-module (gnu packages)
  45. #:use-module (gnu packages gcc)
  46. #:use-module (gnu packages base)
  47. #:use-module (gnu packages gawk)
  48. #:use-module (gnu packages guile)
  49. #:use-module (gnu packages gettext)
  50. #:use-module (gnu packages compression)
  51. #:use-module (gnu packages multiprecision)
  52. #:use-module (gnu packages make-bootstrap)
  53. #:use-module (gnu packages package-management)
  54. #:use-module (gnu system)
  55. #:use-module (gnu system image)
  56. #:use-module (gnu system vm)
  57. #:use-module (gnu system install)
  58. #:use-module (gnu system images hurd)
  59. #:use-module (gnu system images novena)
  60. #:use-module (gnu system images pine64)
  61. #:use-module (gnu system images pinebook-pro)
  62. #:use-module (gnu tests)
  63. #:use-module (srfi srfi-1)
  64. #:use-module (srfi srfi-26)
  65. #:use-module (ice-9 match)
  66. #:export (%cross-targets
  67. channel-source->package
  68. hydra-jobs))
  69. ;;; Commentary:
  70. ;;;
  71. ;;; This file defines build jobs for the Hydra and Cuirass continuation
  72. ;;; integration tools.
  73. ;;;
  74. ;;; Code:
  75. (define* (package->alist store package system
  76. #:optional (package-derivation package-derivation))
  77. "Convert PACKAGE to an alist suitable for Hydra."
  78. (parameterize ((%graft? #f))
  79. (let ((drv (package-derivation store package system
  80. #:graft? #f)))
  81. `((derivation . ,(derivation-file-name drv))
  82. (log . ,(log-file store (derivation-file-name drv)))
  83. (outputs . ,(filter-map (lambda (res)
  84. (match res
  85. ((name . path)
  86. `(,name . ,path))))
  87. (derivation->output-paths drv)))
  88. (nix-name . ,(derivation-name drv))
  89. (system . ,(derivation-system drv))
  90. (description . ,(package-synopsis package))
  91. (long-description . ,(package-description package))
  92. ;; XXX: Hydra ignores licenses that are not a <license> structure or a
  93. ;; list thereof.
  94. (license . ,(let loop ((license (package-license package)))
  95. (match license
  96. ((? license?)
  97. (license-name license))
  98. ((lst ...)
  99. (map loop license)))))
  100. (home-page . ,(package-home-page package))
  101. (maintainers . ("bug-guix@gnu.org"))
  102. (max-silent-time . ,(or (assoc-ref (package-properties package)
  103. 'max-silent-time)
  104. 3600)) ;1 hour by default
  105. (timeout . ,(or (assoc-ref (package-properties package) 'timeout)
  106. 72000)))))) ;20 hours by default
  107. (define (package-job store job-name package system)
  108. "Return a job called JOB-NAME that builds PACKAGE on SYSTEM."
  109. (let ((job-name (symbol-append job-name (string->symbol ".")
  110. (string->symbol system))))
  111. `(,job-name . ,(cut package->alist store package system))))
  112. (define (package-cross-job store job-name package target system)
  113. "Return a job called TARGET.JOB-NAME that cross-builds PACKAGE for TARGET on
  114. SYSTEM."
  115. `(,(symbol-append (string->symbol target) (string->symbol ".") job-name
  116. (string->symbol ".") (string->symbol system)) .
  117. ,(cute package->alist store package system
  118. (lambda* (store package system #:key graft?)
  119. (package-cross-derivation store package target system
  120. #:graft? graft?)))))
  121. (define %core-packages
  122. ;; Note: Don't put the '-final' package variants because (1) that's
  123. ;; implicit, and (2) they cannot be cross-built (due to the explicit input
  124. ;; chain.)
  125. (list gcc-7 gcc-8 gcc-9 gcc-10 glibc binutils
  126. gmp mpfr mpc coreutils findutils diffutils patch sed grep
  127. gawk gnu-gettext hello guile-2.0 guile-2.2 zlib gzip xz
  128. %bootstrap-binaries-tarball
  129. %binutils-bootstrap-tarball
  130. (%glibc-bootstrap-tarball)
  131. %gcc-bootstrap-tarball
  132. %guile-bootstrap-tarball
  133. %bootstrap-tarballs))
  134. (define (packages-to-cross-build target)
  135. "Return the list of packages to cross-build for TARGET."
  136. ;; Don't cross-build the bootstrap tarballs for MinGW.
  137. (if (string-contains target "mingw")
  138. (drop-right %core-packages 6)
  139. %core-packages))
  140. (define %cross-targets
  141. '("mips64el-linux-gnu"
  142. "arm-linux-gnueabihf"
  143. "aarch64-linux-gnu"
  144. "powerpc-linux-gnu"
  145. "riscv64-linux-gnu"
  146. "i586-pc-gnu" ;aka. GNU/Hurd
  147. "i686-w64-mingw32"
  148. "x86_64-w64-mingw32"))
  149. (define (cross-jobs store system)
  150. "Return a list of cross-compilation jobs for SYSTEM."
  151. (define (from-32-to-64? target)
  152. ;; Return true if SYSTEM is 32-bit and TARGET is 64-bit. This hack
  153. ;; prevents known-to-fail cross-builds from i686-linux or armhf-linux to
  154. ;; mips64el-linux-gnuabi64.
  155. (and (or (string-prefix? "i686-" system)
  156. (string-prefix? "i586-" system)
  157. (string-prefix? "armhf-" system))
  158. (string-contains target "64"))) ;x86_64, mips64el, aarch64, etc.
  159. (define (same? target)
  160. ;; Return true if SYSTEM and TARGET are the same thing. This is so we
  161. ;; don't try to cross-compile to 'mips64el-linux-gnu' from
  162. ;; 'mips64el-linux'.
  163. (or (string-contains target system)
  164. (and (string-prefix? "armhf" system) ;armhf-linux
  165. (string-prefix? "arm" target)))) ;arm-linux-gnueabihf
  166. (define (pointless? target)
  167. ;; Return #t if it makes no sense to cross-build to TARGET from SYSTEM.
  168. (match system
  169. ((or "x86_64-linux" "i686-linux")
  170. (if (string-contains target "mingw")
  171. (not (string=? "x86_64-linux" system))
  172. #f))
  173. (_
  174. ;; Don't try to cross-compile from non-Intel platforms: this isn't
  175. ;; very useful and these are often brittle configurations.
  176. #t)))
  177. (define (either proc1 proc2 proc3)
  178. (lambda (x)
  179. (or (proc1 x) (proc2 x) (proc3 x))))
  180. (append-map (lambda (target)
  181. (map (lambda (package)
  182. (package-cross-job store (job-name package)
  183. package target system))
  184. (packages-to-cross-build target)))
  185. (remove (either from-32-to-64? same? pointless?)
  186. %cross-targets)))
  187. ;; Architectures that are able to build or cross-build Guix System images.
  188. ;; This does not mean that other architectures are not supported, only that
  189. ;; they are often not fast enough to support Guix System images building.
  190. (define %guix-system-supported-systems
  191. '("x86_64-linux" "i686-linux"))
  192. (define %guix-system-images
  193. (list hurd-barebones-qcow2-image
  194. pine64-barebones-raw-image
  195. pinebook-pro-barebones-raw-image
  196. novena-barebones-raw-image))
  197. (define (hours hours)
  198. (* 3600 hours))
  199. (define (image-jobs store system)
  200. "Return a list of jobs that build images for SYSTEM. Those jobs are
  201. expensive in storage and I/O operations, hence their periodicity is limited by
  202. passing the PERIOD argument."
  203. (define (->alist drv)
  204. `((derivation . ,(derivation-file-name drv))
  205. (log . ,(log-file store (derivation-file-name drv)))
  206. (outputs . ,(filter-map (lambda (res)
  207. (match res
  208. ((name . path)
  209. `(,name . ,path))))
  210. (derivation->output-paths drv)))
  211. (nix-name . ,(derivation-name drv))
  212. (system . ,(derivation-system drv))
  213. (description . "Stand-alone image of the GNU system")
  214. (long-description . "This is a demo stand-alone image of the GNU
  215. system.")
  216. (license . ,(license-name gpl3+))
  217. (period . ,(hours 48))
  218. (max-silent-time . 3600)
  219. (timeout . 3600)
  220. (home-page . ,%guix-home-page-url)
  221. (maintainers . ("bug-guix@gnu.org"))))
  222. (define (->job name drv)
  223. (let ((name (symbol-append name (string->symbol ".")
  224. (string->symbol system))))
  225. `(,name . ,(lambda ()
  226. (parameterize ((%graft? #f))
  227. (->alist drv))))))
  228. (define (build-image image)
  229. (run-with-store store
  230. (mbegin %store-monad
  231. (set-guile-for-build (default-guile))
  232. (lower-object (system-image image)))))
  233. (define MiB
  234. (expt 2 20))
  235. (if (member system %guix-system-supported-systems)
  236. `(,(->job 'usb-image
  237. (build-image
  238. (image
  239. (inherit efi-disk-image)
  240. (operating-system installation-os))))
  241. ,(->job 'iso9660-image
  242. (build-image
  243. (image
  244. (inherit (image-with-label
  245. iso9660-image
  246. (string-append "GUIX_" system "_"
  247. (if (> (string-length %guix-version) 7)
  248. (substring %guix-version 0 7)
  249. %guix-version))))
  250. (operating-system installation-os))))
  251. ;; Only cross-compile Guix System images from x86_64-linux for now.
  252. ,@(if (string=? system "x86_64-linux")
  253. (map (lambda (image)
  254. (->job (image-name image) (build-image image)))
  255. %guix-system-images)
  256. '()))
  257. '()))
  258. (define channel-build-system
  259. ;; Build system used to "convert" a channel instance to a package.
  260. (let* ((build (lambda* (store name inputs
  261. #:key source commit system
  262. #:allow-other-keys)
  263. (run-with-store store
  264. ;; SOURCE can be a lowerable object such as <local-file>
  265. ;; or a file name. Adjust accordingly.
  266. (mlet* %store-monad ((source (if (string? source)
  267. (return source)
  268. (lower-object source)))
  269. (instance
  270. -> (checkout->channel-instance
  271. source #:commit commit)))
  272. (channel-instances->derivation (list instance)))
  273. #:system system)))
  274. (lower (lambda* (name #:key system source commit
  275. #:allow-other-keys)
  276. (bag
  277. (name name)
  278. (system system)
  279. (build build)
  280. (arguments `(#:source ,source
  281. #:commit ,commit))))))
  282. (build-system (name 'channel)
  283. (description "Turn a channel instance into a package.")
  284. (lower lower))))
  285. (define* (channel-source->package source #:key commit)
  286. "Return a package for the given channel SOURCE, a lowerable object."
  287. (package
  288. (inherit guix)
  289. (version (string-append (package-version guix) "+"))
  290. (build-system channel-build-system)
  291. (arguments `(#:source ,source
  292. #:commit ,commit))
  293. (inputs '())
  294. (native-inputs '())
  295. (propagated-inputs '())))
  296. (define* (system-test-jobs store system
  297. #:key source commit)
  298. "Return a list of jobs for the system tests."
  299. (define (test->thunk test)
  300. (lambda ()
  301. (define drv
  302. (run-with-store store
  303. (mbegin %store-monad
  304. (set-current-system system)
  305. (set-grafting #f)
  306. (set-guile-for-build (default-guile))
  307. (system-test-value test))))
  308. ;; Those tests are extremely expensive in I/O operations and storage
  309. ;; size, use the "period" attribute to run them with a period of at
  310. ;; least 48 hours.
  311. `((derivation . ,(derivation-file-name drv))
  312. (log . ,(log-file store (derivation-file-name drv)))
  313. (outputs . ,(filter-map (lambda (res)
  314. (match res
  315. ((name . path)
  316. `(,name . ,path))))
  317. (derivation->output-paths drv)))
  318. (nix-name . ,(derivation-name drv))
  319. (system . ,(derivation-system drv))
  320. (description . ,(format #f "Guix '~a' system test"
  321. (system-test-name test)))
  322. (long-description . ,(system-test-description test))
  323. (license . ,(license-name gpl3+))
  324. (period . ,(hours 48))
  325. (max-silent-time . 3600)
  326. (timeout . 3600)
  327. (home-page . ,%guix-home-page-url)
  328. (maintainers . ("bug-guix@gnu.org")))))
  329. (define (->job test)
  330. (let ((name (string->symbol
  331. (string-append "test." (system-test-name test)
  332. "." system))))
  333. (cons name (test->thunk test))))
  334. (if (member system %guix-system-supported-systems)
  335. ;; Override the value of 'current-guix' used by system tests. Using a
  336. ;; channel instance makes tests that rely on 'current-guix' less
  337. ;; expensive. It also makes sure we get a valid Guix package when this
  338. ;; code is not running from a checkout.
  339. (parameterize ((current-guix-package
  340. (channel-source->package source #:commit commit)))
  341. (map ->job (all-system-tests)))
  342. '()))
  343. (define (tarball-jobs store system)
  344. "Return Hydra jobs to build the self-contained Guix binary tarball."
  345. (define (->alist drv)
  346. `((derivation . ,(derivation-file-name drv))
  347. (log . ,(log-file store (derivation-file-name drv)))
  348. (outputs . ,(filter-map (lambda (res)
  349. (match res
  350. ((name . path)
  351. `(,name . ,path))))
  352. (derivation->output-paths drv)))
  353. (nix-name . ,(derivation-name drv))
  354. (system . ,(derivation-system drv))
  355. (description . "Stand-alone binary Guix tarball")
  356. (long-description . "This is a tarball containing binaries of Guix and
  357. all its dependencies, and ready to be installed on \"foreign\" distributions.")
  358. (license . ,(license-name gpl3+))
  359. (home-page . ,%guix-home-page-url)
  360. (maintainers . ("bug-guix@gnu.org"))
  361. (period . ,(hours 24))))
  362. (define (->job name drv)
  363. (let ((name (symbol-append name (string->symbol ".")
  364. (string->symbol system))))
  365. `(,name . ,(lambda ()
  366. (parameterize ((%graft? #f))
  367. (->alist drv))))))
  368. ;; XXX: Add a job for the stable Guix?
  369. (list (->job 'binary-tarball
  370. (run-with-store store
  371. (mbegin %store-monad
  372. (set-guile-for-build (default-guile))
  373. (>>= (profile-derivation (packages->manifest (list guix)))
  374. (lambda (profile)
  375. (self-contained-tarball "guix-binary" profile
  376. #:localstatedir? #t
  377. #:compressor
  378. (lookup-compressor "xz")))))
  379. #:system system))))
  380. (define job-name
  381. ;; Return the name of a package's job.
  382. (compose string->symbol
  383. (cut package-full-name <> "-")))
  384. (define package->job
  385. (let ((base-packages
  386. (delete-duplicates
  387. (append-map (match-lambda
  388. ((_ package _ ...)
  389. (match (package-transitive-inputs package)
  390. (((_ inputs _ ...) ...)
  391. inputs))))
  392. (%final-inputs)))))
  393. (lambda (store package system)
  394. "Return a job for PACKAGE on SYSTEM, or #f if this combination is not
  395. valid."
  396. (cond ((member package base-packages)
  397. (package-job store (symbol-append 'base. (job-name package))
  398. package system))
  399. ((supported-package? package system)
  400. (let ((drv (package-derivation store package system
  401. #:graft? #f)))
  402. (and (substitutable-derivation? drv)
  403. (package-job store (job-name package)
  404. package system))))
  405. (else
  406. #f)))))
  407. (define (all-packages)
  408. "Return the list of packages to build."
  409. (define (adjust package result)
  410. (cond ((package-replacement package)
  411. ;; XXX: If PACKAGE and its replacement have the same name/version,
  412. ;; then both Cuirass jobs will have the same name, which
  413. ;; effectively means that the second one will be ignored. Thus,
  414. ;; return the replacement first.
  415. (cons* (package-replacement package) ;build both
  416. package
  417. result))
  418. ((package-superseded package)
  419. result) ;don't build it
  420. (else
  421. (cons package result))))
  422. (fold-packages adjust
  423. (fold adjust '() ;include base packages
  424. (match (%final-inputs)
  425. (((labels packages _ ...) ...)
  426. packages)))
  427. #:select? (const #t))) ;include hidden packages
  428. (define (arguments->manifests arguments)
  429. "Return the list of manifests extracted from ARGUMENTS."
  430. (map (match-lambda
  431. ((input-name . relative-path)
  432. (let* ((checkout (assq-ref arguments (string->symbol input-name)))
  433. (base (assq-ref checkout 'file-name)))
  434. (in-vicinity base relative-path))))
  435. (assq-ref arguments 'manifests)))
  436. (define (manifests->packages store manifests)
  437. "Return the list of packages found in MANIFESTS."
  438. (define (load-manifest manifest)
  439. (save-module-excursion
  440. (lambda ()
  441. (set-current-module (make-user-module '((guix profiles) (gnu))))
  442. (primitive-load manifest))))
  443. (delete-duplicates!
  444. (map manifest-entry-item
  445. (append-map (compose manifest-entries
  446. load-manifest)
  447. manifests))))
  448. (define (find-current-checkout arguments)
  449. "Find the first checkout of ARGUMENTS that provided the current file.
  450. Return #f if no such checkout is found."
  451. (let ((current-root
  452. (canonicalize-path
  453. (string-append (dirname (current-filename)) "/.."))))
  454. (find (lambda (argument)
  455. (and=> (assq-ref argument 'file-name)
  456. (lambda (name)
  457. (string=? name current-root)))) arguments)))
  458. ;;;
  459. ;;; Hydra entry point.
  460. ;;;
  461. (define (hydra-jobs store arguments)
  462. "Return Hydra jobs."
  463. (define subset
  464. (match (assoc-ref arguments 'subset)
  465. ("core" 'core) ; only build core packages
  466. ("hello" 'hello) ; only build hello
  467. (((? string?) (? string?) ...) 'list) ; only build selected list of packages
  468. ("manifests" 'manifests) ; only build packages in the list of manifests
  469. (_ 'all))) ; build everything
  470. (define systems
  471. (match (assoc-ref arguments 'systems)
  472. (#f %hydra-supported-systems)
  473. ((lst ...) lst)
  474. ((? string? str) (call-with-input-string str read))))
  475. (define checkout
  476. (or (find-current-checkout arguments)
  477. (assq-ref arguments 'superior-guix-checkout)))
  478. (define commit
  479. (assq-ref checkout 'revision))
  480. (define source
  481. (assq-ref checkout 'file-name))
  482. ;; Turn off grafts. Grafting is meant to happen on the user's machines.
  483. (parameterize ((%graft? #f))
  484. ;; Return one job for each package, except bootstrap packages.
  485. (append-map (lambda (system)
  486. (format (current-error-port)
  487. "evaluating for '~a' (heap size: ~a MiB)...~%"
  488. system
  489. (round
  490. (/ (assoc-ref (gc-stats) 'heap-size)
  491. (expt 2. 20))))
  492. (invalidate-derivation-caches!)
  493. (case subset
  494. ((all)
  495. ;; Build everything, including replacements.
  496. (let ((all (all-packages))
  497. (job (lambda (package)
  498. (package->job store package
  499. system))))
  500. (append (filter-map job all)
  501. (image-jobs store system)
  502. (system-test-jobs store system
  503. #:source source
  504. #:commit commit)
  505. (tarball-jobs store system)
  506. (cross-jobs store system))))
  507. ((core)
  508. ;; Build core packages only.
  509. (append (map (lambda (package)
  510. (package-job store (job-name package)
  511. package system))
  512. %core-packages)
  513. (cross-jobs store system)))
  514. ((hello)
  515. ;; Build hello package only.
  516. (let ((hello (specification->package "hello")))
  517. (list (package-job store (job-name hello) hello system))))
  518. ((list)
  519. ;; Build selected list of packages only.
  520. (let* ((names (assoc-ref arguments 'subset))
  521. (packages (map specification->package names)))
  522. (map (lambda (package)
  523. (package-job store (job-name package)
  524. package system))
  525. packages)))
  526. ((manifests)
  527. ;; Build packages in the list of manifests.
  528. (let* ((manifests (arguments->manifests arguments))
  529. (packages (manifests->packages store manifests)))
  530. (map (lambda (package)
  531. (package-job store (job-name package)
  532. package system))
  533. packages)))
  534. (else
  535. (error "unknown subset" subset))))
  536. systems)))