profiles.scm 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
  4. ;;; Copyright © 2014, 2016 Alex Kost <alezost@gmail.com>
  5. ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
  6. ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
  7. ;;; Copyright © 2016, 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
  8. ;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
  9. ;;; Copyright © 2017 Huang Ying <huang.ying.caritas@gmail.com>
  10. ;;; Copyright © 2017 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  11. ;;; Copyright © 2019 Kyle Meyer <kyle@kyleam.com>
  12. ;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com>
  13. ;;;
  14. ;;; This file is part of GNU Guix.
  15. ;;;
  16. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  17. ;;; under the terms of the GNU General Public License as published by
  18. ;;; the Free Software Foundation; either version 3 of the License, or (at
  19. ;;; your option) any later version.
  20. ;;;
  21. ;;; GNU Guix is distributed in the hope that it will be useful, but
  22. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  23. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. ;;; GNU General Public License for more details.
  25. ;;;
  26. ;;; You should have received a copy of the GNU General Public License
  27. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  28. (define-module (guix profiles)
  29. #:use-module ((guix config) #:select (%state-directory))
  30. #:use-module ((guix utils) #:hide (package-name->name+version))
  31. #:use-module ((guix build utils)
  32. #:select (package-name->name+version mkdir-p))
  33. #:use-module (guix i18n)
  34. #:use-module (guix records)
  35. #:use-module (guix packages)
  36. #:use-module (guix derivations)
  37. #:use-module (guix search-paths)
  38. #:use-module (guix gexp)
  39. #:use-module (guix modules)
  40. #:use-module (guix monads)
  41. #:use-module (guix store)
  42. #:use-module (guix sets)
  43. #:use-module (ice-9 vlist)
  44. #:use-module (ice-9 match)
  45. #:use-module (ice-9 regex)
  46. #:use-module (ice-9 ftw)
  47. #:use-module (ice-9 format)
  48. #:use-module (srfi srfi-1)
  49. #:use-module (srfi srfi-9)
  50. #:use-module (srfi srfi-11)
  51. #:use-module (srfi srfi-19)
  52. #:use-module (srfi srfi-26)
  53. #:use-module (srfi srfi-34)
  54. #:use-module (srfi srfi-35)
  55. #:export (&profile-error
  56. profile-error?
  57. profile-error-profile
  58. &profile-not-found-error
  59. profile-not-found-error?
  60. &profile-collision-error
  61. profile-collision-error?
  62. profile-collision-error-entry
  63. profile-collision-error-conflict
  64. &missing-generation-error
  65. missing-generation-error?
  66. missing-generation-error-generation
  67. &unmatched-pattern-error
  68. unmatched-pattern-error?
  69. unmatched-pattern-error-pattern
  70. unmatched-pattern-error-manifest
  71. manifest make-manifest
  72. manifest?
  73. manifest-entries
  74. manifest-transitive-entries
  75. <manifest-entry> ; FIXME: eventually make it internal
  76. manifest-entry
  77. manifest-entry?
  78. manifest-entry-name
  79. manifest-entry-version
  80. manifest-entry-output
  81. manifest-entry-item
  82. manifest-entry-dependencies
  83. manifest-entry-search-paths
  84. manifest-entry-parent
  85. manifest-entry-properties
  86. manifest-pattern
  87. manifest-pattern?
  88. manifest-pattern-name
  89. manifest-pattern-version
  90. manifest-pattern-output
  91. concatenate-manifests
  92. map-manifest-entries
  93. manifest-remove
  94. manifest-add
  95. manifest-lookup
  96. manifest-installed?
  97. manifest-matching-entries
  98. manifest-search-paths
  99. manifest-transaction
  100. manifest-transaction?
  101. manifest-transaction-install
  102. manifest-transaction-remove
  103. manifest-transaction-install-entry
  104. manifest-transaction-remove-pattern
  105. manifest-transaction-null?
  106. manifest-transaction-removal-candidate?
  107. manifest-perform-transaction
  108. manifest-transaction-effects
  109. profile-manifest
  110. package->manifest-entry
  111. packages->manifest
  112. ca-certificate-bundle
  113. %default-profile-hooks
  114. profile-derivation
  115. profile-search-paths
  116. generation-number
  117. generation-profile
  118. generation-numbers
  119. profile-generations
  120. relative-generation-spec->number
  121. relative-generation
  122. previous-generation-number
  123. generation-time
  124. generation-file-name
  125. switch-to-generation
  126. roll-back
  127. delete-generation
  128. %user-profile-directory
  129. %profile-directory
  130. %current-profile
  131. ensure-profile-directory
  132. canonicalize-profile
  133. user-friendly-profile))
  134. ;;; Commentary:
  135. ;;;
  136. ;;; Tools to create and manipulate profiles---i.e., the representation of a
  137. ;;; set of installed packages.
  138. ;;;
  139. ;;; Code:
  140. ;;;
  141. ;;; Condition types.
  142. ;;;
  143. (define-condition-type &profile-error &error
  144. profile-error?
  145. (profile profile-error-profile))
  146. (define-condition-type &profile-not-found-error &profile-error
  147. profile-not-found-error?)
  148. (define-condition-type &profile-collision-error &error
  149. profile-collision-error?
  150. (entry profile-collision-error-entry) ;<manifest-entry>
  151. (conflict profile-collision-error-conflict)) ;<manifest-entry>
  152. (define-condition-type &unmatched-pattern-error &error
  153. unmatched-pattern-error?
  154. (pattern unmatched-pattern-error-pattern) ;<manifest-pattern>
  155. (manifest unmatched-pattern-error-manifest)) ;<manifest>
  156. (define-condition-type &missing-generation-error &profile-error
  157. missing-generation-error?
  158. (generation missing-generation-error-generation))
  159. ;;;
  160. ;;; Manifests.
  161. ;;;
  162. (define-record-type <manifest>
  163. (manifest entries)
  164. manifest?
  165. (entries manifest-entries)) ; list of <manifest-entry>
  166. ;; Convenient alias, to avoid name clashes.
  167. (define make-manifest manifest)
  168. (define-record-type* <manifest-entry> manifest-entry
  169. make-manifest-entry
  170. manifest-entry?
  171. (name manifest-entry-name) ; string
  172. (version manifest-entry-version) ; string
  173. (output manifest-entry-output ; string
  174. (default "out"))
  175. (item manifest-entry-item) ; package | file-like | store path
  176. (dependencies manifest-entry-dependencies ; <manifest-entry>*
  177. (default '()))
  178. (search-paths manifest-entry-search-paths ; search-path-specification*
  179. (default '()))
  180. (parent manifest-entry-parent ; promise (#f | <manifest-entry>)
  181. (default (delay #f)))
  182. (properties manifest-entry-properties ; list of symbol/value pairs
  183. (default '())))
  184. (define-record-type* <manifest-pattern> manifest-pattern
  185. make-manifest-pattern
  186. manifest-pattern?
  187. (name manifest-pattern-name) ; string
  188. (version manifest-pattern-version ; string | #f
  189. (default #f))
  190. (output manifest-pattern-output ; string | #f
  191. (default "out")))
  192. (define (manifest-transitive-entries manifest)
  193. "Return the entries of MANIFEST along with their propagated inputs,
  194. recursively."
  195. (let loop ((entries (manifest-entries manifest))
  196. (result '())
  197. (visited (set))) ;compare with 'equal?'
  198. (match entries
  199. (()
  200. (reverse result))
  201. ((head . tail)
  202. (if (set-contains? visited head)
  203. (loop tail result visited)
  204. (loop (append (manifest-entry-dependencies head)
  205. tail)
  206. (cons head result)
  207. (set-insert head visited)))))))
  208. (define (profile-manifest profile)
  209. "Return the PROFILE's manifest."
  210. (let ((file (string-append profile "/manifest")))
  211. (if (file-exists? file)
  212. (call-with-input-file file read-manifest)
  213. (manifest '()))))
  214. (define (manifest-entry-lookup manifest)
  215. "Return a lookup procedure for the entries of MANIFEST. The lookup
  216. procedure takes two arguments: the entry name and output."
  217. (define mapping
  218. (let loop ((entries (manifest-entries manifest))
  219. (mapping vlist-null))
  220. (fold (lambda (entry result)
  221. (vhash-cons (cons (manifest-entry-name entry)
  222. (manifest-entry-output entry))
  223. entry
  224. (loop (manifest-entry-dependencies entry)
  225. result)))
  226. mapping
  227. entries)))
  228. (lambda (name output)
  229. (match (vhash-assoc (cons name output) mapping)
  230. ((_ . entry) entry)
  231. (#f #f))))
  232. (define* (lower-manifest-entry entry system #:key target)
  233. "Lower ENTRY for SYSTEM and TARGET such that its 'item' field is a store
  234. file name."
  235. (let ((item (manifest-entry-item entry)))
  236. (if (string? item)
  237. (with-monad %store-monad
  238. (return entry))
  239. (mlet %store-monad ((drv (lower-object item system
  240. #:target target))
  241. (output -> (manifest-entry-output entry)))
  242. (return (manifest-entry
  243. (inherit entry)
  244. (item (derivation->output-path drv output))))))))
  245. (define* (check-for-collisions manifest system #:key target)
  246. "Check whether the entries of MANIFEST conflict with one another; raise a
  247. '&profile-collision-error' when a conflict is encountered."
  248. (define lookup
  249. (manifest-entry-lookup manifest))
  250. (with-monad %store-monad
  251. (foldm %store-monad
  252. (lambda (entry result)
  253. (match (lookup (manifest-entry-name entry)
  254. (manifest-entry-output entry))
  255. ((? manifest-entry? second) ;potential conflict
  256. (mlet %store-monad ((first (lower-manifest-entry entry system
  257. #:target
  258. target))
  259. (second (lower-manifest-entry second system
  260. #:target
  261. target)))
  262. (if (string=? (manifest-entry-item first)
  263. (manifest-entry-item second))
  264. (return result)
  265. (raise (condition
  266. (&profile-collision-error
  267. (entry first)
  268. (conflict second)))))))
  269. (#f ;no conflict
  270. (return result))))
  271. #t
  272. (manifest-transitive-entries manifest))))
  273. (define* (package->manifest-entry package #:optional (output "out")
  274. #:key (parent (delay #f))
  275. (properties '()))
  276. "Return a manifest entry for the OUTPUT of package PACKAGE."
  277. ;; For each dependency, keep a promise pointing to its "parent" entry.
  278. (letrec* ((deps (map (match-lambda
  279. ((label package)
  280. (package->manifest-entry package
  281. #:parent (delay entry)))
  282. ((label package output)
  283. (package->manifest-entry package output
  284. #:parent (delay entry))))
  285. (package-propagated-inputs package)))
  286. (entry (manifest-entry
  287. (name (package-name package))
  288. (version (package-version package))
  289. (output output)
  290. (item package)
  291. (dependencies (delete-duplicates deps))
  292. (search-paths
  293. (package-transitive-native-search-paths package))
  294. (parent parent)
  295. (properties properties))))
  296. entry))
  297. (define (packages->manifest packages)
  298. "Return a list of manifest entries, one for each item listed in PACKAGES.
  299. Elements of PACKAGES can be either package objects or package/string tuples
  300. denoting a specific output of a package."
  301. (define inferiors-loaded?
  302. ;; This hack allows us to provide seamless integration for inferior
  303. ;; packages while not having a hard dependency on (guix inferior).
  304. (resolve-module '(guix inferior) #f #f #:ensure #f))
  305. (define (inferior->entry)
  306. (module-ref (resolve-interface '(guix inferior))
  307. 'inferior-package->manifest-entry))
  308. (manifest
  309. (map (match-lambda
  310. (((? package? package) output)
  311. (package->manifest-entry package output))
  312. ((? package? package)
  313. (package->manifest-entry package))
  314. ((thing output)
  315. (if inferiors-loaded?
  316. ((inferior->entry) thing output)
  317. (throw 'wrong-type-arg 'packages->manifest
  318. "Wrong package object: ~S" (list thing) (list thing))))
  319. (thing
  320. (if inferiors-loaded?
  321. ((inferior->entry) thing)
  322. (throw 'wrong-type-arg 'packages->manifest
  323. "Wrong package object: ~S" (list thing) (list thing)))))
  324. packages)))
  325. (define (manifest->gexp manifest)
  326. "Return a representation of MANIFEST as a gexp."
  327. (define (entry->gexp entry)
  328. (match entry
  329. (($ <manifest-entry> name version output (? string? path)
  330. (deps ...) (search-paths ...) _ (properties ...))
  331. #~(#$name #$version #$output #$path
  332. (propagated-inputs #$(map entry->gexp deps))
  333. (search-paths #$(map search-path-specification->sexp
  334. search-paths))
  335. (properties . #$properties)))
  336. (($ <manifest-entry> name version output package
  337. (deps ...) (search-paths ...) _ (properties ...))
  338. #~(#$name #$version #$output
  339. (ungexp package (or output "out"))
  340. (propagated-inputs #$(map entry->gexp deps))
  341. (search-paths #$(map search-path-specification->sexp
  342. search-paths))
  343. (properties . #$properties)))))
  344. (match manifest
  345. (($ <manifest> (entries ...))
  346. #~(manifest (version 3)
  347. (packages #$(map entry->gexp entries))))))
  348. (define (find-package name version)
  349. "Return a package from the distro matching NAME and possibly VERSION. This
  350. procedure is here for backward-compatibility and will eventually vanish."
  351. (define find-best-packages-by-name ;break abstractions
  352. (module-ref (resolve-interface '(gnu packages))
  353. 'find-best-packages-by-name))
  354. ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; the
  355. ;; former traverses the module tree only once and then allows for efficient
  356. ;; access via a vhash.
  357. (match (find-best-packages-by-name name version)
  358. ((p _ ...) p)
  359. (_
  360. (match (find-best-packages-by-name name #f)
  361. ((p _ ...) p)
  362. (_ #f)))))
  363. (define (sexp->manifest sexp)
  364. "Parse SEXP as a manifest."
  365. (define (infer-search-paths name version)
  366. ;; Infer the search path specifications for NAME-VERSION by looking up a
  367. ;; same-named package in the distro. Useful for the old manifest formats
  368. ;; that did not store search path info.
  369. (let ((package (find-package name version)))
  370. (if package
  371. (package-native-search-paths package)
  372. '())))
  373. (define (infer-dependency item parent)
  374. ;; Return a <manifest-entry> for ITEM.
  375. (let-values (((name version)
  376. (package-name->name+version
  377. (store-path-package-name item))))
  378. (manifest-entry
  379. (name name)
  380. (version version)
  381. (item item)
  382. (parent parent))))
  383. (define* (sexp->manifest-entry sexp #:optional (parent (delay #f)))
  384. (match sexp
  385. ((name version output path
  386. ('propagated-inputs deps)
  387. ('search-paths search-paths)
  388. extra-stuff ...)
  389. ;; For each of DEPS, keep a promise pointing to ENTRY.
  390. (letrec* ((deps* (map (cut sexp->manifest-entry <> (delay entry))
  391. deps))
  392. (entry (manifest-entry
  393. (name name)
  394. (version version)
  395. (output output)
  396. (item path)
  397. (dependencies deps*)
  398. (search-paths (map sexp->search-path-specification
  399. search-paths))
  400. (parent parent)
  401. (properties (or (assoc-ref extra-stuff 'properties)
  402. '())))))
  403. entry))))
  404. (match sexp
  405. (('manifest ('version 0)
  406. ('packages ((name version output path) ...)))
  407. (manifest
  408. (map (lambda (name version output path)
  409. (manifest-entry
  410. (name name)
  411. (version version)
  412. (output output)
  413. (item path)
  414. (search-paths (infer-search-paths name version))))
  415. name version output path)))
  416. ;; Version 1 adds a list of propagated inputs to the
  417. ;; name/version/output/path tuples.
  418. (('manifest ('version 1)
  419. ('packages ((name version output path deps) ...)))
  420. (manifest
  421. (map (lambda (name version output path deps)
  422. ;; Up to Guix 0.7 included, dependencies were listed as ("gmp"
  423. ;; "/gnu/store/...-gmp") for instance. Discard the 'label' in
  424. ;; such lists.
  425. (let ((deps (match deps
  426. (((labels directories) ...)
  427. directories)
  428. ((directories ...)
  429. directories))))
  430. (letrec* ((deps* (map (cute infer-dependency <> (delay entry))
  431. deps))
  432. (entry (manifest-entry
  433. (name name)
  434. (version version)
  435. (output output)
  436. (item path)
  437. (dependencies deps*)
  438. (search-paths
  439. (infer-search-paths name version)))))
  440. entry)))
  441. name version output path deps)))
  442. ;; Version 2 adds search paths and is slightly more verbose.
  443. (('manifest ('version 2 minor-version ...)
  444. ('packages ((name version output path
  445. ('propagated-inputs deps)
  446. ('search-paths search-paths)
  447. extra-stuff ...)
  448. ...)))
  449. (manifest
  450. (map (lambda (name version output path deps search-paths)
  451. (letrec* ((deps* (map (cute infer-dependency <> (delay entry))
  452. deps))
  453. (entry (manifest-entry
  454. (name name)
  455. (version version)
  456. (output output)
  457. (item path)
  458. (dependencies deps*)
  459. (search-paths
  460. (map sexp->search-path-specification
  461. search-paths)))))
  462. entry))
  463. name version output path deps search-paths)))
  464. ;; Version 3 represents DEPS as full-blown manifest entries.
  465. (('manifest ('version 3 minor-version ...)
  466. ('packages (entries ...)))
  467. (manifest (map sexp->manifest-entry entries)))
  468. (_
  469. (raise (condition
  470. (&message (message "unsupported manifest format")))))))
  471. (define (read-manifest port)
  472. "Return the packages listed in MANIFEST."
  473. (sexp->manifest (read port)))
  474. (define (concatenate-manifests lst)
  475. "Concatenate the manifests listed in LST and return the resulting manifest."
  476. (manifest (append-map manifest-entries lst)))
  477. (define (map-manifest-entries proc manifest)
  478. "Apply PROC to all the entries of MANIFEST and return a new manifest."
  479. (make-manifest
  480. (map proc (manifest-entries manifest))))
  481. (define (entry-predicate pattern)
  482. "Return a procedure that returns #t when passed a manifest entry that
  483. matches NAME/OUTPUT/VERSION. OUTPUT and VERSION may be #f, in which case they
  484. are ignored."
  485. (match pattern
  486. (($ <manifest-pattern> name version output)
  487. (match-lambda
  488. (($ <manifest-entry> entry-name entry-version entry-output)
  489. (and (string=? entry-name name)
  490. (or (not entry-output) (not output)
  491. (string=? entry-output output))
  492. (or (not version)
  493. (string=? entry-version version))))))))
  494. (define (manifest-remove manifest patterns)
  495. "Remove entries for each of PATTERNS from MANIFEST. Each item in PATTERNS
  496. must be a manifest-pattern."
  497. (define (remove-entry pattern lst)
  498. (remove (entry-predicate pattern) lst))
  499. (make-manifest (fold remove-entry
  500. (manifest-entries manifest)
  501. patterns)))
  502. (define (manifest-add manifest entries)
  503. "Add a list of manifest ENTRIES to MANIFEST and return new manifest.
  504. Remove MANIFEST entries that have the same name and output as ENTRIES."
  505. (define (same-entry? entry name output)
  506. (match entry
  507. (($ <manifest-entry> entry-name _ entry-output _)
  508. (and (equal? name entry-name)
  509. (equal? output entry-output)))))
  510. (make-manifest
  511. (fold (lambda (entry result) ;XXX: quadratic
  512. (match entry
  513. (($ <manifest-entry> name _ out _)
  514. (cons entry
  515. (remove (cut same-entry? <> name out)
  516. result)))))
  517. (manifest-entries manifest)
  518. entries)))
  519. (define (manifest-lookup manifest pattern)
  520. "Return the first item of MANIFEST that matches PATTERN, or #f if there is
  521. no match.."
  522. (find (entry-predicate pattern)
  523. (manifest-entries manifest)))
  524. (define (manifest-installed? manifest pattern)
  525. "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern),
  526. #f otherwise."
  527. (->bool (manifest-lookup manifest pattern)))
  528. (define (manifest-matching-entries manifest patterns)
  529. "Return all the entries of MANIFEST that match one of the PATTERNS. Raise
  530. an '&unmatched-pattern-error' if none of the entries of MANIFEST matches one
  531. of PATTERNS."
  532. (fold-right (lambda (pattern matches)
  533. (match (filter (entry-predicate pattern)
  534. (manifest-entries manifest))
  535. (()
  536. (raise (condition
  537. (&unmatched-pattern-error
  538. (pattern pattern)
  539. (manifest manifest)))))
  540. (lst
  541. (append lst matches))))
  542. '()
  543. patterns))
  544. (define (manifest-search-paths manifest)
  545. "Return the list of search path specifications that apply to MANIFEST,
  546. including the search path specification for $PATH."
  547. (delete-duplicates
  548. (cons $PATH
  549. (append-map manifest-entry-search-paths
  550. (manifest-entries manifest)))))
  551. ;;;
  552. ;;; Manifest transactions.
  553. ;;;
  554. (define-record-type* <manifest-transaction> manifest-transaction
  555. make-manifest-transaction
  556. manifest-transaction?
  557. (install manifest-transaction-install ; list of <manifest-entry>
  558. (default '()))
  559. (remove manifest-transaction-remove ; list of <manifest-pattern>
  560. (default '())))
  561. (define (manifest-transaction-install-entry entry transaction)
  562. "Augment TRANSACTION's set of installed packages with ENTRY, a
  563. <manifest-entry>."
  564. (manifest-transaction
  565. (inherit transaction)
  566. (install
  567. (cons entry (manifest-transaction-install transaction)))))
  568. (define (manifest-transaction-remove-pattern pattern transaction)
  569. "Add PATTERN to TRANSACTION's list of packages to remove."
  570. (manifest-transaction
  571. (inherit transaction)
  572. (remove
  573. (cons pattern (manifest-transaction-remove transaction)))))
  574. (define (manifest-transaction-null? transaction)
  575. "Return true if TRANSACTION has no effect---i.e., it neither installs nor
  576. remove software."
  577. (match transaction
  578. (($ <manifest-transaction> () ()) #t)
  579. (($ <manifest-transaction> _ _) #f)))
  580. (define (manifest-transaction-removal-candidate? entry transaction)
  581. "Return true if ENTRY is a candidate for removal in TRANSACTION."
  582. (any (lambda (pattern)
  583. ((entry-predicate pattern) entry))
  584. (manifest-transaction-remove transaction)))
  585. (define (manifest-transaction-effects manifest transaction)
  586. "Compute the effect of applying TRANSACTION to MANIFEST. Return 4 values:
  587. the list of packages that would be removed, installed, upgraded, or downgraded
  588. when applying TRANSACTION to MANIFEST. Upgrades are represented as pairs
  589. where the head is the entry being upgraded and the tail is the entry that will
  590. replace it."
  591. (define (manifest-entry->pattern entry)
  592. (manifest-pattern
  593. (name (manifest-entry-name entry))
  594. (output (manifest-entry-output entry))))
  595. (let loop ((input (manifest-transaction-install transaction))
  596. (install '())
  597. (upgrade '())
  598. (downgrade '()))
  599. (match input
  600. (()
  601. (let ((remove (manifest-transaction-remove transaction)))
  602. (values (manifest-matching-entries manifest remove)
  603. (reverse install) (reverse upgrade) (reverse downgrade))))
  604. ((entry rest ...)
  605. ;; Check whether installing ENTRY corresponds to the installation of a
  606. ;; new package or to an upgrade.
  607. ;; XXX: When the exact same output directory is installed, we're not
  608. ;; really upgrading anything. Add a check for that case.
  609. (let* ((pattern (manifest-entry->pattern entry))
  610. (previous (manifest-lookup manifest pattern))
  611. (newer? (and previous
  612. (version>=? (manifest-entry-version entry)
  613. (manifest-entry-version previous)))))
  614. (loop rest
  615. (if previous install (cons entry install))
  616. (if (and previous newer?)
  617. (alist-cons previous entry upgrade)
  618. upgrade)
  619. (if (and previous (not newer?))
  620. (alist-cons previous entry downgrade)
  621. downgrade)))))))
  622. (define (manifest-perform-transaction manifest transaction)
  623. "Perform TRANSACTION on MANIFEST and return the new manifest."
  624. (let ((install (manifest-transaction-install transaction))
  625. (remove (manifest-transaction-remove transaction)))
  626. (manifest-add (manifest-remove manifest remove)
  627. install)))
  628. ;;;
  629. ;;; Profiles.
  630. ;;;
  631. (define (manifest-inputs manifest)
  632. "Return a list of <gexp-input> objects for MANIFEST."
  633. (define entry->input
  634. (match-lambda
  635. (($ <manifest-entry> name version output thing deps)
  636. ;; THING may be a package or a file name. In the latter case, assume
  637. ;; it's already valid.
  638. (cons (gexp-input thing output)
  639. (append-map entry->input deps)))))
  640. (append-map entry->input (manifest-entries manifest)))
  641. (define* (manifest-lookup-package manifest name #:optional version)
  642. "Return as a monadic value the first package or store path referenced by
  643. MANIFEST that is named NAME and optionally has the given VERSION prefix, or #f
  644. if not found."
  645. ;; Return as a monadic value the package or store path referenced by the
  646. ;; manifest ENTRY, or #f if not referenced.
  647. (define (entry-lookup-package entry)
  648. (define (find-among-inputs inputs)
  649. (find (lambda (input)
  650. (and (package? input)
  651. (equal? name (package-name input))
  652. (if version
  653. (string-prefix? version (package-version input))
  654. #t)))
  655. inputs))
  656. (define (find-among-store-items items)
  657. (find (lambda (item)
  658. (let-values (((name* version*)
  659. (package-name->name+version
  660. (store-path-package-name item))))
  661. (and (string=? name name*)
  662. (if version
  663. (string-prefix? version version*)
  664. #t))))
  665. items))
  666. (with-monad %store-monad
  667. (match (manifest-entry-item entry)
  668. ((? package? package)
  669. (match (cons (list (package-name package) package)
  670. (package-transitive-inputs package))
  671. (((labels inputs . _) ...)
  672. (return (find-among-inputs inputs)))))
  673. ((? string? item)
  674. (mlet %store-monad ((refs (references* item)))
  675. (return (find-among-store-items refs))))
  676. (item
  677. ;; XXX: ITEM might be a 'computed-file' or anything like that, in
  678. ;; which case we don't know what to do. The fix may be to check
  679. ;; references once ITEM is compiled, as proposed at
  680. ;; <https://bugs.gnu.org/29927>.
  681. (return #f)))))
  682. (anym %store-monad
  683. entry-lookup-package (manifest-entries manifest)))
  684. (define (info-dir-file manifest)
  685. "Return a derivation that builds the 'dir' file for all the entries of
  686. MANIFEST."
  687. (define texinfo ;lazy reference
  688. (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
  689. (define gzip ;lazy reference
  690. (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
  691. (define glibc-utf8-locales ;lazy reference
  692. (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
  693. (define build
  694. (with-imported-modules '((guix build utils))
  695. #~(begin
  696. (use-modules (guix build utils)
  697. (srfi srfi-1) (srfi srfi-26)
  698. (ice-9 ftw))
  699. (define (info-file? file)
  700. (or (string-suffix? ".info" file)
  701. (string-suffix? ".info.gz" file)))
  702. (define (info-files top)
  703. (let ((infodir (string-append top "/share/info")))
  704. (map (cut string-append infodir "/" <>)
  705. (or (scandir infodir info-file?) '()))))
  706. (define (info-file-language file)
  707. (let* ((base (if (string-suffix? ".gz" file)
  708. (basename file ".info.gz")
  709. (basename file ".info")))
  710. (dot (string-rindex base #\.)))
  711. (if dot
  712. (string-drop base (+ 1 dot))
  713. "en")))
  714. (define (install-info info)
  715. (let ((language (info-file-language info)))
  716. ;; We need to choose a valid locale for $LANGUAGE to be honored.
  717. (setenv "LC_ALL" "en_US.utf8")
  718. (setenv "LANGUAGE" language)
  719. (zero?
  720. (system* #+(file-append texinfo "/bin/install-info")
  721. "--silent" info
  722. (apply string-append #$output "/share/info/dir"
  723. (if (string=? "en" language)
  724. '("")
  725. `("." ,language)))))))
  726. (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
  727. (setenv "GUIX_LOCPATH"
  728. #+(file-append glibc-utf8-locales "/lib/locale"))
  729. (mkdir-p (string-append #$output "/share/info"))
  730. (exit (every install-info
  731. (append-map info-files
  732. '#$(manifest-inputs manifest)))))))
  733. (gexp->derivation "info-dir" build
  734. #:local-build? #t
  735. #:substitutable? #f
  736. #:properties
  737. `((type . profile-hook)
  738. (hook . info-dir))))
  739. (define (ghc-package-cache-file manifest)
  740. "Return a derivation that builds the GHC 'package.cache' file for all the
  741. entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
  742. (define ghc ;lazy reference
  743. (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
  744. (define build
  745. (with-imported-modules '((guix build utils))
  746. #~(begin
  747. (use-modules (guix build utils)
  748. (srfi srfi-1) (srfi srfi-26)
  749. (ice-9 ftw))
  750. (define ghc-name-version
  751. (let* ((base (basename #+ghc)))
  752. (string-drop base
  753. (+ 1 (string-index base #\-)))))
  754. (define db-subdir
  755. (string-append "lib/" ghc-name-version "/package.conf.d"))
  756. (define db-dir
  757. (string-append #$output "/" db-subdir))
  758. (define (conf-files top)
  759. (let ((db (string-append top "/" db-subdir)))
  760. (if (file-exists? db)
  761. (find-files db "\\.conf$")
  762. '())))
  763. (define (copy-conf-file conf)
  764. (let ((base (basename conf)))
  765. (copy-file conf (string-append db-dir "/" base))))
  766. (system* (string-append #+ghc "/bin/ghc-pkg") "init" db-dir)
  767. (for-each copy-conf-file
  768. (append-map conf-files
  769. (delete-duplicates
  770. '#$(manifest-inputs manifest))))
  771. (let ((success
  772. (zero?
  773. (system* (string-append #+ghc "/bin/ghc-pkg") "recache"
  774. (string-append "--package-db=" db-dir)))))
  775. (for-each delete-file (find-files db-dir "\\.conf$"))
  776. (exit success)))))
  777. (with-monad %store-monad
  778. ;; Don't depend on GHC when there's nothing to do.
  779. (if (any (cut string-prefix? "ghc" <>)
  780. (map manifest-entry-name (manifest-entries manifest)))
  781. (gexp->derivation "ghc-package-cache" build
  782. #:local-build? #t
  783. #:substitutable? #f
  784. #:properties
  785. `((type . profile-hook)
  786. (hook . ghc-package-cache)))
  787. (return #f))))
  788. (define (ca-certificate-bundle manifest)
  789. "Return a derivation that builds a single-file bundle containing the CA
  790. certificates in the /etc/ssl/certs sub-directories of the packages in
  791. MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
  792. ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
  793. ;; for a discussion.
  794. (define glibc-utf8-locales ;lazy reference
  795. (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
  796. (define build
  797. (with-imported-modules '((guix build utils))
  798. #~(begin
  799. (use-modules (guix build utils)
  800. (rnrs io ports)
  801. (srfi srfi-1)
  802. (srfi srfi-26)
  803. (ice-9 ftw)
  804. (ice-9 match))
  805. (define (pem-file? file)
  806. (string-suffix? ".pem" file))
  807. (define (ca-files top)
  808. (let ((cert-dir (string-append top "/etc/ssl/certs")))
  809. (map (cut string-append cert-dir "/" <>)
  810. (or (scandir cert-dir pem-file?) '()))))
  811. (define (concatenate-files files result)
  812. "Make RESULT the concatenation of all of FILES."
  813. (define (dump file port)
  814. (display (call-with-input-file file get-string-all)
  815. port)
  816. (newline port)) ;required, see <https://bugs.debian.org/635570>
  817. (call-with-output-file result
  818. (lambda (port)
  819. (for-each (cut dump <> port) files))))
  820. ;; Some file names in the NSS certificates are UTF-8 encoded so
  821. ;; install a UTF-8 locale.
  822. (setenv "LOCPATH"
  823. (string-append #+glibc-utf8-locales "/lib/locale/"
  824. #+(version-major+minor
  825. (package-version glibc-utf8-locales))))
  826. (setlocale LC_ALL "en_US.utf8")
  827. (match (append-map ca-files '#$(manifest-inputs manifest))
  828. (()
  829. ;; Since there are no CA files, just create an empty directory. Do
  830. ;; not create the etc/ssl/certs sub-directory, since that would
  831. ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
  832. ;; defined.
  833. (mkdir #$output)
  834. #t)
  835. ((ca-files ...)
  836. (let ((result (string-append #$output "/etc/ssl/certs")))
  837. (mkdir-p result)
  838. (concatenate-files ca-files
  839. (string-append result
  840. "/ca-certificates.crt"))
  841. #t))))))
  842. (gexp->derivation "ca-certificate-bundle" build
  843. #:local-build? #t
  844. #:substitutable? #f
  845. #:properties
  846. `((type . profile-hook)
  847. (hook . ca-certificate-bundle))))
  848. (define (glib-schemas manifest)
  849. "Return a derivation that unions all schemas from manifest entries and
  850. creates the Glib 'gschemas.compiled' file."
  851. (define glib ; lazy reference
  852. (module-ref (resolve-interface '(gnu packages glib)) 'glib))
  853. (mlet %store-monad ((%glib (manifest-lookup-package manifest "glib"))
  854. ;; XXX: Can't use glib-compile-schemas corresponding
  855. ;; to the glib referenced by 'manifest'. Because
  856. ;; '%glib' can be either a package or store path, and
  857. ;; there's no way to get the "bin" output for the later.
  858. (glib-compile-schemas
  859. -> #~(string-append #+glib:bin
  860. "/bin/glib-compile-schemas")))
  861. (define build
  862. (with-imported-modules '((guix build utils)
  863. (guix build union)
  864. (guix build profiles)
  865. (guix search-paths)
  866. (guix records))
  867. #~(begin
  868. (use-modules (guix build utils)
  869. (guix build union)
  870. (guix build profiles)
  871. (srfi srfi-26))
  872. (let* ((destdir (string-append #$output "/share/glib-2.0/schemas"))
  873. (schemadirs (filter file-exists?
  874. (map (cut string-append <> "/share/glib-2.0/schemas")
  875. '#$(manifest-inputs manifest)))))
  876. ;; Union all the schemas.
  877. (mkdir-p (string-append #$output "/share/glib-2.0"))
  878. (union-build destdir schemadirs
  879. #:log-port (%make-void-port "w"))
  880. (let ((dir destdir))
  881. (when (file-is-directory? dir)
  882. (ensure-writable-directory dir)
  883. (invoke #+glib-compile-schemas
  884. (string-append "--targetdir=" dir)
  885. dir)))))))
  886. ;; Don't run the hook when there's nothing to do.
  887. (if %glib
  888. (gexp->derivation "glib-schemas" build
  889. #:local-build? #t
  890. #:substitutable? #f
  891. #:properties
  892. `((type . profile-hook)
  893. (hook . glib-schemas)))
  894. (return #f))))
  895. (define (gtk-icon-themes manifest)
  896. "Return a derivation that unions all icon themes from manifest entries and
  897. creates the GTK+ 'icon-theme.cache' file for each theme."
  898. (define gtk+ ; lazy reference
  899. (module-ref (resolve-interface '(gnu packages gtk)) 'gtk+))
  900. (mlet %store-monad ((%gtk+ (manifest-lookup-package manifest "gtk+"))
  901. ;; XXX: Can't use gtk-update-icon-cache corresponding
  902. ;; to the gtk+ referenced by 'manifest'. Because
  903. ;; '%gtk+' can be either a package or store path, and
  904. ;; there's no way to get the "bin" output for the later.
  905. (gtk-update-icon-cache
  906. -> #~(string-append #+gtk+:bin
  907. "/bin/gtk-update-icon-cache")))
  908. (define build
  909. (with-imported-modules '((guix build utils)
  910. (guix build union)
  911. (guix build profiles)
  912. (guix search-paths)
  913. (guix records))
  914. #~(begin
  915. (use-modules (guix build utils)
  916. (guix build union)
  917. (guix build profiles)
  918. (srfi srfi-26)
  919. (ice-9 ftw))
  920. (let* ((destdir (string-append #$output "/share/icons"))
  921. (icondirs (filter file-exists?
  922. (map (cut string-append <> "/share/icons")
  923. '#$(manifest-inputs manifest)))))
  924. ;; Union all the icons.
  925. (mkdir-p (string-append #$output "/share"))
  926. (union-build destdir icondirs
  927. #:log-port (%make-void-port "w"))
  928. ;; Update the 'icon-theme.cache' file for each icon theme.
  929. (for-each
  930. (lambda (theme)
  931. (let ((dir (string-append destdir "/" theme)))
  932. ;; Occasionally DESTDIR contains plain files, such as
  933. ;; "abiword_48.png". Ignore these.
  934. (when (file-is-directory? dir)
  935. (ensure-writable-directory dir)
  936. (system* #+gtk-update-icon-cache "-t" dir "--quiet"))))
  937. (scandir destdir (negate (cut member <> '("." "..")))))))))
  938. ;; Don't run the hook when there's nothing to do.
  939. (if %gtk+
  940. (gexp->derivation "gtk-icon-themes" build
  941. #:local-build? #t
  942. #:substitutable? #f
  943. #:properties
  944. `((type . profile-hook)
  945. (hook . gtk-icon-themes)))
  946. (return #f))))
  947. (define (gtk-im-modules manifest)
  948. "Return a derivation that builds the cache files for input method modules
  949. for both major versions of GTK+."
  950. (mlet %store-monad ((gtk+ (manifest-lookup-package manifest "gtk+" "3"))
  951. (gtk+-2 (manifest-lookup-package manifest "gtk+" "2")))
  952. (define (build gtk gtk-version query)
  953. (let ((major (string-take gtk-version 1)))
  954. (with-imported-modules '((guix build utils)
  955. (guix build union)
  956. (guix build profiles)
  957. (guix search-paths)
  958. (guix records))
  959. #~(begin
  960. (use-modules (guix build utils)
  961. (guix build union)
  962. (guix build profiles)
  963. (ice-9 popen)
  964. (srfi srfi-1)
  965. (srfi srfi-26))
  966. (let* ((prefix (string-append "/lib/gtk-" #$major ".0/"
  967. #$gtk-version))
  968. (destdir (string-append #$output prefix))
  969. (moddirs (cons (string-append #$gtk prefix "/immodules")
  970. (filter file-exists?
  971. (map (cut string-append <> prefix "/immodules")
  972. '#$(manifest-inputs manifest)))))
  973. (modules (append-map (cut find-files <> "\\.so$")
  974. moddirs)))
  975. ;; Generate a new immodules cache file.
  976. (mkdir-p (string-append #$output prefix))
  977. (let ((pipe (apply open-pipe* OPEN_READ #$query modules))
  978. (outfile (string-append #$output prefix
  979. "/immodules-gtk" #$major ".cache")))
  980. (dynamic-wind
  981. (const #t)
  982. (lambda ()
  983. (call-with-output-file outfile
  984. (lambda (out)
  985. (while (not (eof-object? (peek-char pipe)))
  986. (write-char (read-char pipe) out))))
  987. #t)
  988. (lambda ()
  989. (close-pipe pipe)))))))))
  990. ;; Don't run the hook when there's nothing to do.
  991. (let* ((pkg-gtk+ (module-ref ; lazy reference
  992. (resolve-interface '(gnu packages gtk)) 'gtk+))
  993. (gexp #~(begin
  994. #$(if gtk+
  995. (build
  996. gtk+ "3.0.0"
  997. ;; Use 'gtk-query-immodules-3.0' from the 'bin'
  998. ;; output of latest gtk+ package.
  999. #~(string-append
  1000. #$pkg-gtk+:bin "/bin/gtk-query-immodules-3.0"))
  1001. #t)
  1002. #$(if gtk+-2
  1003. (build
  1004. gtk+-2 "2.10.0"
  1005. #~(string-append
  1006. #$gtk+-2 "/bin/gtk-query-immodules-2.0"))
  1007. #t))))
  1008. (if (or gtk+ gtk+-2)
  1009. (gexp->derivation "gtk-im-modules" gexp
  1010. #:local-build? #t
  1011. #:substitutable? #f
  1012. #:properties
  1013. `((type . profile-hook)
  1014. (hook . gtk-im-modules)))
  1015. (return #f)))))
  1016. (define (xdg-desktop-database manifest)
  1017. "Return a derivation that builds the @file{mimeinfo.cache} database from
  1018. desktop files. It's used to query what applications can handle a given
  1019. MIME type."
  1020. (define desktop-file-utils ; lazy reference
  1021. (module-ref (resolve-interface '(gnu packages freedesktop))
  1022. 'desktop-file-utils))
  1023. (mlet %store-monad ((glib
  1024. (manifest-lookup-package
  1025. manifest "glib")))
  1026. (define build
  1027. (with-imported-modules '((guix build utils)
  1028. (guix build union))
  1029. #~(begin
  1030. (use-modules (srfi srfi-26)
  1031. (guix build utils)
  1032. (guix build union))
  1033. (let* ((destdir (string-append #$output "/share/applications"))
  1034. (appdirs (filter file-exists?
  1035. (map (cut string-append <>
  1036. "/share/applications")
  1037. '#$(manifest-inputs manifest))))
  1038. (update-desktop-database (string-append
  1039. #+desktop-file-utils
  1040. "/bin/update-desktop-database")))
  1041. (mkdir-p (string-append #$output "/share"))
  1042. (union-build destdir appdirs
  1043. #:log-port (%make-void-port "w"))
  1044. (exit (zero? (system* update-desktop-database destdir)))))))
  1045. ;; Don't run the hook when 'glib' is not referenced.
  1046. (if glib
  1047. (gexp->derivation "xdg-desktop-database" build
  1048. #:local-build? #t
  1049. #:substitutable? #f
  1050. #:properties
  1051. `((type . profile-hook)
  1052. (hook . xdg-desktop-database)))
  1053. (return #f))))
  1054. (define (xdg-mime-database manifest)
  1055. "Return a derivation that builds the @file{mime.cache} database from manifest
  1056. entries. It's used to query the MIME type of a given file."
  1057. (define shared-mime-info ; lazy reference
  1058. (module-ref (resolve-interface '(gnu packages gnome)) 'shared-mime-info))
  1059. (mlet %store-monad ((glib
  1060. (manifest-lookup-package
  1061. manifest "glib")))
  1062. (define build
  1063. (with-imported-modules '((guix build utils)
  1064. (guix build union))
  1065. #~(begin
  1066. (use-modules (srfi srfi-26)
  1067. (guix build utils)
  1068. (guix build union))
  1069. (let* ((datadir (string-append #$output "/share"))
  1070. (destdir (string-append datadir "/mime"))
  1071. (pkgdirs (filter file-exists?
  1072. (map (cut string-append <>
  1073. "/share/mime/packages")
  1074. (cons #+shared-mime-info
  1075. '#$(manifest-inputs manifest)))))
  1076. (update-mime-database (string-append
  1077. #+shared-mime-info
  1078. "/bin/update-mime-database")))
  1079. (mkdir-p destdir)
  1080. (union-build (string-append destdir "/packages") pkgdirs
  1081. #:log-port (%make-void-port "w"))
  1082. (setenv "XDG_DATA_HOME" datadir)
  1083. (exit (zero? (system* update-mime-database destdir)))))))
  1084. ;; Don't run the hook when there are no GLib based applications.
  1085. (if glib
  1086. (gexp->derivation "xdg-mime-database" build
  1087. #:local-build? #t
  1088. #:substitutable? #f
  1089. #:properties
  1090. `((type . profile-hook)
  1091. (hook . xdg-mime-database)))
  1092. (return #f))))
  1093. ;; Several font packages may install font files into same directory, so
  1094. ;; fonts.dir and fonts.scale file should be generated here, instead of in
  1095. ;; packages.
  1096. (define (fonts-dir-file manifest)
  1097. "Return a derivation that builds the @file{fonts.dir} and @file{fonts.scale}
  1098. files for the fonts of the @var{manifest} entries."
  1099. (define mkfontscale
  1100. (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontscale))
  1101. (define mkfontdir
  1102. (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontdir))
  1103. (define build
  1104. #~(begin
  1105. (use-modules (srfi srfi-26)
  1106. (guix build utils)
  1107. (guix build union))
  1108. (let ((fonts-dirs (filter file-exists?
  1109. (map (cut string-append <>
  1110. "/share/fonts")
  1111. '#$(manifest-inputs manifest)))))
  1112. (mkdir #$output)
  1113. (if (null? fonts-dirs)
  1114. (exit #t)
  1115. (let* ((share-dir (string-append #$output "/share"))
  1116. (fonts-dir (string-append share-dir "/fonts"))
  1117. (mkfontscale (string-append #+mkfontscale
  1118. "/bin/mkfontscale"))
  1119. (mkfontdir (string-append #+mkfontdir
  1120. "/bin/mkfontdir"))
  1121. (empty-file? (lambda (filename)
  1122. (call-with-ascii-input-file filename
  1123. (lambda (p)
  1124. (eqv? #\0 (read-char p))))))
  1125. (fonts-dir-file "fonts.dir")
  1126. (fonts-scale-file "fonts.scale"))
  1127. (mkdir-p share-dir)
  1128. ;; Create all sub-directories, because we may create fonts.dir
  1129. ;; and fonts.scale files in the sub-directories.
  1130. (union-build fonts-dir fonts-dirs
  1131. #:log-port (%make-void-port "w")
  1132. #:create-all-directories? #t)
  1133. (let ((directories (find-files fonts-dir
  1134. (lambda (file stat)
  1135. (eq? 'directory (stat:type stat)))
  1136. #:directories? #t)))
  1137. (for-each (lambda (dir)
  1138. (with-directory-excursion dir
  1139. (when (file-exists? fonts-scale-file)
  1140. (delete-file fonts-scale-file))
  1141. (when (file-exists? fonts-dir-file)
  1142. (delete-file fonts-dir-file))
  1143. (unless (and (zero? (system* mkfontscale))
  1144. (zero? (system* mkfontdir)))
  1145. (exit #f))
  1146. (when (and (file-exists? fonts-scale-file)
  1147. (empty-file? fonts-scale-file))
  1148. (delete-file fonts-scale-file))
  1149. (when (and (file-exists? fonts-dir-file)
  1150. (empty-file? fonts-dir-file))
  1151. (delete-file fonts-dir-file))))
  1152. directories)))))))
  1153. (gexp->derivation "fonts-dir" build
  1154. #:modules '((guix build utils)
  1155. (guix build union)
  1156. (srfi srfi-26))
  1157. #:local-build? #t
  1158. #:substitutable? #f
  1159. #:properties
  1160. `((type . profile-hook)
  1161. (hook . fonts-dir))))
  1162. (define (manual-database manifest)
  1163. "Return a derivation that builds the manual page database (\"mandb\") for
  1164. the entries in MANIFEST."
  1165. (define gdbm-ffi
  1166. (module-ref (resolve-interface '(gnu packages guile))
  1167. 'guile-gdbm-ffi))
  1168. (define zlib
  1169. (module-ref (resolve-interface '(gnu packages compression)) 'zlib))
  1170. (define config.scm
  1171. (scheme-file "config.scm"
  1172. #~(begin
  1173. (define-module #$'(guix config) ;placate Geiser
  1174. #:export (%libz))
  1175. (define %libz
  1176. #+(file-append zlib "/lib/libz")))))
  1177. (define modules
  1178. (cons `((guix config) => ,config.scm)
  1179. (delete '(guix config)
  1180. (source-module-closure `((guix build utils)
  1181. (guix man-db))))))
  1182. (define build
  1183. (with-imported-modules modules
  1184. (with-extensions (list gdbm-ffi) ;for (guix man-db)
  1185. #~(begin
  1186. (use-modules (guix man-db)
  1187. (guix build utils)
  1188. (srfi srfi-1)
  1189. (srfi srfi-19))
  1190. (define (compute-entries)
  1191. ;; This is the most expensive part (I/O and CPU, due to
  1192. ;; decompression), so report progress as we traverse INPUTS.
  1193. (let* ((inputs '#$(manifest-inputs manifest))
  1194. (total (length inputs)))
  1195. (append-map (lambda (directory count)
  1196. (format #t "\r[~3d/~3d] building list of \
  1197. man-db entries..."
  1198. count total)
  1199. (force-output)
  1200. (let ((man (string-append directory
  1201. "/share/man")))
  1202. (if (directory-exists? man)
  1203. (mandb-entries man)
  1204. '())))
  1205. inputs
  1206. (iota total 1))))
  1207. (define man-directory
  1208. (string-append #$output "/share/man"))
  1209. (mkdir-p man-directory)
  1210. (format #t "Creating manual page database...~%")
  1211. (force-output)
  1212. (let* ((start (current-time))
  1213. (entries (compute-entries))
  1214. (_ (write-mandb-database (string-append man-directory
  1215. "/index.db")
  1216. entries))
  1217. (duration (time-difference (current-time) start)))
  1218. (newline)
  1219. (format #t "~a entries processed in ~,1f s~%"
  1220. (length entries)
  1221. (+ (time-second duration)
  1222. (* (time-nanosecond duration) (expt 10 -9))))
  1223. (force-output))))))
  1224. (gexp->derivation "manual-database" build
  1225. ;; Work around GDBM 1.13 issue whereby uninitialized bytes
  1226. ;; get written to disk:
  1227. ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29654#23>.
  1228. #:env-vars `(("MALLOC_PERTURB_" . "1"))
  1229. #:local-build? #t
  1230. #:properties
  1231. `((type . profile-hook)
  1232. (hook . manual-database))))
  1233. (define (texlive-configuration manifest)
  1234. "Return a derivation that builds a TeXlive configuration for the entries in
  1235. MANIFEST."
  1236. (define entry->texlive-input
  1237. (match-lambda
  1238. (($ <manifest-entry> name version output thing deps)
  1239. (if (string-prefix? "texlive-" name)
  1240. (cons (gexp-input thing output)
  1241. (append-map entry->texlive-input deps))
  1242. '()))))
  1243. (define build
  1244. (with-imported-modules '((guix build utils)
  1245. (guix build union))
  1246. #~(begin
  1247. (use-modules (guix build utils)
  1248. (guix build union))
  1249. ;; Build a modifiable union of all texlive inputs. We do this so
  1250. ;; that TeX live can resolve the parent and grandparent directories
  1251. ;; correctly. There might be a more elegant way to accomplish this.
  1252. (union-build #$output
  1253. '#$(append-map entry->texlive-input
  1254. (manifest-entries manifest))
  1255. #:create-all-directories? #t
  1256. #:log-port (%make-void-port "w"))
  1257. (let ((texmf.cnf (string-append
  1258. #$output
  1259. "/share/texmf-dist/web2c/texmf.cnf")))
  1260. (when (file-exists? texmf.cnf)
  1261. (substitute* texmf.cnf
  1262. (("^TEXMFROOT = .*")
  1263. (string-append "TEXMFROOT = " #$output "/share\n"))
  1264. (("^TEXMF = .*")
  1265. "TEXMF = $TEXMFROOT/share/texmf-dist\n"))))
  1266. #t)))
  1267. (with-monad %store-monad
  1268. (if (any (cut string-prefix? "texlive-" <>)
  1269. (map manifest-entry-name (manifest-entries manifest)))
  1270. (gexp->derivation "texlive-configuration" build
  1271. #:substitutable? #f
  1272. #:local-build? #t
  1273. #:properties
  1274. `((type . profile-hook)
  1275. (hook . texlive-configuration)))
  1276. (return #f))))
  1277. (define %default-profile-hooks
  1278. ;; This is the list of derivation-returning procedures that are called by
  1279. ;; default when making a non-empty profile.
  1280. (list info-dir-file
  1281. manual-database
  1282. fonts-dir-file
  1283. ghc-package-cache-file
  1284. ca-certificate-bundle
  1285. glib-schemas
  1286. gtk-icon-themes
  1287. gtk-im-modules
  1288. texlive-configuration
  1289. xdg-desktop-database
  1290. xdg-mime-database))
  1291. (define* (profile-derivation manifest
  1292. #:key
  1293. (hooks %default-profile-hooks)
  1294. (locales? #t)
  1295. (allow-collisions? #f)
  1296. (relative-symlinks? #f)
  1297. system target)
  1298. "Return a derivation that builds a profile (aka. 'user environment') with
  1299. the given MANIFEST. The profile includes additional derivations returned by
  1300. the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc.
  1301. Unless ALLOW-COLLISIONS? is true, a '&profile-collision-error' is raised if
  1302. entries in MANIFEST collide (for instance if there are two same-name packages
  1303. with a different version number.)
  1304. When LOCALES? is true, the build is performed under a UTF-8 locale; this adds
  1305. a dependency on the 'glibc-utf8-locales' package.
  1306. When RELATIVE-SYMLINKS? is true, use relative file names for symlink targets.
  1307. This is one of the things to do for the result to be relocatable.
  1308. When TARGET is true, it must be a GNU triplet, and the packages in MANIFEST
  1309. are cross-built for TARGET."
  1310. (mlet* %store-monad ((system (if system
  1311. (return system)
  1312. (current-system)))
  1313. (target (if target
  1314. (return target)
  1315. (current-target-system)))
  1316. (ok? (if allow-collisions?
  1317. (return #t)
  1318. (check-for-collisions manifest system
  1319. #:target target)))
  1320. (extras (if (null? (manifest-entries manifest))
  1321. (return '())
  1322. (mapm %store-monad
  1323. (lambda (hook)
  1324. (hook manifest))
  1325. hooks))))
  1326. (define inputs
  1327. (append (filter-map (lambda (drv)
  1328. (and (derivation? drv)
  1329. (gexp-input drv)))
  1330. extras)
  1331. (manifest-inputs manifest)))
  1332. (define glibc-utf8-locales ;lazy reference
  1333. (module-ref (resolve-interface '(gnu packages base))
  1334. 'glibc-utf8-locales))
  1335. (define set-utf8-locale
  1336. ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so
  1337. ;; install a UTF-8 locale.
  1338. #~(begin
  1339. (setenv "LOCPATH"
  1340. #$(file-append glibc-utf8-locales "/lib/locale/"
  1341. (version-major+minor
  1342. (package-version glibc-utf8-locales))))
  1343. (setlocale LC_ALL "en_US.utf8")))
  1344. (define builder
  1345. (with-imported-modules '((guix build profiles)
  1346. (guix build union)
  1347. (guix build utils)
  1348. (guix search-paths)
  1349. (guix records))
  1350. #~(begin
  1351. (use-modules (guix build profiles)
  1352. (guix search-paths)
  1353. (srfi srfi-1))
  1354. (setvbuf (current-output-port) _IOLBF)
  1355. (setvbuf (current-error-port) _IOLBF)
  1356. #+(if locales? set-utf8-locale #t)
  1357. (define search-paths
  1358. ;; Search paths of MANIFEST's packages, converted back to their
  1359. ;; record form.
  1360. (map sexp->search-path-specification
  1361. (delete-duplicates
  1362. '#$(map search-path-specification->sexp
  1363. (manifest-search-paths manifest)))))
  1364. (build-profile #$output '#$inputs
  1365. #:symlink #$(if relative-symlinks?
  1366. #~symlink-relative
  1367. #~symlink)
  1368. #:manifest '#$(manifest->gexp manifest)
  1369. #:search-paths search-paths))))
  1370. (gexp->derivation "profile" builder
  1371. #:system system
  1372. #:target target
  1373. ;; Don't complain about _IO* on Guile 2.2.
  1374. #:env-vars '(("GUILE_WARN_DEPRECATED" . "no"))
  1375. ;; Not worth offloading.
  1376. #:local-build? #t
  1377. ;; Disable substitution because it would trigger a
  1378. ;; connection to the substitute server, which is likely
  1379. ;; to have no substitute to offer.
  1380. #:substitutable? #f)))
  1381. (define* (profile-search-paths profile
  1382. #:optional (manifest (profile-manifest profile))
  1383. #:key (getenv (const #f)))
  1384. "Read the manifest of PROFILE and evaluate the values of search path
  1385. environment variables required by PROFILE; return a list of
  1386. specification/value pairs. If MANIFEST is not #f, it is assumed to be the
  1387. manifest of PROFILE, which avoids rereading it.
  1388. Use GETENV to determine the current settings and report only settings not
  1389. already effective."
  1390. (evaluate-search-paths (manifest-search-paths manifest)
  1391. (list profile) getenv))
  1392. (define (profile-regexp profile)
  1393. "Return a regular expression that matches PROFILE's name and number."
  1394. (make-regexp (string-append "^" (regexp-quote (basename profile))
  1395. "-([0-9]+)")))
  1396. (define (generation-number profile)
  1397. "Return PROFILE's number or 0. An absolute file name must be used."
  1398. (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
  1399. (basename (readlink profile))))
  1400. (compose string->number (cut match:substring <> 1)))
  1401. 0))
  1402. (define %profile-generation-rx
  1403. ;; Regexp that matches profile generation.
  1404. (make-regexp "(.*)-([0-9]+)-link$"))
  1405. (define (generation-profile file)
  1406. "If FILE is a profile generation GC root such as \"guix-profile-42-link\",
  1407. return its corresponding profile---e.g., \"guix-profile\". Otherwise return
  1408. #f."
  1409. (match (regexp-exec %profile-generation-rx file)
  1410. (#f #f)
  1411. (m (let ((profile (match:substring m 1)))
  1412. (and (file-exists? (string-append profile "/manifest"))
  1413. profile)))))
  1414. (define (generation-numbers profile)
  1415. "Return the sorted list of generation numbers of PROFILE, or '(0) if no
  1416. former profiles were found."
  1417. (match (scandir (dirname profile)
  1418. (cute regexp-exec (profile-regexp profile) <>))
  1419. (#f ; no profile directory
  1420. '(0))
  1421. (() ; no profiles
  1422. '(0))
  1423. ((profiles ...) ; former profiles around
  1424. (sort (map (compose string->number
  1425. (cut match:substring <> 1)
  1426. (cute regexp-exec (profile-regexp profile) <>))
  1427. profiles)
  1428. <))))
  1429. (define (profile-generations profile)
  1430. "Return a list of PROFILE's generations."
  1431. (let ((generations (generation-numbers profile)))
  1432. (if (equal? generations '(0))
  1433. '()
  1434. generations)))
  1435. (define (relative-generation-spec->number profile spec)
  1436. "Return PROFILE's generation specified by SPEC, which is a string. The SPEC
  1437. may be a N, -N, or +N, where N is a number. If the spec is N, then the number
  1438. returned is N. If it is -N, then the number returned is the profile's current
  1439. generation number minus N. If it is +N, then the number returned is the
  1440. profile's current generation number plus N. Return #f if there is no such
  1441. generation."
  1442. (let ((number (string->number spec)))
  1443. (and number
  1444. (case (string-ref spec 0)
  1445. ((#\+ #\-)
  1446. (relative-generation profile number))
  1447. (else (if (memv number (profile-generations profile))
  1448. number
  1449. #f))))))
  1450. (define* (relative-generation profile shift #:optional
  1451. (current (generation-number profile)))
  1452. "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
  1453. SHIFT is a positive or negative number.
  1454. Return #f if there is no such generation."
  1455. (let* ((abs-shift (abs shift))
  1456. (numbers (profile-generations profile))
  1457. (from-current (memq current
  1458. (if (negative? shift)
  1459. (reverse numbers)
  1460. numbers))))
  1461. (and from-current
  1462. (< abs-shift (length from-current))
  1463. (list-ref from-current abs-shift))))
  1464. (define* (previous-generation-number profile #:optional
  1465. (number (generation-number profile)))
  1466. "Return the number of the generation before generation NUMBER of
  1467. PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
  1468. case when generations have been deleted (there are \"holes\")."
  1469. (or (relative-generation profile -1 number)
  1470. 0))
  1471. (define (generation-file-name profile generation)
  1472. "Return the file name for PROFILE's GENERATION."
  1473. (format #f "~a-~a-link" profile generation))
  1474. (define (generation-time profile number)
  1475. "Return the creation time of a generation in the UTC format."
  1476. (make-time time-utc 0
  1477. (stat:ctime (stat (generation-file-name profile number)))))
  1478. (define (link-to-empty-profile store generation)
  1479. "Link GENERATION, a string, to the empty profile. An error is raised if
  1480. that fails."
  1481. (let* ((drv (run-with-store store
  1482. (profile-derivation (manifest '())
  1483. #:locales? #f)))
  1484. (prof (derivation->output-path drv "out")))
  1485. (build-derivations store (list drv))
  1486. (switch-symlinks generation prof)))
  1487. (define (switch-to-generation profile number)
  1488. "Atomically switch PROFILE to the generation NUMBER. Return the number of
  1489. the generation that was current before switching."
  1490. (let ((current (generation-number profile))
  1491. (generation (generation-file-name profile number)))
  1492. (cond ((not (file-exists? profile))
  1493. (raise (condition (&profile-not-found-error
  1494. (profile profile)))))
  1495. ((not (file-exists? generation))
  1496. (raise (condition (&missing-generation-error
  1497. (profile profile)
  1498. (generation number)))))
  1499. (else
  1500. (switch-symlinks profile (basename generation))
  1501. current))))
  1502. (define (switch-to-previous-generation profile)
  1503. "Atomically switch PROFILE to the previous generation. Return the former
  1504. generation number and the current one."
  1505. (let ((previous (previous-generation-number profile)))
  1506. (values (switch-to-generation profile previous)
  1507. previous)))
  1508. (define (roll-back store profile)
  1509. "Roll back to the previous generation of PROFILE. Return the number of the
  1510. generation that was current before switching and the new generation number."
  1511. (let* ((number (generation-number profile))
  1512. (previous-number (previous-generation-number profile number))
  1513. (previous-generation (generation-file-name profile previous-number)))
  1514. (cond ((not (file-exists? profile)) ;invalid profile
  1515. (raise (condition (&profile-not-found-error
  1516. (profile profile)))))
  1517. ((zero? number) ;empty profile
  1518. (values number number))
  1519. ((or (zero? previous-number) ;going to emptiness
  1520. (not (file-exists? previous-generation)))
  1521. (link-to-empty-profile store previous-generation)
  1522. (switch-to-previous-generation profile))
  1523. (else ;anything else
  1524. (switch-to-previous-generation profile)))))
  1525. (define (delete-generation store profile number)
  1526. "Delete generation with NUMBER from PROFILE. Return the file name of the
  1527. generation that has been deleted, or #f if nothing was done (for instance
  1528. because the NUMBER is zero.)"
  1529. (define (delete-and-return)
  1530. (let ((generation (generation-file-name profile number)))
  1531. (delete-file generation)
  1532. generation))
  1533. (let* ((current-number (generation-number profile))
  1534. (previous-number (previous-generation-number profile number))
  1535. (previous-generation (generation-file-name profile previous-number)))
  1536. (cond ((zero? number) #f) ;do not delete generation 0
  1537. ((and (= number current-number)
  1538. (not (file-exists? previous-generation)))
  1539. (link-to-empty-profile store previous-generation)
  1540. (switch-to-previous-generation profile)
  1541. (delete-and-return))
  1542. ((= number current-number)
  1543. (roll-back store profile)
  1544. (delete-and-return))
  1545. (else
  1546. (delete-and-return)))))
  1547. (define %user-profile-directory
  1548. (and=> (getenv "HOME")
  1549. (cut string-append <> "/.guix-profile")))
  1550. (define %profile-directory
  1551. (string-append %state-directory "/profiles/"
  1552. (or (and=> (or (getenv "USER")
  1553. (getenv "LOGNAME")
  1554. (false-if-exception
  1555. (passwd:name (getpwuid (getuid)))))
  1556. (cut string-append "per-user/" <>))
  1557. "default")))
  1558. (define %current-profile
  1559. ;; Call it `guix-profile', not `profile', to allow Guix profiles to
  1560. ;; coexist with Nix profiles.
  1561. (string-append %profile-directory "/guix-profile"))
  1562. (define (ensure-profile-directory)
  1563. "Attempt to create /…/profiles/per-user/$USER if needed. Nowadays this is
  1564. taken care of by the daemon."
  1565. (let ((s (stat %profile-directory #f)))
  1566. (unless (and s (eq? 'directory (stat:type s)))
  1567. (catch 'system-error
  1568. (lambda ()
  1569. (mkdir-p %profile-directory))
  1570. (lambda args
  1571. ;; Often, we cannot create %PROFILE-DIRECTORY because its
  1572. ;; parent directory is root-owned and we're running
  1573. ;; unprivileged.
  1574. (raise (condition
  1575. (&message
  1576. (message
  1577. (format #f
  1578. (G_ "while creating directory `~a': ~a")
  1579. %profile-directory
  1580. (strerror (system-error-errno args)))))
  1581. (&fix-hint
  1582. (hint
  1583. (format #f (G_ "Please create the @file{~a} directory, \
  1584. with you as the owner.")
  1585. %profile-directory))))))))
  1586. ;; Bail out if it's not owned by the user.
  1587. (unless (or (not s) (= (stat:uid s) (getuid)))
  1588. (raise (condition
  1589. (&message
  1590. (message
  1591. (format #f (G_ "directory `~a' is not owned by you")
  1592. %profile-directory)))
  1593. (&fix-hint
  1594. (hint
  1595. (format #f (G_ "Please change the owner of @file{~a} \
  1596. to user ~s.")
  1597. %profile-directory (or (getenv "USER")
  1598. (getenv "LOGNAME")
  1599. (getuid))))))))))
  1600. (define (canonicalize-profile profile)
  1601. "If PROFILE points to a profile in %PROFILE-DIRECTORY, return that.
  1602. Otherwise return PROFILE unchanged. The goal is to treat '-p ~/.guix-profile'
  1603. as if '-p' was omitted." ; see <http://bugs.gnu.org/17939>
  1604. ;; Trim trailing slashes so 'readlink' can do its job.
  1605. (let ((profile (string-trim-right profile #\/)))
  1606. (catch 'system-error
  1607. (lambda ()
  1608. (let ((target (readlink profile)))
  1609. (if (string=? (dirname target) %profile-directory)
  1610. target
  1611. profile)))
  1612. (const profile))))
  1613. (define %known-shorthand-profiles
  1614. ;; Known shorthand forms for profiles that the user manipulates.
  1615. (list (string-append (config-directory #:ensure? #f) "/current")
  1616. %user-profile-directory))
  1617. (define (user-friendly-profile profile)
  1618. "Return either ~/.guix-profile or ~/.config/guix/current if that's what
  1619. PROFILE refers to, directly or indirectly, or PROFILE."
  1620. (or (find (lambda (shorthand)
  1621. (and shorthand
  1622. (let ((target (false-if-exception
  1623. (readlink shorthand))))
  1624. (and target (string=? target profile)))))
  1625. %known-shorthand-profiles)
  1626. profile))
  1627. ;;; profiles.scm ends here