upstream.scm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2015 Alex Kost <alezost@gmail.com>
  4. ;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
  5. ;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
  6. ;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@telenet.be>
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (guix upstream)
  23. #:use-module (guix records)
  24. #:use-module (guix utils)
  25. #:use-module (guix discovery)
  26. #:use-module ((guix download)
  27. #:select (download-to-store url-fetch))
  28. #:use-module (guix git-download)
  29. #:use-module (guix gnupg)
  30. #:use-module (guix packages)
  31. #:use-module (guix diagnostics)
  32. #:use-module (guix ui)
  33. #:use-module (guix base32)
  34. #:use-module (guix gexp)
  35. #:autoload (guix git) (latest-repository-commit git-reference->git-checkout)
  36. #:use-module (guix hash)
  37. #:use-module (guix store)
  38. #:use-module ((guix derivations) #:select (built-derivations derivation->output-path))
  39. #:autoload (gcrypt hash) (port-sha256)
  40. #:use-module (guix monads)
  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 (rnrs bytevectors)
  48. #:use-module (ice-9 match)
  49. #:use-module (ice-9 regex)
  50. #:export (upstream-source
  51. upstream-source?
  52. upstream-source-package
  53. upstream-source-version
  54. upstream-source-urls
  55. upstream-source-signature-urls
  56. upstream-source-archive-types
  57. upstream-source-input-changes
  58. url-predicate
  59. url-prefix-predicate
  60. coalesce-sources
  61. upstream-updater
  62. upstream-updater?
  63. upstream-updater-name
  64. upstream-updater-description
  65. upstream-updater-predicate
  66. upstream-updater-latest
  67. upstream-input-change?
  68. upstream-input-change-name
  69. upstream-input-change-type
  70. upstream-input-change-action
  71. changed-inputs
  72. %updaters
  73. lookup-updater
  74. download-tarball
  75. package-latest-release
  76. package-latest-release*
  77. package-update
  78. update-package-source))
  79. ;;; Commentary:
  80. ;;;
  81. ;;; This module provides tools to represent and manipulate a upstream source
  82. ;;; code, and to auto-update package recipes.
  83. ;;;
  84. ;;; Code:
  85. ;; Representation of upstream's source. There can be several URLs--e.g.,
  86. ;; tar.gz, tar.gz, etc. There can be correspond signature URLs, one per
  87. ;; source URL.
  88. (define-record-type* <upstream-source>
  89. upstream-source make-upstream-source
  90. upstream-source?
  91. (package upstream-source-package) ;string
  92. (version upstream-source-version) ;string
  93. (urls upstream-source-urls) ;list of strings|git-reference
  94. (signature-urls upstream-source-signature-urls ;#f | list of strings
  95. (default #f))
  96. (input-changes upstream-source-input-changes
  97. (default '()) (thunked)))
  98. ;; Representation of an upstream input change.
  99. (define-record-type* <upstream-input-change>
  100. upstream-input-change make-upstream-input-change
  101. upstream-input-change?
  102. (name upstream-input-change-name) ;string
  103. (type upstream-input-change-type) ;symbol: regular | native | propagated
  104. (action upstream-input-change-action)) ;symbol: add | remove
  105. (define (changed-inputs package package-sexp)
  106. "Return a list of input changes for PACKAGE based on the newly imported
  107. S-expression PACKAGE-SEXP."
  108. (match package-sexp
  109. ((and expr ('package fields ...))
  110. (let* ((input->name (match-lambda ((name pkg . out) name)))
  111. (new-regular
  112. (match expr
  113. ((path *** ('inputs
  114. ('quasiquote ((label ('unquote sym)) ...)))) label)
  115. (_ '())))
  116. (new-native
  117. (match expr
  118. ((path *** ('native-inputs
  119. ('quasiquote ((label ('unquote sym)) ...)))) label)
  120. (_ '())))
  121. (new-propagated
  122. (match expr
  123. ((path *** ('propagated-inputs
  124. ('quasiquote ((label ('unquote sym)) ...)))) label)
  125. (_ '())))
  126. (current-regular
  127. (map input->name (package-inputs package)))
  128. (current-native
  129. (map input->name (package-native-inputs package)))
  130. (current-propagated
  131. (map input->name (package-propagated-inputs package))))
  132. (append-map
  133. (match-lambda
  134. ((action type names)
  135. (map (lambda (name)
  136. (upstream-input-change
  137. (name name)
  138. (type type)
  139. (action action)))
  140. names)))
  141. `((add regular
  142. ,(lset-difference equal?
  143. new-regular current-regular))
  144. (remove regular
  145. ,(lset-difference equal?
  146. current-regular new-regular))
  147. (add native
  148. ,(lset-difference equal?
  149. new-native current-native))
  150. (remove native
  151. ,(lset-difference equal?
  152. current-native new-native))
  153. (add propagated
  154. ,(lset-difference equal?
  155. new-propagated current-propagated))
  156. (remove propagated
  157. ,(lset-difference equal?
  158. current-propagated new-propagated))))))
  159. (_ '())))
  160. (define* (url-predicate matching-url?)
  161. "Return a predicate that returns true when passed a package whose source is
  162. an <origin> with the URL-FETCH method, and one of its URLs passes
  163. MATCHING-URL?."
  164. (lambda (package)
  165. (match (package-source package)
  166. ((? origin? origin)
  167. (and (eq? (origin-method origin) url-fetch)
  168. (match (origin-uri origin)
  169. ((? string? url)
  170. (matching-url? url))
  171. (((? string? urls) ...)
  172. (any matching-url? urls))
  173. (_
  174. #f))))
  175. (_ #f))))
  176. (define (url-prefix-predicate prefix)
  177. "Return a predicate that returns true when passed a package where one of its
  178. source URLs starts with PREFIX."
  179. (url-predicate (cut string-prefix? prefix <>)))
  180. (define (upstream-source-archive-types release)
  181. "Return the available types of archives for RELEASE---a list of strings such
  182. as \"gz\" or \"xz\"."
  183. (map file-extension (upstream-source-urls release)))
  184. (define (coalesce-sources sources)
  185. "Coalesce the elements of SOURCES, a list of <upstream-source>, that
  186. correspond to the same version."
  187. (define (same-version? r1 r2)
  188. (string=? (upstream-source-version r1) (upstream-source-version r2)))
  189. (define (release>? r1 r2)
  190. (version>? (upstream-source-version r1) (upstream-source-version r2)))
  191. (fold (lambda (release result)
  192. (match result
  193. ((head . tail)
  194. (if (same-version? release head)
  195. (cons (upstream-source
  196. (inherit release)
  197. (urls (append (upstream-source-urls release)
  198. (upstream-source-urls head)))
  199. (signature-urls
  200. (let ((one (upstream-source-signature-urls release))
  201. (two (upstream-source-signature-urls head)))
  202. (and one two (append one two)))))
  203. tail)
  204. (cons release result)))
  205. (()
  206. (list release))))
  207. '()
  208. (sort sources release>?)))
  209. ;;;
  210. ;;; Auto-update.
  211. ;;;
  212. (define-record-type* <upstream-updater>
  213. upstream-updater make-upstream-updater
  214. upstream-updater?
  215. (name upstream-updater-name)
  216. (description upstream-updater-description)
  217. (pred upstream-updater-predicate)
  218. (latest upstream-updater-latest))
  219. (define (importer-modules)
  220. "Return the list of importer modules."
  221. (cons (resolve-interface '(guix gnu-maintenance))
  222. (all-modules (map (lambda (entry)
  223. `(,entry . "guix/import"))
  224. %load-path)
  225. #:warn warn-about-load-error)))
  226. (define %updaters
  227. ;; The list of publically-known updaters.
  228. (delay (fold-module-public-variables (lambda (obj result)
  229. (if (upstream-updater? obj)
  230. (cons obj result)
  231. result))
  232. '()
  233. (importer-modules))))
  234. ;; Tests need to mock this variable so mark it as "non-declarative".
  235. (set! %updaters %updaters)
  236. (define* (lookup-updater package
  237. #:optional (updaters (force %updaters)))
  238. "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
  239. them matches."
  240. (find (match-lambda
  241. (($ <upstream-updater> name description pred latest)
  242. (pred package)))
  243. updaters))
  244. (define* (package-latest-release package
  245. #:optional
  246. (updaters (force %updaters)))
  247. "Return an upstream source to update PACKAGE, a <package> object, or #f if
  248. none of UPDATERS matches PACKAGE. When several updaters match PACKAGE, try
  249. them until one of them returns an upstream source. It is the caller's
  250. responsibility to ensure that the returned source is newer than the current
  251. one."
  252. (any (match-lambda
  253. (($ <upstream-updater> name description pred latest)
  254. (and (pred package)
  255. (latest package))))
  256. updaters))
  257. (define* (package-latest-release* package
  258. #:optional
  259. (updaters (force %updaters)))
  260. "Like 'package-latest-release', but ensure that the return source is newer
  261. than that of PACKAGE."
  262. (match (package-latest-release package updaters)
  263. ((and source ($ <upstream-source> name version))
  264. (and (version>? version (package-version package))
  265. source))
  266. (_
  267. #f)))
  268. (define (uncompressed-tarball name tarball)
  269. "Return a derivation that decompresses TARBALL."
  270. (define (ref package)
  271. (module-ref (resolve-interface '(gnu packages compression))
  272. package))
  273. (define compressor
  274. (cond ((or (string-suffix? ".gz" tarball)
  275. (string-suffix? ".tgz" tarball))
  276. (file-append (ref 'gzip) "/bin/gzip"))
  277. ((string-suffix? ".bz2" tarball)
  278. (file-append (ref 'bzip2) "/bin/bzip2"))
  279. ((string-suffix? ".xz" tarball)
  280. (file-append (ref 'xz) "/bin/xz"))
  281. ((string-suffix? ".lz" tarball)
  282. (file-append (ref 'lzip) "/bin/lzip"))
  283. (else
  284. (error "unknown archive type" tarball))))
  285. (gexp->derivation (file-sans-extension name)
  286. #~(begin
  287. (copy-file #+tarball #+name)
  288. (and (zero? (system* #+compressor "-d" #+name))
  289. (copy-file #+(file-sans-extension name)
  290. #$output)))))
  291. (define* (download-tarball store url signature-url
  292. #:key (key-download 'interactive))
  293. "Download the tarball at URL to the store; check its OpenPGP signature at
  294. SIGNATURE-URL, unless SIGNATURE-URL is false. On success, return the tarball
  295. file name; return #f on failure (network failure or authentication failure).
  296. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  297. values: 'interactive' (default), 'always', and 'never'."
  298. (let ((tarball (download-to-store store url)))
  299. (if (not signature-url)
  300. tarball
  301. (let* ((sig (download-to-store store signature-url))
  302. ;; Sometimes we get a signature over the uncompressed tarball.
  303. ;; In that case, decompress the tarball in the store so that we
  304. ;; can check the signature.
  305. (data (if (string-prefix? (basename url)
  306. (basename signature-url))
  307. tarball
  308. (run-with-store store
  309. (mlet %store-monad ((drv (uncompressed-tarball
  310. (basename url) tarball)))
  311. (mbegin %store-monad
  312. (built-derivations (list drv))
  313. (return (derivation->output-path drv))))))))
  314. (let-values (((status data)
  315. (if sig
  316. (gnupg-verify* sig data
  317. #:key-download key-download)
  318. (values 'missing-signature data))))
  319. (match status
  320. ('valid-signature
  321. tarball)
  322. ('missing-signature
  323. (warning (G_ "failed to download detached signature from ~a~%")
  324. signature-url)
  325. #f)
  326. ('invalid-signature
  327. (warning (G_ "signature verification failed for '~a' (key: ~a)~%")
  328. url data)
  329. #f)
  330. ('missing-key
  331. (warning (G_ "missing public key ~a for '~a'~%")
  332. data url)
  333. #f)))))))
  334. (define (upstream-source-compiler/url-fetch source system)
  335. "Lower SOURCE, an <upstream-source> pointing to a tarball, as a
  336. fixed-output derivation that would fetch it, and verify its authenticity."
  337. (mlet* %store-monad ((url -> (first (upstream-source-urls source)))
  338. (signature
  339. -> (and=> (upstream-source-signature-urls source)
  340. first))
  341. (tarball ((store-lift download-tarball) url signature)))
  342. (unless tarball
  343. (raise (formatted-message (G_ "failed to fetch source from '~a'")
  344. url)))
  345. ;; Instead of returning TARBALL, return a fixed-output derivation that
  346. ;; would be able to re-download it. In practice, since TARBALL is already
  347. ;; in the store, no extra download will happen, but having the derivation
  348. ;; in store improves provenance tracking.
  349. (let ((hash (call-with-input-file tarball port-sha256)))
  350. (url-fetch url 'sha256 hash (store-path-package-name tarball)
  351. #:system system))))
  352. (define (upstream-source-compiler/git-fetch source system)
  353. "Lower SOURCE, an <upstream-source> using git, as a fixed-output
  354. derivation that would fetch it."
  355. (mlet* %store-monad ((reference -> (upstream-source-urls source))
  356. (checkout
  357. (lower-object
  358. (git-reference->git-checkout reference)
  359. system)))
  360. ;; Like in 'upstream-source-compiler/url-fetch', return a fixed-output
  361. ;; derivation instead of CHECKOUT.
  362. (git-fetch reference 'sha256
  363. (file-hash* checkout #:recursive? #true #:select? (const #true))
  364. (git-file-name (upstream-source-package source)
  365. (upstream-source-version source))
  366. #:system system)))
  367. (define-gexp-compiler (upstream-source-compiler (source <upstream-source>)
  368. system target)
  369. "Download SOURCE, lower it as a fixed-output derivation that would fetch it,
  370. and verify its authenticity if possible."
  371. (if (git-reference? (upstream-source-urls source))
  372. (upstream-source-compiler/git-fetch source system)
  373. (upstream-source-compiler/url-fetch source system)))
  374. (define (find2 pred lst1 lst2)
  375. "Like 'find', but operate on items from both LST1 and LST2. Return two
  376. values: the item from LST1 and the item from LST2 that match PRED."
  377. (let loop ((lst1 lst1) (lst2 lst2))
  378. (match lst1
  379. ((head1 . tail1)
  380. (match lst2
  381. ((head2 . tail2)
  382. (if (pred head1 head2)
  383. (values head1 head2)
  384. (loop tail1 tail2)))))
  385. (()
  386. (values #f #f)))))
  387. (define* (package-update/url-fetch store package source
  388. #:key key-download)
  389. "Return the version, tarball, and SOURCE, to update PACKAGE to
  390. SOURCE, an <upstream-source>."
  391. (match source
  392. (($ <upstream-source> _ version urls signature-urls)
  393. (let*-values (((archive-type)
  394. (match (and=> (package-source package) origin-uri)
  395. ((? string? uri)
  396. (let ((type (or (file-extension (basename uri)) "")))
  397. ;; Sometimes we have URLs such as
  398. ;; "https://github.com/…/tarball/v0.1", in which case
  399. ;; we must not consider "1" as the extension.
  400. (and (or (string-contains type "z")
  401. (string=? type "tar"))
  402. type)))
  403. (_
  404. "gz")))
  405. ((url signature-url)
  406. ;; Try to find a URL that matches ARCHIVE-TYPE.
  407. (find2 (lambda (url sig-url)
  408. ;; Some URIs lack a file extension, like
  409. ;; 'https://crates.io/???/0.1/download'. In that
  410. ;; case, pick the first URL.
  411. (or (not archive-type)
  412. (string-suffix? archive-type url)))
  413. urls
  414. (or signature-urls (circular-list #f)))))
  415. ;; If none of URLS matches ARCHIVE-TYPE, then URL is #f; in that case,
  416. ;; pick up the first element of URLS.
  417. (let ((tarball (download-tarball store
  418. (or url (first urls))
  419. (and (pair? signature-urls)
  420. (or signature-url
  421. (first signature-urls)))
  422. #:key-download key-download)))
  423. (values version tarball source))))))
  424. (define* (package-update/git-fetch store package source #:key key-download)
  425. "Return the version, checkout, and SOURCE, to update PACKAGE to
  426. SOURCE, an <upstream-source>."
  427. ;; TODO: it would be nice to authenticate commits, e.g. with
  428. ;; "guix git authenticate" or a list of permitted signing keys.
  429. (define ref (upstream-source-urls source)) ; a <git-reference>
  430. (values (upstream-source-version source)
  431. (latest-repository-commit
  432. store
  433. (git-reference-url ref)
  434. #:ref `(tag-or-commit . ,(git-reference-commit ref))
  435. #:recursive? (git-reference-recursive? ref))
  436. source))
  437. (define %method-updates
  438. ;; Mapping of origin methods to source update procedures.
  439. `((,url-fetch . ,package-update/url-fetch)
  440. (,git-fetch . ,package-update/git-fetch)))
  441. (define* (package-update store package
  442. #:optional (updaters (force %updaters))
  443. #:key (key-download 'interactive))
  444. "Return the new version, the file name of the new version tarball, and input
  445. changes for PACKAGE; return #f (three values) when PACKAGE is up-to-date.
  446. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  447. values: 'always', 'never', and 'interactive' (default)."
  448. (match (package-latest-release* package updaters)
  449. ((? upstream-source? source)
  450. (let ((method (match (package-source package)
  451. ((? origin? origin)
  452. (origin-method origin))
  453. (_
  454. #f))))
  455. (match (assq method %method-updates)
  456. (#f
  457. (raise (make-compound-condition
  458. (formatted-message (G_ "cannot download for \
  459. this method: ~s")
  460. method)
  461. (condition
  462. (&error-location
  463. (location (package-location package)))))))
  464. ((_ . update)
  465. (update store package source
  466. #:key-download key-download)))))
  467. (#f
  468. (values #f #f #f))))
  469. (define* (update-package-source package source hash)
  470. "Modify the source file that defines PACKAGE to refer to SOURCE, an
  471. <upstream-source> whose tarball has SHA256 HASH (a bytevector). Return the
  472. new version string if an update was made, and #f otherwise."
  473. (define (update-expression expr replacements)
  474. ;; Apply REPLACEMENTS to package expression EXPR, a string. REPLACEMENTS
  475. ;; must be a list of replacement pairs, either bytevectors or strings.
  476. (fold (lambda (replacement str)
  477. (match replacement
  478. (((? bytevector? old-bv) . (? bytevector? new-bv))
  479. (string-replace-substring
  480. str
  481. (bytevector->nix-base32-string old-bv)
  482. (bytevector->nix-base32-string new-bv)))
  483. ((old . new)
  484. (string-replace-substring str old new))))
  485. expr
  486. replacements))
  487. (let ((name (package-name package))
  488. (version (upstream-source-version source))
  489. (version-loc (package-field-location package 'version)))
  490. (if version-loc
  491. (let* ((loc (package-location package))
  492. (old-version (package-version package))
  493. (old-hash (content-hash-value
  494. (origin-hash (package-source package))))
  495. (old-url (match (origin-uri (package-source package))
  496. ((? string? url) url)
  497. ((? git-reference? ref)
  498. (git-reference-url ref))
  499. (_ #f)))
  500. (new-url (match (upstream-source-urls source)
  501. ((first _ ...) first)
  502. ((? git-reference? ref)
  503. (git-reference-url ref))
  504. (_ #f)))
  505. (old-commit (match (origin-uri (package-source package))
  506. ((? git-reference? ref)
  507. (git-reference-commit ref))
  508. (_ #f)))
  509. (new-commit (match (upstream-source-urls source)
  510. ((? git-reference? ref)
  511. (git-reference-commit ref))
  512. (_ #f)))
  513. (file (and=> (location-file loc)
  514. (cut search-path %load-path <>))))
  515. (if file
  516. ;; Be sure to use absolute filename. Replace the URL directory
  517. ;; when OLD-URL is available; this is useful notably for
  518. ;; mirror://cpan/ URLs where the directory may change as a
  519. ;; function of the person who uploads the package. Note that
  520. ;; package definitions usually concatenate fragments of the URL,
  521. ;; which is why we only attempt to replace a subset of the URL.
  522. (let ((properties (assq-set! (location->source-properties loc)
  523. 'filename file))
  524. (replacements `((,old-version . ,version)
  525. (,old-hash . ,hash)
  526. ,@(if (and old-commit new-commit)
  527. `((,old-commit . ,new-commit))
  528. '())
  529. ,@(if (and old-url new-url)
  530. `((,(dirname old-url) .
  531. ,(dirname new-url)))
  532. '()))))
  533. (and (edit-expression properties
  534. (cut update-expression <> replacements))
  535. version))
  536. (begin
  537. (warning (G_ "~a: could not locate source file")
  538. (location-file loc))
  539. #f)))
  540. (warning (package-location package)
  541. (G_ "~a: no `version' field in source; skipping~%")
  542. name))))
  543. ;;; upstream.scm ends here