transformations.scm 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016-2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2021 Marius Bakke <marius@gnu.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix transformations)
  20. #:use-module ((guix config) #:select (%system))
  21. #:use-module (guix i18n)
  22. #:use-module (guix store)
  23. #:use-module (guix packages)
  24. #:use-module (guix build-system)
  25. #:use-module (guix profiles)
  26. #:use-module (guix diagnostics)
  27. #:autoload (guix download) (download-to-store)
  28. #:autoload (guix git-download) (git-reference? git-reference-url)
  29. #:autoload (guix git) (git-checkout git-checkout? git-checkout-url)
  30. #:autoload (guix upstream) (package-latest-release
  31. upstream-source-version
  32. upstream-source-signature-urls)
  33. #:autoload (guix cpu) (current-cpu cpu->gcc-architecture)
  34. #:use-module (guix utils)
  35. #:use-module (guix memoization)
  36. #:use-module (guix gexp)
  37. ;; Use the procedure that destructures "NAME-VERSION" forms.
  38. #:use-module ((guix build utils)
  39. #:select ((package-name->name+version
  40. . hyphen-package-name->name+version)))
  41. #:use-module (srfi srfi-1)
  42. #:use-module (srfi srfi-9)
  43. #:use-module (srfi srfi-11)
  44. #:use-module (srfi srfi-26)
  45. #:use-module (srfi srfi-34)
  46. #:use-module (srfi srfi-35)
  47. #:use-module (srfi srfi-37)
  48. #:use-module (ice-9 match)
  49. #:use-module (ice-9 vlist)
  50. #:export (options->transformation
  51. manifest-entry-with-transformations
  52. tunable-package?
  53. tuned-package
  54. show-transformation-options-help
  55. transformation-option-key?
  56. %transformation-options))
  57. ;;; Commentary:
  58. ;;;
  59. ;;; This module implements "package transformation options"---tools for
  60. ;;; package graph rewriting. It contains the graph rewriting logic, but also
  61. ;;; the tip of its user interface: command-line option handling.
  62. ;;;
  63. ;;; Code:
  64. (module-autoload! (current-module) '(gnu packages)
  65. '(specification->package))
  66. (define (numeric-extension? file-name)
  67. "Return true if FILE-NAME ends with digits."
  68. (string-every char-set:hex-digit (file-extension file-name)))
  69. (define (tarball-base-name file-name)
  70. "Return the \"base\" of FILE-NAME, removing '.tar.gz' or similar
  71. extensions."
  72. ;; TODO: Factorize.
  73. (cond ((not (file-extension file-name))
  74. file-name)
  75. ((numeric-extension? file-name)
  76. file-name)
  77. ((string=? (file-extension file-name) "tar")
  78. (file-sans-extension file-name))
  79. ((file-extension file-name)
  80. =>
  81. (match-lambda
  82. ("scm" file-name)
  83. (_ (tarball-base-name (file-sans-extension file-name)))))
  84. (else
  85. file-name)))
  86. ;; Files to be downloaded.
  87. (define-record-type <downloaded-file>
  88. (downloaded-file uri recursive?)
  89. downloaded-file?
  90. (uri downloaded-file-uri)
  91. (recursive? downloaded-file-recursive?))
  92. (define download-to-store*
  93. (store-lift download-to-store))
  94. (define-gexp-compiler (compile-downloaded-file (file <downloaded-file>)
  95. system target)
  96. "Download FILE and return the result as a store item."
  97. (match file
  98. (($ <downloaded-file> uri recursive?)
  99. (download-to-store* uri #:recursive? recursive?))))
  100. (define* (package-with-source p uri #:optional version)
  101. "Return a package based on P but with its source taken from URI. Extract
  102. the new package's version number from URI."
  103. (let ((base (tarball-base-name (basename uri))))
  104. (let-values (((_ version*)
  105. (hyphen-package-name->name+version base)))
  106. (package (inherit p)
  107. (version (or version version*
  108. (package-version p)))
  109. ;; Use #:recursive? #t to allow for directories.
  110. (source (downloaded-file uri #t))))))
  111. ;;;
  112. ;;; Transformations.
  113. ;;;
  114. (define (transform-package-source sources)
  115. "Return a transformation procedure that replaces package sources with the
  116. matching URIs given in SOURCES."
  117. (define new-sources
  118. (map (lambda (uri)
  119. (match (string-index uri #\=)
  120. (#f
  121. ;; Determine the package name and version from URI.
  122. (call-with-values
  123. (lambda ()
  124. (hyphen-package-name->name+version
  125. (tarball-base-name (basename uri))))
  126. (lambda (name version)
  127. (list name version uri))))
  128. (index
  129. ;; What's before INDEX is a "PKG@VER" or "PKG" spec.
  130. (call-with-values
  131. (lambda ()
  132. (package-name->name+version (string-take uri index)))
  133. (lambda (name version)
  134. (list name version
  135. (string-drop uri (+ 1 index))))))))
  136. sources))
  137. (lambda (obj)
  138. (let loop ((sources new-sources)
  139. (result '()))
  140. (match obj
  141. ((? package? p)
  142. (match (assoc-ref sources (package-name p))
  143. ((version source)
  144. (package-with-source p source version))
  145. (#f
  146. p)))
  147. (_
  148. obj)))))
  149. (define (evaluate-replacement-specs specs proc)
  150. "Parse SPECS, a list of strings like \"guile=guile@2.1\" and return a list
  151. of package spec/procedure pairs as expected by 'package-input-rewriting/spec'.
  152. PROC is called with the package to be replaced and its replacement according
  153. to SPECS. Raise an error if an element of SPECS uses invalid syntax, or if a
  154. package it refers to could not be found."
  155. (define not-equal
  156. (char-set-complement (char-set #\=)))
  157. (map (lambda (spec)
  158. (match (string-tokenize spec not-equal)
  159. ((spec new)
  160. (cons spec
  161. (let ((new (specification->package new)))
  162. (lambda (old)
  163. (proc old new)))))
  164. (x
  165. (raise (formatted-message
  166. (G_ "invalid replacement specification: ~s")
  167. spec)))))
  168. specs))
  169. (define (transform-package-inputs replacement-specs)
  170. "Return a procedure that, when passed a package, replaces its direct
  171. dependencies according to REPLACEMENT-SPECS. REPLACEMENT-SPECS is a list of
  172. strings like \"guile=guile@2.1\" meaning that, any dependency on a package
  173. called \"guile\" must be replaced with a dependency on a version 2.1 of
  174. \"guile\"."
  175. (let* ((replacements (evaluate-replacement-specs replacement-specs
  176. (lambda (old new)
  177. new)))
  178. (rewrite (package-input-rewriting/spec replacements)))
  179. (lambda (obj)
  180. (if (package? obj)
  181. (rewrite obj)
  182. obj))))
  183. (define (transform-package-inputs/graft replacement-specs)
  184. "Return a procedure that, when passed a package, replaces its direct
  185. dependencies according to REPLACEMENT-SPECS. REPLACEMENT-SPECS is a list of
  186. strings like \"gnutls=gnutls@3.5.4\" meaning that packages are built using the
  187. current 'gnutls' package, after which version 3.5.4 is grafted onto them."
  188. (define (set-replacement old new)
  189. (package (inherit old) (replacement new)))
  190. (let* ((replacements (evaluate-replacement-specs replacement-specs
  191. set-replacement))
  192. (rewrite (package-input-rewriting/spec replacements)))
  193. (lambda (obj)
  194. (if (package? obj)
  195. (rewrite obj)
  196. obj))))
  197. (define %not-equal
  198. (char-set-complement (char-set #\=)))
  199. (define (package-git-url package)
  200. "Return the URL of the Git repository for package, or raise an error if
  201. the source of PACKAGE is not fetched from a Git repository."
  202. (let ((source (package-source package)))
  203. (cond ((and (origin? source)
  204. (git-reference? (origin-uri source)))
  205. (git-reference-url (origin-uri source)))
  206. ((git-checkout? source)
  207. (git-checkout-url source))
  208. (else
  209. (raise
  210. (formatted-message (G_ "the source of ~a is not a Git reference")
  211. (package-full-name package)))))))
  212. (define (evaluate-git-replacement-specs specs proc)
  213. "Parse SPECS, a list of strings like \"guile=stable-2.2\", and return a list
  214. of package pairs, where (PROC PACKAGE URL BRANCH-OR-COMMIT) returns the
  215. replacement package. Raise an error if an element of SPECS uses invalid
  216. syntax, or if a package it refers to could not be found."
  217. (map (lambda (spec)
  218. (match (string-tokenize spec %not-equal)
  219. ((spec branch-or-commit)
  220. (define (replace old)
  221. (let* ((source (package-source old))
  222. (url (package-git-url old)))
  223. (proc old url branch-or-commit)))
  224. (cons spec replace))
  225. (_
  226. (raise
  227. (formatted-message (G_ "invalid replacement specification: ~s")
  228. spec)))))
  229. specs))
  230. (define (transform-package-source-branch replacement-specs)
  231. "Return a procedure that, when passed a package, replaces its direct
  232. dependencies according to REPLACEMENT-SPECS. REPLACEMENT-SPECS is a list of
  233. strings like \"guile-next=stable-3.0\" meaning that packages are built using
  234. 'guile-next' from the latest commit on its 'stable-3.0' branch."
  235. (define (replace old url branch)
  236. (package
  237. (inherit old)
  238. (version (string-append "git." (string-map (match-lambda
  239. (#\/ #\-)
  240. (chr chr))
  241. branch)))
  242. (source (git-checkout (url url) (branch branch)
  243. (recursive? #t)))))
  244. (let* ((replacements (evaluate-git-replacement-specs replacement-specs
  245. replace))
  246. (rewrite (package-input-rewriting/spec replacements)))
  247. (lambda (obj)
  248. (if (package? obj)
  249. (rewrite obj)
  250. obj))))
  251. (define (commit->version-string commit)
  252. "Return a string suitable for use in the 'version' field of a package based
  253. on the given COMMIT."
  254. (cond ((and (> (string-length commit) 1)
  255. (string-prefix? "v" commit)
  256. (char-set-contains? char-set:digit
  257. (string-ref commit 1)))
  258. ;; Probably a tag like "v1.0" or a 'git describe' identifier.
  259. (string-drop commit 1))
  260. ((not (string-every char-set:hex-digit commit))
  261. ;; Pass through tags and 'git describe' style IDs directly.
  262. commit)
  263. (else
  264. (string-append "git."
  265. (if (< (string-length commit) 7)
  266. commit
  267. (string-take commit 7))))))
  268. (define (transform-package-source-commit replacement-specs)
  269. "Return a procedure that, when passed a package, replaces its direct
  270. dependencies according to REPLACEMENT-SPECS. REPLACEMENT-SPECS is a list of
  271. strings like \"guile-next=cabba9e\" meaning that packages are built using
  272. 'guile-next' from commit 'cabba9e'."
  273. (define (replace old url commit)
  274. (package
  275. (inherit old)
  276. (version (commit->version-string commit))
  277. (source (git-checkout (url url) (commit commit)
  278. (recursive? #t)))))
  279. (let* ((replacements (evaluate-git-replacement-specs replacement-specs
  280. replace))
  281. (rewrite (package-input-rewriting/spec replacements)))
  282. (lambda (obj)
  283. (if (package? obj)
  284. (rewrite obj)
  285. obj))))
  286. (define (transform-package-source-git-url replacement-specs)
  287. "Return a procedure that, when passed a package, replaces its dependencies
  288. according to REPLACEMENT-SPECS. REPLACEMENT-SPECS is a list of strings like
  289. \"guile-json=https://gitthing.com/…\" meaning that packages are built using
  290. a checkout of the Git repository at the given URL."
  291. (define replacements
  292. (map (lambda (spec)
  293. (match (string-tokenize spec %not-equal)
  294. ((spec url)
  295. (cons spec
  296. (lambda (old)
  297. (package
  298. (inherit old)
  299. (source (git-checkout (url url)
  300. (recursive? #t)))))))
  301. (_
  302. (raise
  303. (formatted-message
  304. (G_ "~a: invalid Git URL replacement specification")
  305. spec)))))
  306. replacement-specs))
  307. (define rewrite
  308. (package-input-rewriting/spec replacements))
  309. (lambda (obj)
  310. (if (package? obj)
  311. (rewrite obj)
  312. obj)))
  313. (define (package-dependents/spec top bottom)
  314. "Return the list of dependents of BOTTOM, a spec string, that are also
  315. dependencies of TOP, a package."
  316. (define-values (name version)
  317. (package-name->name+version bottom))
  318. (define dependent?
  319. (mlambda (p)
  320. (and (package? p)
  321. (or (and (string=? name (package-name p))
  322. (or (not version)
  323. (version-prefix? version (package-version p))))
  324. (match (bag-direct-inputs (package->bag p))
  325. (((labels dependencies . _) ...)
  326. (any dependent? dependencies)))))))
  327. (filter dependent? (package-closure (list top))))
  328. (define (package-toolchain-rewriting p bottom toolchain)
  329. "Return a procedure that, when passed a package that's either BOTTOM or one
  330. of its dependents up to P so, changes it so it is built with TOOLCHAIN.
  331. TOOLCHAIN must be an input list."
  332. (define rewriting-property
  333. (gensym " package-toolchain-rewriting"))
  334. (match (package-dependents/spec p bottom)
  335. (() ;P does not depend on BOTTOM
  336. identity)
  337. (set
  338. ;; SET is the list of packages "between" P and BOTTOM (included) whose
  339. ;; toolchain needs to be changed.
  340. (package-mapping (lambda (p)
  341. (if (or (assq rewriting-property
  342. (package-properties p))
  343. (not (memq p set)))
  344. p
  345. (let ((p (package-with-c-toolchain p toolchain)))
  346. (package/inherit p
  347. (properties `((,rewriting-property . #t)
  348. ,@(package-properties p)))))))
  349. (lambda (p)
  350. (or (assq rewriting-property (package-properties p))
  351. (not (memq p set))))
  352. #:deep? #t))))
  353. (define (transform-package-toolchain replacement-specs)
  354. "Return a procedure that, when passed a package, changes its toolchain or
  355. that of its dependencies according to REPLACEMENT-SPECS. REPLACEMENT-SPECS is
  356. a list of strings like \"fftw=gcc-toolchain@10\" meaning that the package to
  357. the left of the equal sign must be built with the toolchain to the right of
  358. the equal sign."
  359. (define split-on-commas
  360. (cute string-tokenize <> (char-set-complement (char-set #\,))))
  361. (define (specification->input spec)
  362. (let ((package (specification->package spec)))
  363. (list (package-name package) package)))
  364. (define replacements
  365. (map (lambda (spec)
  366. (match (string-tokenize spec %not-equal)
  367. ((spec (= split-on-commas toolchain))
  368. (cons spec (map specification->input toolchain)))
  369. (_
  370. (raise
  371. (formatted-message
  372. (G_ "~a: invalid toolchain replacement specification")
  373. spec)))))
  374. replacement-specs))
  375. (lambda (obj)
  376. (if (package? obj)
  377. (or (any (match-lambda
  378. ((bottom . toolchain)
  379. ((package-toolchain-rewriting obj bottom toolchain) obj)))
  380. replacements)
  381. obj)
  382. obj)))
  383. (define tuning-compiler
  384. (mlambda (micro-architecture)
  385. "Return a compiler wrapper that passes '-march=MICRO-ARCHITECTURE' to the
  386. actual compiler."
  387. (define wrapper
  388. #~(begin
  389. (use-modules (ice-9 match))
  390. (define* (search-next command
  391. #:optional
  392. (path (string-split (getenv "PATH")
  393. #\:)))
  394. ;; Search the next COMMAND on PATH, a list of
  395. ;; directories representing the executable search path.
  396. (define this
  397. (stat (car (command-line))))
  398. (let loop ((path path))
  399. (match path
  400. (()
  401. (match command
  402. ("cc" (search-next "gcc"))
  403. (_ #f)))
  404. ((directory rest ...)
  405. (let* ((file (string-append
  406. directory "/" command))
  407. (st (stat file #f)))
  408. (if (and st (not (equal? this st)))
  409. file
  410. (loop rest)))))))
  411. (match (command-line)
  412. ((command arguments ...)
  413. (match (search-next (basename command))
  414. (#f (exit 127))
  415. (next
  416. (apply execl next
  417. (append (cons next arguments)
  418. (list (string-append "-march="
  419. #$micro-architecture))))))))))
  420. (define program
  421. (program-file (string-append "tuning-compiler-wrapper-" micro-architecture)
  422. wrapper))
  423. (computed-file (string-append "tuning-compiler-" micro-architecture)
  424. (with-imported-modules '((guix build utils))
  425. #~(begin
  426. (use-modules (guix build utils))
  427. (define bin (string-append #$output "/bin"))
  428. (mkdir-p bin)
  429. (for-each (lambda (program)
  430. (symlink #$program
  431. (string-append bin "/" program)))
  432. '("cc" "gcc" "clang" "g++" "c++" "clang++")))))))
  433. (define (build-system-with-tuning-compiler bs micro-architecture)
  434. "Return a variant of BS, a build system, that ensures that the compiler that
  435. BS uses (usually an implicit input) can generate code for MICRO-ARCHITECTURE,
  436. which names a specific CPU of the target architecture--e.g., when targeting
  437. 86_64 MICRO-ARCHITECTURE might be \"skylake\". If it does, return a build
  438. system that builds code for MICRO-ARCHITECTURE; otherwise raise an error."
  439. (define %not-hyphen
  440. (char-set-complement (char-set #\-)))
  441. (define lower
  442. (build-system-lower bs))
  443. (define (lower* . args)
  444. ;; The list of CPU names supported by the '-march' option of C/C++
  445. ;; compilers is specific to each compiler and version thereof. Rather
  446. ;; than pass '-march=MICRO-ARCHITECTURE' as is to the compiler, possibly
  447. ;; leading to an obscure build error, check whether the compiler is known
  448. ;; to support MICRO-ARCHITECTURE. If not, bail out.
  449. (let* ((lowered (apply lower args))
  450. (architecture (match (string-tokenize (bag-system lowered)
  451. %not-hyphen)
  452. ((arch _ ...) arch)))
  453. (compiler (any (match-lambda
  454. ((label (? package? p) . _)
  455. (and (assoc-ref (package-properties p)
  456. 'compiler-cpu-architectures)
  457. p))
  458. (_ #f))
  459. (bag-build-inputs lowered))))
  460. (unless compiler
  461. (raise (formatted-message
  462. (G_ "failed to determine which compiler is used"))))
  463. (let ((lst (assoc-ref (package-properties compiler)
  464. 'compiler-cpu-architectures)))
  465. (unless lst
  466. (raise (formatted-message
  467. (G_ "failed to determine whether ~a supports ~a")
  468. (package-full-name compiler)
  469. micro-architecture)))
  470. (unless (member micro-architecture
  471. (or (assoc-ref lst architecture) '()))
  472. (raise
  473. (make-compound-condition
  474. (formatted-message
  475. (G_ "compiler ~a does not support micro-architecture ~a")
  476. (package-full-name compiler)
  477. micro-architecture)
  478. (condition
  479. (&fix-hint
  480. (hint (match (assoc-ref lst architecture)
  481. (#f (format #f (G_ "Compiler ~a does not support
  482. micro-architectures of ~a.")
  483. (package-full-name compiler "@@")
  484. architecture))
  485. (lst
  486. (format #f (G_ "Compiler ~a supports the following ~a
  487. micro-architectures:
  488. @quotation
  489. ~a
  490. @end quotation")
  491. (package-full-name compiler "@@")
  492. architecture
  493. (string-join lst ", ")))))))))))
  494. (bag
  495. (inherit lowered)
  496. (build-inputs
  497. ;; Arrange so that the compiler wrapper comes first in $PATH.
  498. `(("tuning-compiler" ,(tuning-compiler micro-architecture))
  499. ,@(bag-build-inputs lowered))))))
  500. (build-system
  501. (inherit bs)
  502. (lower lower*)))
  503. (define (tuned-package p micro-architecture)
  504. "Return package P tuned for MICRO-ARCHITECTURE."
  505. (package
  506. (inherit p)
  507. (build-system
  508. (build-system-with-tuning-compiler (package-build-system p)
  509. micro-architecture))
  510. (arguments
  511. ;; The machine building this package may or may not be able to run code
  512. ;; for MICRO-ARCHITECTURE. Because of that, skip tests; they are run for
  513. ;; the "baseline" variant anyway.
  514. (substitute-keyword-arguments (package-arguments p)
  515. ((#:tests? _ #f) #f)))
  516. (properties
  517. `((cpu-tuning . ,micro-architecture)
  518. ;; Remove the 'tunable?' property so that 'package-tuning' does not
  519. ;; call 'tuned-package' again on this one.
  520. ,@(alist-delete 'tunable? (package-properties p))))))
  521. (define (tunable-package? package)
  522. "Return true if package PACKAGE is \"tunable\"--i.e., if tuning it for the
  523. host CPU is worthwhile."
  524. (assq 'tunable? (package-properties package)))
  525. (define package-tuning
  526. (mlambda (micro-architecture)
  527. "Return a procedure that maps the given package to its counterpart tuned
  528. for MICRO-ARCHITECTURE, a string suitable for GCC's '-march'."
  529. (define rewriting-property
  530. (gensym " package-tuning"))
  531. (package-mapping (lambda (p)
  532. (cond ((assq rewriting-property (package-properties p))
  533. p)
  534. ((assq 'tunable? (package-properties p))
  535. (info (G_ "tuning ~a for CPU ~a~%")
  536. (package-full-name p) micro-architecture)
  537. (package/inherit p
  538. (replacement (tuned-package p micro-architecture))
  539. (properties `((,rewriting-property . #t)
  540. ,@(package-properties p)))))
  541. (else
  542. p)))
  543. (lambda (p)
  544. (assq rewriting-property (package-properties p)))
  545. #:deep? #t)))
  546. (define (transform-package-tuning micro-architectures)
  547. "Return a procedure that, when "
  548. (match micro-architectures
  549. ((micro-architecture _ ...)
  550. (let ((rewrite (package-tuning micro-architecture)))
  551. (lambda (obj)
  552. (if (package? obj)
  553. (rewrite obj)
  554. obj))))))
  555. (define (transform-package-with-debug-info specs)
  556. "Return a procedure that, when passed a package, set its 'replacement' field
  557. to the same package but with #:strip-binaries? #f in its 'arguments' field."
  558. (define (non-stripped p)
  559. (package
  560. (inherit p)
  561. (arguments
  562. (substitute-keyword-arguments (package-arguments p)
  563. ((#:strip-binaries? _ #f) #f)))))
  564. (define (package-with-debug-info p)
  565. (if (member "debug" (package-outputs p))
  566. p
  567. (let loop ((p p))
  568. (match (package-replacement p)
  569. (#f
  570. (package
  571. (inherit p)
  572. (replacement (non-stripped p))))
  573. (next
  574. (package
  575. (inherit p)
  576. (replacement (loop next))))))))
  577. (define rewrite
  578. (package-input-rewriting/spec (map (lambda (spec)
  579. (cons spec package-with-debug-info))
  580. specs)))
  581. (lambda (obj)
  582. (if (package? obj)
  583. (rewrite obj)
  584. obj)))
  585. (define (transform-package-tests specs)
  586. "Return a procedure that, when passed a package, sets #:tests? #f in its
  587. 'arguments' field."
  588. (define (package-without-tests p)
  589. (package/inherit p
  590. (arguments
  591. (substitute-keyword-arguments (package-arguments p)
  592. ((#:tests? _ #f) #f)))))
  593. (define rewrite
  594. (package-input-rewriting/spec (map (lambda (spec)
  595. (cons spec package-without-tests))
  596. specs)))
  597. (lambda (obj)
  598. (if (package? obj)
  599. (rewrite obj)
  600. obj)))
  601. (define (patched-source name source patches)
  602. "Return a file-like object with the given NAME that applies PATCHES to
  603. SOURCE. SOURCE must itself be a file-like object of any type, including
  604. <git-checkout>, <local-file>, etc."
  605. (define patch
  606. (module-ref (resolve-interface '(gnu packages base)) 'patch))
  607. (computed-file name
  608. (with-imported-modules '((guix build utils))
  609. #~(begin
  610. (use-modules (guix build utils))
  611. (setenv "PATH" #+(file-append patch "/bin"))
  612. ;; XXX: Assume SOURCE is a directory. This is true in
  613. ;; most practical cases, where it's a <git-checkout>.
  614. (copy-recursively #+source #$output)
  615. (chdir #$output)
  616. (for-each (lambda (patch)
  617. (invoke "patch" "-p1" "--batch"
  618. "-i" patch))
  619. '(#+@patches))))))
  620. (define (transform-package-patches specs)
  621. "Return a procedure that, when passed a package, returns a package with
  622. additional patches."
  623. (define (package-with-extra-patches p patches)
  624. (let ((patches (map (lambda (file)
  625. (local-file file))
  626. patches)))
  627. (if (origin? (package-source p))
  628. (package/inherit p
  629. (source (origin
  630. (inherit (package-source p))
  631. (patches (append patches
  632. (origin-patches (package-source p)))))))
  633. (package/inherit p
  634. (source (patched-source (string-append (package-full-name p "-")
  635. "-source")
  636. (package-source p) patches))))))
  637. (define (coalesce-alist alist)
  638. ;; Coalesce multiple occurrences of the same key in ALIST.
  639. (let loop ((alist alist)
  640. (keys '())
  641. (mapping vlist-null))
  642. (match alist
  643. (()
  644. (map (lambda (key)
  645. (cons key (vhash-fold* cons '() key mapping)))
  646. (delete-duplicates (reverse keys))))
  647. (((key . value) . rest)
  648. (loop rest
  649. (cons key keys)
  650. (vhash-cons key value mapping))))))
  651. (define patches
  652. ;; Spec/patch alist.
  653. (coalesce-alist
  654. (map (lambda (spec)
  655. (match (string-tokenize spec %not-equal)
  656. ((spec patch)
  657. (cons spec (canonicalize-path patch)))
  658. (_
  659. (raise (formatted-message
  660. (G_ "~a: invalid package patch specification")
  661. spec)))))
  662. specs)))
  663. (define rewrite
  664. (package-input-rewriting/spec
  665. (map (match-lambda
  666. ((spec . patches)
  667. (cons spec (cut package-with-extra-patches <> patches))))
  668. patches)))
  669. (lambda (obj)
  670. (if (package? obj)
  671. (rewrite obj)
  672. obj)))
  673. (define (transform-package-latest specs)
  674. "Return a procedure that rewrites package graphs such that those in SPECS
  675. are replaced by their latest upstream version."
  676. (define (package-with-latest-upstream p)
  677. (let ((source (package-latest-release p)))
  678. (cond ((not source)
  679. (warning
  680. (G_ "could not determine latest upstream release of '~a'~%")
  681. (package-name p))
  682. p)
  683. ((string=? (upstream-source-version source)
  684. (package-version p))
  685. p)
  686. (else
  687. (unless (pair? (upstream-source-signature-urls source))
  688. (warning (G_ "cannot authenticate source of '~a', version ~a~%")
  689. (package-name p)
  690. (upstream-source-version source)))
  691. ;; TODO: Take 'upstream-source-input-changes' into account.
  692. (package
  693. (inherit p)
  694. (version (upstream-source-version source))
  695. (source source))))))
  696. (define rewrite
  697. (package-input-rewriting/spec
  698. (map (lambda (spec)
  699. (cons spec package-with-latest-upstream))
  700. specs)))
  701. (lambda (obj)
  702. (if (package? obj)
  703. (rewrite obj)
  704. obj)))
  705. (define %transformations
  706. ;; Transformations that can be applied to things to build. The car is the
  707. ;; key used in the option alist, and the cdr is the transformation
  708. ;; procedure; it is called with two arguments: the store, and a list of
  709. ;; things to build.
  710. `((with-source . ,transform-package-source)
  711. (with-input . ,transform-package-inputs)
  712. (with-graft . ,transform-package-inputs/graft)
  713. (with-branch . ,transform-package-source-branch)
  714. (with-commit . ,transform-package-source-commit)
  715. (with-git-url . ,transform-package-source-git-url)
  716. (with-c-toolchain . ,transform-package-toolchain)
  717. (tune . ,transform-package-tuning)
  718. (with-debug-info . ,transform-package-with-debug-info)
  719. (without-tests . ,transform-package-tests)
  720. (with-patch . ,transform-package-patches)
  721. (with-latest . ,transform-package-latest)))
  722. (define (transformation-procedure key)
  723. "Return the transformation procedure associated with KEY, a symbol such as
  724. 'with-source', or #f if there is none."
  725. (any (match-lambda
  726. ((k . proc)
  727. (and (eq? k key) proc)))
  728. %transformations))
  729. (define (transformation-option-key? key)
  730. "Return true if KEY is an option key (as returned while parsing options with
  731. %TRANSFORMATION-OPTIONS) corresponding to a package transformation option.
  732. For example, (transformation-option-key? 'with-input) => #t."
  733. (->bool (transformation-procedure key)))
  734. ;;;
  735. ;;; Command-line handling.
  736. ;;;
  737. (define %transformation-options
  738. ;; The command-line interface to the above transformations.
  739. (let ((parser (lambda (symbol)
  740. (lambda (opt name arg result . rest)
  741. (apply values
  742. (alist-cons symbol arg result)
  743. rest)))))
  744. (list (option '("with-source") #t #f
  745. (parser 'with-source))
  746. (option '("with-input") #t #f
  747. (parser 'with-input))
  748. (option '("with-graft") #t #f
  749. (parser 'with-graft))
  750. (option '("with-branch") #t #f
  751. (parser 'with-branch))
  752. (option '("with-commit") #t #f
  753. (parser 'with-commit))
  754. (option '("with-git-url") #t #f
  755. (parser 'with-git-url))
  756. (option '("with-c-toolchain") #t #f
  757. (parser 'with-c-toolchain))
  758. (option '("tune") #f #t
  759. (lambda (opt name arg result . rest)
  760. (define micro-architecture
  761. (match arg
  762. ((or #f "native")
  763. (unless (string=? (or (assoc-ref result 'system)
  764. (%current-system))
  765. %system)
  766. (leave (G_ "\
  767. building for ~a instead of ~a, so tuning cannot be guessed~%")
  768. (assoc-ref result 'system) %system))
  769. (cpu->gcc-architecture (current-cpu)))
  770. ("generic" #f)
  771. (_ arg)))
  772. (apply values
  773. (if micro-architecture
  774. (alist-cons 'tune micro-architecture
  775. result)
  776. (alist-delete 'tune result))
  777. rest)))
  778. (option '("with-debug-info") #t #f
  779. (parser 'with-debug-info))
  780. (option '("without-tests") #t #f
  781. (parser 'without-tests))
  782. (option '("with-patch") #t #f
  783. (parser 'with-patch))
  784. (option '("with-latest") #t #f
  785. (parser 'with-latest))
  786. (option '("help-transform") #f #f
  787. (lambda _
  788. (format #t
  789. (G_ "Available package transformation options:~%"))
  790. (show-transformation-options-help/detailed)
  791. (newline)
  792. (exit 0))))))
  793. (define (show-transformation-options-help/detailed)
  794. (display (G_ "
  795. --with-source=[PACKAGE=]SOURCE
  796. use SOURCE when building the corresponding package"))
  797. (display (G_ "
  798. --with-input=PACKAGE=REPLACEMENT
  799. replace dependency PACKAGE by REPLACEMENT"))
  800. (display (G_ "
  801. --with-graft=PACKAGE=REPLACEMENT
  802. graft REPLACEMENT on packages that refer to PACKAGE"))
  803. (display (G_ "
  804. --with-branch=PACKAGE=BRANCH
  805. build PACKAGE from the latest commit of BRANCH"))
  806. (display (G_ "
  807. --with-commit=PACKAGE=COMMIT
  808. build PACKAGE from COMMIT"))
  809. (display (G_ "
  810. --with-git-url=PACKAGE=URL
  811. build PACKAGE from the repository at URL"))
  812. (display (G_ "
  813. --with-patch=PACKAGE=FILE
  814. add FILE to the list of patches of PACKAGE"))
  815. (display (G_ "
  816. --with-latest=PACKAGE
  817. use the latest upstream release of PACKAGE"))
  818. (display (G_ "
  819. --with-c-toolchain=PACKAGE=TOOLCHAIN
  820. build PACKAGE and its dependents with TOOLCHAIN"))
  821. (display (G_ "
  822. --with-debug-info=PACKAGE
  823. build PACKAGE and preserve its debug info"))
  824. (display (G_ "
  825. --without-tests=PACKAGE
  826. build PACKAGE without running its tests")))
  827. (define (show-transformation-options-help)
  828. "Show basic help for package transformation options."
  829. (display (G_ "
  830. --help-transform list package transformation options not shown here")))
  831. (define (options->transformation opts)
  832. "Return a procedure that, when passed an object to build (package,
  833. derivation, etc.), applies the transformations specified by OPTS and returns
  834. the resulting objects. OPTS must be a list of symbol/string pairs such as:
  835. ((with-branch . \"guile-gcrypt=master\")
  836. (without-tests . \"libgcrypt\"))
  837. Each symbol names a transformation and the corresponding string is an argument
  838. to that transformation."
  839. (define applicable
  840. ;; List of applicable transformations as symbol/procedure pairs in the
  841. ;; order in which they appear on the command line.
  842. (filter-map (match-lambda
  843. ((key . value)
  844. (match (transformation-procedure key)
  845. (#f
  846. #f)
  847. (transform
  848. ;; XXX: We used to pass TRANSFORM a list of several
  849. ;; arguments, but we now pass only one, assuming that
  850. ;; transform composes well.
  851. (list key value (transform (list value)))))))
  852. (reverse opts)))
  853. (define (package-with-transformation-properties p)
  854. (package/inherit p
  855. (properties `((transformations
  856. . ,(map (match-lambda
  857. ((key value _)
  858. (cons key value)))
  859. applicable))
  860. ,@(package-properties p)))))
  861. (lambda (obj)
  862. (define (tagged-object new)
  863. (if (and (not (eq? obj new))
  864. (package? new) (not (null? applicable)))
  865. (package-with-transformation-properties new)
  866. new))
  867. (tagged-object
  868. (fold (match-lambda*
  869. (((name value transform) obj)
  870. (let ((new (transform obj)))
  871. (when (eq? new obj)
  872. (warning (G_ "transformation '~a' had no effect on ~a~%")
  873. name
  874. (if (package? obj)
  875. (package-full-name obj)
  876. obj)))
  877. new)))
  878. obj
  879. applicable))))
  880. (define (package-transformations package)
  881. "Return the transformations applied to PACKAGE according to its properties."
  882. (match (assq-ref (package-properties package) 'transformations)
  883. (#f '())
  884. (transformations transformations)))
  885. (define (manifest-entry-with-transformations entry)
  886. "Return ENTRY with an additional 'transformations' property if it's not
  887. already there."
  888. (let ((properties (manifest-entry-properties entry)))
  889. (if (assq 'transformations properties)
  890. entry
  891. (let ((item (manifest-entry-item entry)))
  892. (manifest-entry
  893. (inherit entry)
  894. (properties
  895. (match (and (package? item)
  896. (package-transformations item))
  897. ((or #f '())
  898. properties)
  899. (transformations
  900. `((transformations . ,transformations)
  901. ,@properties)))))))))