upstream.scm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2010-2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2015 Alex Kost <alezost@gmail.com>
  4. ;;; Copyright © 2019, 2022 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. ((path *** ('inputs
  116. ('list sym ...))) (map symbol->string sym))
  117. (_ '())))
  118. (new-native
  119. (match expr
  120. ((path *** ('native-inputs
  121. ('quasiquote ((label ('unquote sym)) ...)))) label)
  122. ((path *** ('native-inputs
  123. ('list sym ...))) (map symbol->string sym))
  124. (_ '())))
  125. (new-propagated
  126. (match expr
  127. ((path *** ('propagated-inputs
  128. ('quasiquote ((label ('unquote sym)) ...)))) label)
  129. ((path *** ('propagated-inputs
  130. ('list sym ...))) (map symbol->string sym))
  131. (_ '())))
  132. (current-regular
  133. (map input->name (package-inputs package)))
  134. (current-native
  135. (map input->name (package-native-inputs package)))
  136. (current-propagated
  137. (map input->name (package-propagated-inputs package))))
  138. (append-map
  139. (match-lambda
  140. ((action type names)
  141. (map (lambda (name)
  142. (upstream-input-change
  143. (name name)
  144. (type type)
  145. (action action)))
  146. names)))
  147. `((add regular
  148. ,(lset-difference equal?
  149. new-regular current-regular))
  150. (remove regular
  151. ,(lset-difference equal?
  152. current-regular new-regular))
  153. (add native
  154. ,(lset-difference equal?
  155. new-native current-native))
  156. (remove native
  157. ,(lset-difference equal?
  158. current-native new-native))
  159. (add propagated
  160. ,(lset-difference equal?
  161. new-propagated current-propagated))
  162. (remove propagated
  163. ,(lset-difference equal?
  164. current-propagated new-propagated))))))
  165. (_ '())))
  166. (define* (url-predicate matching-url?)
  167. "Return a predicate that returns true when passed a package whose source is
  168. an <origin> with the URL-FETCH method, and one of its URLs passes
  169. MATCHING-URL?."
  170. (lambda (package)
  171. (match (package-source package)
  172. ((? origin? origin)
  173. (and (eq? (origin-method origin) url-fetch)
  174. (match (origin-uri origin)
  175. ((? string? url)
  176. (matching-url? url))
  177. (((? string? urls) ...)
  178. (any matching-url? urls))
  179. (_
  180. #f))))
  181. (_ #f))))
  182. (define (url-prefix-predicate prefix)
  183. "Return a predicate that returns true when passed a package where one of its
  184. source URLs starts with PREFIX."
  185. (url-predicate (cut string-prefix? prefix <>)))
  186. (define (upstream-source-archive-types release)
  187. "Return the available types of archives for RELEASE---a list of strings such
  188. as \"gz\" or \"xz\"."
  189. (map file-extension (upstream-source-urls release)))
  190. (define (coalesce-sources sources)
  191. "Coalesce the elements of SOURCES, a list of <upstream-source>, that
  192. correspond to the same version."
  193. (define (same-version? r1 r2)
  194. (string=? (upstream-source-version r1) (upstream-source-version r2)))
  195. (define (release>? r1 r2)
  196. (version>? (upstream-source-version r1) (upstream-source-version r2)))
  197. (fold (lambda (release result)
  198. (match result
  199. ((head . tail)
  200. (if (same-version? release head)
  201. (cons (upstream-source
  202. (inherit release)
  203. (urls (append (upstream-source-urls release)
  204. (upstream-source-urls head)))
  205. (signature-urls
  206. (let ((one (upstream-source-signature-urls release))
  207. (two (upstream-source-signature-urls head)))
  208. (and one two (append one two)))))
  209. tail)
  210. (cons release result)))
  211. (()
  212. (list release))))
  213. '()
  214. (sort sources release>?)))
  215. ;;;
  216. ;;; Auto-update.
  217. ;;;
  218. (define-record-type* <upstream-updater>
  219. upstream-updater make-upstream-updater
  220. upstream-updater?
  221. (name upstream-updater-name)
  222. (description upstream-updater-description)
  223. (pred upstream-updater-predicate)
  224. (latest upstream-updater-latest))
  225. (define (importer-modules)
  226. "Return the list of importer modules."
  227. (cons (resolve-interface '(guix gnu-maintenance))
  228. (all-modules (map (lambda (entry)
  229. `(,entry . "guix/import"))
  230. %load-path)
  231. #:warn warn-about-load-error)))
  232. (define %updaters
  233. ;; The list of publically-known updaters, alphabetically sorted.
  234. (delay
  235. (sort (fold-module-public-variables (lambda (obj result)
  236. (if (upstream-updater? obj)
  237. (cons obj result)
  238. result))
  239. '()
  240. (importer-modules))
  241. (lambda (updater1 updater2)
  242. (string<? (symbol->string (upstream-updater-name updater1))
  243. (symbol->string (upstream-updater-name updater2)))))))
  244. ;; Tests need to mock this variable so mark it as "non-declarative".
  245. (set! %updaters %updaters)
  246. (define* (lookup-updater package
  247. #:optional (updaters (force %updaters)))
  248. "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
  249. them matches."
  250. (find (match-lambda
  251. (($ <upstream-updater> name description pred latest)
  252. (pred package)))
  253. updaters))
  254. (define* (package-latest-release package
  255. #:optional
  256. (updaters (force %updaters)))
  257. "Return an upstream source to update PACKAGE, a <package> object, or #f if
  258. none of UPDATERS matches PACKAGE. When several updaters match PACKAGE, try
  259. them until one of them returns an upstream source. It is the caller's
  260. responsibility to ensure that the returned source is newer than the current
  261. one."
  262. (any (match-lambda
  263. (($ <upstream-updater> name description pred latest)
  264. (and (pred package)
  265. (latest package))))
  266. updaters))
  267. (define* (package-latest-release* package
  268. #:optional
  269. (updaters (force %updaters)))
  270. "Like 'package-latest-release', but ensure that the return source is newer
  271. than that of PACKAGE."
  272. (match (package-latest-release package updaters)
  273. ((and source ($ <upstream-source> name version))
  274. (and (version>? version (package-version package))
  275. source))
  276. (_
  277. #f)))
  278. (define (uncompressed-tarball name tarball)
  279. "Return a derivation that decompresses TARBALL."
  280. (define (ref package)
  281. (module-ref (resolve-interface '(gnu packages compression))
  282. package))
  283. (define compressor
  284. (cond ((or (string-suffix? ".gz" tarball)
  285. (string-suffix? ".tgz" tarball))
  286. (file-append (ref 'gzip) "/bin/gzip"))
  287. ((string-suffix? ".bz2" tarball)
  288. (file-append (ref 'bzip2) "/bin/bzip2"))
  289. ((string-suffix? ".xz" tarball)
  290. (file-append (ref 'xz) "/bin/xz"))
  291. ((string-suffix? ".lz" tarball)
  292. (file-append (ref 'lzip) "/bin/lzip"))
  293. (else
  294. (error "unknown archive type" tarball))))
  295. (gexp->derivation (file-sans-extension name)
  296. #~(begin
  297. (copy-file #+tarball #+name)
  298. (and (zero? (system* #+compressor "-d" #+name))
  299. (copy-file #+(file-sans-extension name)
  300. #$output)))))
  301. (define* (download-tarball store url signature-url
  302. #:key (key-download 'interactive))
  303. "Download the tarball at URL to the store; check its OpenPGP signature at
  304. SIGNATURE-URL, unless SIGNATURE-URL is false. On success, return the tarball
  305. file name; return #f on failure (network failure or authentication failure).
  306. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  307. values: 'interactive' (default), 'always', and 'never'."
  308. (let ((tarball (download-to-store store url)))
  309. (if (not signature-url)
  310. tarball
  311. (let* ((sig (download-to-store store signature-url))
  312. ;; Sometimes we get a signature over the uncompressed tarball.
  313. ;; In that case, decompress the tarball in the store so that we
  314. ;; can check the signature.
  315. (data (if (string-prefix? (basename url)
  316. (basename signature-url))
  317. tarball
  318. (run-with-store store
  319. (mlet %store-monad ((drv (uncompressed-tarball
  320. (basename url) tarball)))
  321. (mbegin %store-monad
  322. (built-derivations (list drv))
  323. (return (derivation->output-path drv))))))))
  324. (let-values (((status data)
  325. (if sig
  326. (gnupg-verify* sig data
  327. #:key-download key-download)
  328. (values 'missing-signature data))))
  329. (match status
  330. ('valid-signature
  331. tarball)
  332. ('missing-signature
  333. (warning (G_ "failed to download detached signature from ~a~%")
  334. signature-url)
  335. #f)
  336. ('invalid-signature
  337. (warning (G_ "signature verification failed for '~a' (key: ~a)~%")
  338. url data)
  339. #f)
  340. ('missing-key
  341. (warning (G_ "missing public key ~a for '~a'~%")
  342. data url)
  343. #f)))))))
  344. (define (upstream-source-compiler/url-fetch source system)
  345. "Lower SOURCE, an <upstream-source> pointing to a tarball, as a
  346. fixed-output derivation that would fetch it, and verify its authenticity."
  347. (mlet* %store-monad ((url -> (first (upstream-source-urls source)))
  348. (signature
  349. -> (and=> (upstream-source-signature-urls source)
  350. first))
  351. (tarball ((store-lift download-tarball) url signature)))
  352. (unless tarball
  353. (raise (formatted-message (G_ "failed to fetch source from '~a'")
  354. url)))
  355. ;; Instead of returning TARBALL, return a fixed-output derivation that
  356. ;; would be able to re-download it. In practice, since TARBALL is already
  357. ;; in the store, no extra download will happen, but having the derivation
  358. ;; in store improves provenance tracking.
  359. (let ((hash (call-with-input-file tarball port-sha256)))
  360. (url-fetch url 'sha256 hash (store-path-package-name tarball)
  361. #:system system))))
  362. (define (upstream-source-compiler/git-fetch source system)
  363. "Lower SOURCE, an <upstream-source> using git, as a fixed-output
  364. derivation that would fetch it."
  365. (mlet* %store-monad ((reference -> (upstream-source-urls source))
  366. (checkout
  367. (lower-object
  368. (git-reference->git-checkout reference)
  369. system)))
  370. ;; Like in 'upstream-source-compiler/url-fetch', return a fixed-output
  371. ;; derivation instead of CHECKOUT.
  372. (git-fetch reference 'sha256
  373. (file-hash* checkout #:recursive? #true #:select? (const #true))
  374. (git-file-name (upstream-source-package source)
  375. (upstream-source-version source))
  376. #:system system)))
  377. (define-gexp-compiler (upstream-source-compiler (source <upstream-source>)
  378. system target)
  379. "Download SOURCE, lower it as a fixed-output derivation that would fetch it,
  380. and verify its authenticity if possible."
  381. (if (git-reference? (upstream-source-urls source))
  382. (upstream-source-compiler/git-fetch source system)
  383. (upstream-source-compiler/url-fetch source system)))
  384. (define (find2 pred lst1 lst2)
  385. "Like 'find', but operate on items from both LST1 and LST2. Return two
  386. values: the item from LST1 and the item from LST2 that match PRED."
  387. (let loop ((lst1 lst1) (lst2 lst2))
  388. (match lst1
  389. ((head1 . tail1)
  390. (match lst2
  391. ((head2 . tail2)
  392. (if (pred head1 head2)
  393. (values head1 head2)
  394. (loop tail1 tail2)))))
  395. (()
  396. (values #f #f)))))
  397. (define* (package-update/url-fetch store package source
  398. #:key key-download)
  399. "Return the version, tarball, and SOURCE, to update PACKAGE to
  400. SOURCE, an <upstream-source>."
  401. (match source
  402. (($ <upstream-source> _ version urls signature-urls)
  403. (let*-values (((archive-type)
  404. (match (and=> (package-source package) origin-uri)
  405. ((? string? uri)
  406. (let ((type (or (file-extension (basename uri)) "")))
  407. ;; Sometimes we have URLs such as
  408. ;; "https://github.com/…/tarball/v0.1", in which case
  409. ;; we must not consider "1" as the extension.
  410. (and (or (string-contains type "z")
  411. (string=? type "tar"))
  412. type)))
  413. (_
  414. "gz")))
  415. ((url signature-url)
  416. ;; Try to find a URL that matches ARCHIVE-TYPE.
  417. (find2 (lambda (url sig-url)
  418. ;; Some URIs lack a file extension, like
  419. ;; 'https://crates.io/???/0.1/download'. In that
  420. ;; case, pick the first URL.
  421. (or (not archive-type)
  422. (string-suffix? archive-type url)))
  423. urls
  424. (or signature-urls (circular-list #f)))))
  425. ;; If none of URLS matches ARCHIVE-TYPE, then URL is #f; in that case,
  426. ;; pick up the first element of URLS.
  427. (let ((tarball (download-tarball store
  428. (or url (first urls))
  429. (and (pair? signature-urls)
  430. (or signature-url
  431. (first signature-urls)))
  432. #:key-download key-download)))
  433. (values version tarball source))))))
  434. (define* (package-update/git-fetch store package source #:key key-download)
  435. "Return the version, checkout, and SOURCE, to update PACKAGE to
  436. SOURCE, an <upstream-source>."
  437. ;; TODO: it would be nice to authenticate commits, e.g. with
  438. ;; "guix git authenticate" or a list of permitted signing keys.
  439. (define ref (upstream-source-urls source)) ; a <git-reference>
  440. (values (upstream-source-version source)
  441. (latest-repository-commit
  442. store
  443. (git-reference-url ref)
  444. #:ref `(tag-or-commit . ,(git-reference-commit ref))
  445. #:recursive? (git-reference-recursive? ref))
  446. source))
  447. (define %method-updates
  448. ;; Mapping of origin methods to source update procedures.
  449. `((,url-fetch . ,package-update/url-fetch)
  450. (,git-fetch . ,package-update/git-fetch)))
  451. (define* (package-update store package
  452. #:optional (updaters (force %updaters))
  453. #:key (key-download 'interactive))
  454. "Return the new version, the file name of the new version tarball, and input
  455. changes for PACKAGE; return #f (three values) when PACKAGE is up-to-date;
  456. raise an error when the updater could not determine available releases.
  457. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  458. values: 'always', 'never', and 'interactive' (default)."
  459. (match (package-latest-release package updaters)
  460. ((? upstream-source? source)
  461. (if (version>? (upstream-source-version source)
  462. (package-version package))
  463. (let ((method (match (package-source package)
  464. ((? origin? origin)
  465. (origin-method origin))
  466. (_
  467. #f))))
  468. (match (assq method %method-updates)
  469. (#f
  470. (raise (make-compound-condition
  471. (formatted-message (G_ "cannot download for \
  472. this method: ~s")
  473. method)
  474. (condition
  475. (&error-location
  476. (location (package-location package)))))))
  477. ((_ . update)
  478. (update store package source
  479. #:key-download key-download))))
  480. (values #f #f #f)))
  481. (#f
  482. ;; Warn rather than abort so that other updates can still take place.
  483. (warning (G_ "updater failed to determine available releases for ~a~%")
  484. (package-name package))
  485. (values #f #f #f))))
  486. (define* (update-package-source package source hash)
  487. "Modify the source file that defines PACKAGE to refer to SOURCE, an
  488. <upstream-source> whose tarball has SHA256 HASH (a bytevector). Return the
  489. new version string if an update was made, and #f otherwise."
  490. (define (update-expression expr replacements)
  491. ;; Apply REPLACEMENTS to package expression EXPR, a string. REPLACEMENTS
  492. ;; must be a list of replacement pairs, either bytevectors or strings.
  493. (fold (lambda (replacement str)
  494. (match replacement
  495. (((? bytevector? old-bv) . (? bytevector? new-bv))
  496. (string-replace-substring
  497. str
  498. (bytevector->nix-base32-string old-bv)
  499. (bytevector->nix-base32-string new-bv)))
  500. ((old . new)
  501. (string-replace-substring str old new))))
  502. expr
  503. replacements))
  504. (let ((name (package-name package))
  505. (version (upstream-source-version source))
  506. (version-loc (package-field-location package 'version)))
  507. (if version-loc
  508. (let* ((loc (package-location package))
  509. (old-version (package-version package))
  510. (old-hash (content-hash-value
  511. (origin-hash (package-source package))))
  512. (old-url (match (origin-uri (package-source package))
  513. ((? string? url) url)
  514. ((? git-reference? ref)
  515. (git-reference-url ref))
  516. (_ #f)))
  517. (new-url (match (upstream-source-urls source)
  518. ((first _ ...) first)
  519. ((? git-reference? ref)
  520. (git-reference-url ref))
  521. (_ #f)))
  522. (old-commit (match (origin-uri (package-source package))
  523. ((? git-reference? ref)
  524. (git-reference-commit ref))
  525. (_ #f)))
  526. (new-commit (match (upstream-source-urls source)
  527. ((? git-reference? ref)
  528. (git-reference-commit ref))
  529. (_ #f)))
  530. (file (and=> (location-file loc)
  531. (cut search-path %load-path <>))))
  532. (if file
  533. ;; Be sure to use absolute filename. Replace the URL directory
  534. ;; when OLD-URL is available; this is useful notably for
  535. ;; mirror://cpan/ URLs where the directory may change as a
  536. ;; function of the person who uploads the package. Note that
  537. ;; package definitions usually concatenate fragments of the URL,
  538. ;; which is why we only attempt to replace a subset of the URL.
  539. (let ((properties (assq-set! (location->source-properties loc)
  540. 'filename file))
  541. (replacements `((,old-version . ,version)
  542. (,old-hash . ,hash)
  543. ,@(if (and old-commit new-commit)
  544. `((,old-commit . ,new-commit))
  545. '())
  546. ,@(if (and old-url new-url)
  547. `((,(dirname old-url) .
  548. ,(dirname new-url)))
  549. '()))))
  550. (and (edit-expression properties
  551. (cut update-expression <> replacements))
  552. version))
  553. (begin
  554. (warning (G_ "~a: could not locate source file")
  555. (location-file loc))
  556. #f)))
  557. (warning (package-location package)
  558. (G_ "~a: no `version' field in source; skipping~%")
  559. name))))
  560. ;;; upstream.scm ends here