upstream.scm 21 KB

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