upstream.scm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 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)
  34. #:select (built-derivations derivation->output-path))
  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. (define (lookup-updater package updaters)
  230. "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
  231. them matches."
  232. (find (match-lambda
  233. (($ <upstream-updater> name description pred latest)
  234. (pred package)))
  235. updaters))
  236. (define (package-latest-release package updaters)
  237. "Return an upstream source to update PACKAGE, a <package> object, or #f if
  238. none of UPDATERS matches PACKAGE. It is the caller's responsibility to ensure
  239. that the returned source is newer than the current one."
  240. (match (lookup-updater package updaters)
  241. ((? upstream-updater? updater)
  242. ((upstream-updater-latest updater) package))
  243. (_ #f)))
  244. (define (package-latest-release* package updaters)
  245. "Like 'package-latest-release', but ensure that the return source is newer
  246. than that of PACKAGE."
  247. (match (package-latest-release package updaters)
  248. ((and source ($ <upstream-source> name version))
  249. (and (version>? version (package-version package))
  250. source))
  251. (_
  252. #f)))
  253. (define (uncompressed-tarball name tarball)
  254. "Return a derivation that decompresses TARBALL."
  255. (define (ref package)
  256. (module-ref (resolve-interface '(gnu packages compression))
  257. package))
  258. (define compressor
  259. (cond ((or (string-suffix? ".gz" tarball)
  260. (string-suffix? ".tgz" tarball))
  261. (file-append (ref 'gzip) "/bin/gzip"))
  262. ((string-suffix? ".bz2" tarball)
  263. (file-append (ref 'bzip2) "/bin/bzip2"))
  264. ((string-suffix? ".xz" tarball)
  265. (file-append (ref 'xz) "/bin/xz"))
  266. ((string-suffix? ".lz" tarball)
  267. (file-append (ref 'lzip) "/bin/lzip"))
  268. (else
  269. (error "unknown archive type" tarball))))
  270. (gexp->derivation (file-sans-extension name)
  271. #~(begin
  272. (copy-file #+tarball #+name)
  273. (and (zero? (system* #+compressor "-d" #+name))
  274. (copy-file #+(file-sans-extension name)
  275. #$output)))))
  276. (define* (download-tarball store url signature-url
  277. #:key (key-download 'interactive))
  278. "Download the tarball at URL to the store; check its OpenPGP signature at
  279. SIGNATURE-URL, unless SIGNATURE-URL is false. On success, return the tarball
  280. file name; return #f on failure (network failure or authentication failure).
  281. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  282. values: 'interactive' (default), 'always', and 'never'."
  283. (let ((tarball (download-to-store store url)))
  284. (if (not signature-url)
  285. tarball
  286. (let* ((sig (download-to-store store signature-url))
  287. ;; Sometimes we get a signature over the uncompressed tarball.
  288. ;; In that case, decompress the tarball in the store so that we
  289. ;; can check the signature.
  290. (data (if (string-prefix? (basename url)
  291. (basename signature-url))
  292. tarball
  293. (run-with-store store
  294. (mlet %store-monad ((drv (uncompressed-tarball
  295. (basename url) tarball)))
  296. (mbegin %store-monad
  297. (built-derivations (list drv))
  298. (return (derivation->output-path drv))))))))
  299. (let-values (((status data)
  300. (if sig
  301. (gnupg-verify* sig data
  302. #:key-download key-download)
  303. (values 'missing-signature data))))
  304. (match status
  305. ('valid-signature
  306. tarball)
  307. ('missing-signature
  308. (warning (G_ "failed to download detached signature from ~a~%")
  309. signature-url)
  310. #f)
  311. ('invalid-signature
  312. (warning (G_ "signature verification failed for '~a' (key: ~a)~%")
  313. url data)
  314. #f)
  315. ('missing-key
  316. (warning (G_ "missing public key ~a for '~a'~%")
  317. data url)
  318. #f)))))))
  319. (define (find2 pred lst1 lst2)
  320. "Like 'find', but operate on items from both LST1 and LST2. Return two
  321. values: the item from LST1 and the item from LST2 that match PRED."
  322. (let loop ((lst1 lst1) (lst2 lst2))
  323. (match lst1
  324. ((head1 . tail1)
  325. (match lst2
  326. ((head2 . tail2)
  327. (if (pred head1 head2)
  328. (values head1 head2)
  329. (loop tail1 tail2)))))
  330. (()
  331. (values #f #f)))))
  332. (define* (package-update/url-fetch store package source
  333. #:key key-download)
  334. "Return the version, tarball, and SOURCE, to update PACKAGE to
  335. SOURCE, an <upstream-source>."
  336. (match source
  337. (($ <upstream-source> _ version urls signature-urls)
  338. (let*-values (((archive-type)
  339. (match (and=> (package-source package) origin-uri)
  340. ((? string? uri)
  341. (let ((type (or (file-extension (basename uri)) "")))
  342. ;; Sometimes we have URLs such as
  343. ;; "https://github.com/…/tarball/v0.1", in which case
  344. ;; we must not consider "1" as the extension.
  345. (and (or (string-contains type "z")
  346. (string=? type "tar"))
  347. type)))
  348. (_
  349. "gz")))
  350. ((url signature-url)
  351. ;; Try to find a URL that matches ARCHIVE-TYPE.
  352. (find2 (lambda (url sig-url)
  353. ;; Some URIs lack a file extension, like
  354. ;; 'https://crates.io/???/0.1/download'. In that
  355. ;; case, pick the first URL.
  356. (or (not archive-type)
  357. (string-suffix? archive-type url)))
  358. urls
  359. (or signature-urls (circular-list #f)))))
  360. ;; If none of URLS matches ARCHIVE-TYPE, then URL is #f; in that case,
  361. ;; pick up the first element of URLS.
  362. (let ((tarball (download-tarball store
  363. (or url (first urls))
  364. (and (pair? signature-urls)
  365. (or signature-url
  366. (first signature-urls)))
  367. #:key-download key-download)))
  368. (values version tarball source))))))
  369. (define %method-updates
  370. ;; Mapping of origin methods to source update procedures.
  371. `((,url-fetch . ,package-update/url-fetch)))
  372. (define* (package-update store package updaters
  373. #:key (key-download 'interactive))
  374. "Return the new version, the file name of the new version tarball, and input
  375. changes for PACKAGE; return #f (three values) when PACKAGE is up-to-date.
  376. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  377. values: 'always', 'never', and 'interactive' (default)."
  378. (match (package-latest-release* package updaters)
  379. ((? upstream-source? source)
  380. (let ((method (match (package-source package)
  381. ((? origin? origin)
  382. (origin-method origin))
  383. (_
  384. #f))))
  385. (match (assq method %method-updates)
  386. (#f
  387. (raise (make-compound-condition
  388. (formatted-message (G_ "cannot download for \
  389. this method: ~s")
  390. method)
  391. (condition
  392. (&error-location
  393. (location (package-location package)))))))
  394. ((_ . update)
  395. (update store package source
  396. #:key-download key-download)))))
  397. (#f
  398. (values #f #f #f))))
  399. (define* (update-package-source package source hash)
  400. "Modify the source file that defines PACKAGE to refer to SOURCE, an
  401. <upstream-source> whose tarball has SHA256 HASH (a bytevector). Return the
  402. new version string if an update was made, and #f otherwise."
  403. (define (update-expression expr replacements)
  404. ;; Apply REPLACEMENTS to package expression EXPR, a string. REPLACEMENTS
  405. ;; must be a list of replacement pairs, either bytevectors or strings.
  406. (fold (lambda (replacement str)
  407. (match replacement
  408. (((? bytevector? old-bv) . (? bytevector? new-bv))
  409. (string-replace-substring
  410. str
  411. (bytevector->nix-base32-string old-bv)
  412. (bytevector->nix-base32-string new-bv)))
  413. ((old . new)
  414. (string-replace-substring str old new))))
  415. expr
  416. replacements))
  417. (let ((name (package-name package))
  418. (version (upstream-source-version source))
  419. (version-loc (package-field-location package 'version)))
  420. (if version-loc
  421. (let* ((loc (package-location package))
  422. (old-version (package-version package))
  423. (old-hash (content-hash-value
  424. (origin-hash (package-source package))))
  425. (old-url (match (origin-uri (package-source package))
  426. ((? string? url) url)
  427. (_ #f)))
  428. (new-url (match (upstream-source-urls source)
  429. ((first _ ...) first)))
  430. (file (and=> (location-file loc)
  431. (cut search-path %load-path <>))))
  432. (if file
  433. ;; Be sure to use absolute filename. Replace the URL directory
  434. ;; when OLD-URL is available; this is useful notably for
  435. ;; mirror://cpan/ URLs where the directory may change as a
  436. ;; function of the person who uploads the package. Note that
  437. ;; package definitions usually concatenate fragments of the URL,
  438. ;; which is why we only attempt to replace a subset of the URL.
  439. (let ((properties (assq-set! (location->source-properties loc)
  440. 'filename file))
  441. (replacements `((,old-version . ,version)
  442. (,old-hash . ,hash)
  443. ,@(if (and old-url new-url)
  444. `((,(dirname old-url) .
  445. ,(dirname new-url)))
  446. '()))))
  447. (and (edit-expression properties
  448. (cut update-expression <> replacements))
  449. version))
  450. (begin
  451. (warning (G_ "~a: could not locate source file")
  452. (location-file loc))
  453. #f)))
  454. (warning (package-location package)
  455. (G_ "~a: no `version' field in source; skipping~%")
  456. name))))
  457. ;;; upstream.scm ends here