gnu-maintenance.scm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  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 © 2012, 2013 Nikita Karetnikov <nikita@karetnikov.org>
  4. ;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
  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 gnu-maintenance)
  21. #:use-module (web uri)
  22. #:use-module (web client)
  23. #:use-module (web response)
  24. #:use-module (sxml simple)
  25. #:use-module (ice-9 regex)
  26. #:use-module (ice-9 match)
  27. #:use-module (srfi srfi-1)
  28. #:use-module (srfi srfi-11)
  29. #:use-module (srfi srfi-26)
  30. #:use-module (srfi srfi-34)
  31. #:use-module (rnrs io ports)
  32. #:use-module (system foreign)
  33. #:use-module (guix http-client)
  34. #:use-module (guix ftp-client)
  35. #:use-module (guix utils)
  36. #:use-module (guix memoization)
  37. #:use-module (guix records)
  38. #:use-module (guix upstream)
  39. #:use-module (guix packages)
  40. #:autoload (zlib) (call-with-gzip-input-port)
  41. #:autoload (htmlprag) (html->sxml) ;from Guile-Lib
  42. #:export (gnu-package-name
  43. gnu-package-mundane-name
  44. gnu-package-copyright-holder
  45. gnu-package-savannah
  46. gnu-package-fsd
  47. gnu-package-language
  48. gnu-package-logo
  49. gnu-package-doc-category
  50. gnu-package-doc-summary
  51. gnu-package-doc-description
  52. gnu-package-doc-urls
  53. gnu-package-download-url
  54. official-gnu-packages
  55. find-package
  56. gnu-package?
  57. release-file?
  58. releases
  59. latest-release
  60. gnu-release-archive-types
  61. gnu-package-name->name+version
  62. %gnu-updater
  63. %gnu-ftp-updater
  64. %savannah-updater
  65. %xorg-updater
  66. %kernel.org-updater
  67. %generic-html-updater))
  68. ;;; Commentary:
  69. ;;;
  70. ;;; Code for dealing with the maintenance of GNU packages, such as
  71. ;;; auto-updates.
  72. ;;;
  73. ;;; Code:
  74. ;;;
  75. ;;; List of GNU packages.
  76. ;;;
  77. (define %gnumaint-base-url
  78. "https://web.cvs.savannah.gnu.org/viewvc/*checkout*/www/www/prep/gnumaint/")
  79. (define %package-list-url
  80. (string->uri
  81. (string-append %gnumaint-base-url "rec/gnupackages.rec")))
  82. (define %package-description-url
  83. ;; This file contains package descriptions in recutils format.
  84. ;; See <https://lists.gnu.org/archive/html/guix-devel/2013-10/msg00071.html>
  85. ;; and <https://lists.gnu.org/archive/html/guix-devel/2018-06/msg00362.html>.
  86. (string->uri
  87. (string-append %gnumaint-base-url "rec/pkgblurbs.rec")))
  88. (define-record-type* <gnu-package-descriptor>
  89. gnu-package-descriptor
  90. make-gnu-package-descriptor
  91. gnu-package-descriptor?
  92. (name gnu-package-name)
  93. (mundane-name gnu-package-mundane-name)
  94. (copyright-holder gnu-package-copyright-holder)
  95. (savannah gnu-package-savannah)
  96. (fsd gnu-package-fsd)
  97. (language gnu-package-language) ; list of strings
  98. (logo gnu-package-logo)
  99. (doc-category gnu-package-doc-category)
  100. (doc-summary gnu-package-doc-summary)
  101. (doc-description gnu-package-doc-description) ; taken from 'pkgdescr.txt'
  102. (doc-urls gnu-package-doc-urls) ; list of strings
  103. (download-url gnu-package-download-url))
  104. (define* (official-gnu-packages
  105. #:optional (fetch http-fetch/cached))
  106. "Return a list of records, which are GNU packages. Use FETCH,
  107. to fetch the list of GNU packages over HTTP."
  108. (define (read-records port)
  109. ;; Return a list of alists. Each alist contains fields of a GNU
  110. ;; package.
  111. (let loop ((alist (recutils->alist port))
  112. (result '()))
  113. (if (null? alist)
  114. (reverse result)
  115. (loop (recutils->alist port)
  116. ;; Ignore things like "%rec" (info "(recutils) Record
  117. ;; Descriptors").
  118. (if (assoc-ref alist "package")
  119. (cons alist result)
  120. result)))))
  121. (define official-description
  122. (let ((db (read-records (fetch %package-description-url #:text? #t))))
  123. (lambda (name)
  124. ;; Return the description found upstream for package NAME, or #f.
  125. (and=> (find (lambda (alist)
  126. (equal? name (assoc-ref alist "package")))
  127. db)
  128. (lambda (record)
  129. (let ((field (assoc-ref record "blurb")))
  130. ;; The upstream description file uses "redirect PACKAGE" as
  131. ;; a blurb in cases where the description of the two
  132. ;; packages should be considered the same (e.g., GTK+ has
  133. ;; "redirect gnome".) This is usually not acceptable for
  134. ;; us because we prefer to have distinct descriptions in
  135. ;; such cases. Thus, ignore the 'blurb' field when that
  136. ;; happens.
  137. (and field
  138. (not (string-prefix? "redirect " field))
  139. field)))))))
  140. (map (lambda (alist)
  141. (let ((name (assoc-ref alist "package")))
  142. (alist->record `(("description" . ,(official-description name))
  143. ,@alist)
  144. make-gnu-package-descriptor
  145. (list "package" "mundane_name" "copyright_holder"
  146. "savannah" "fsd" "language" "logo"
  147. "doc_category" "doc_summary" "description"
  148. "doc_url"
  149. "download_url")
  150. '("doc_url" "language"))))
  151. (let* ((port (fetch %package-list-url #:text? #t))
  152. (lst (read-records port)))
  153. (close-port port)
  154. lst)))
  155. (define (find-package name)
  156. "Find GNU package called NAME and return it. Return #f if it was not
  157. found."
  158. (find (lambda (package)
  159. (string=? name (gnu-package-name package)))
  160. (official-gnu-packages)))
  161. (define gnu-package?
  162. (let ((official-gnu-packages (memoize official-gnu-packages)))
  163. (mlambdaq (package)
  164. "Return true if PACKAGE is a GNU package. This procedure may access the
  165. network to check in GNU's database."
  166. (define (mirror-type url)
  167. (let ((uri (string->uri url)))
  168. (and (eq? (uri-scheme uri) 'mirror)
  169. (cond
  170. ((member (uri-host uri)
  171. '("gnu" "gnupg" "gcc" "gnome"))
  172. ;; Definitely GNU.
  173. 'gnu)
  174. ((equal? (uri-host uri) "cran")
  175. ;; Possibly GNU: mirror://cran could be either GNU R itself
  176. ;; or a non-GNU package.
  177. #f)
  178. (else
  179. ;; Definitely non-GNU.
  180. 'non-gnu)))))
  181. (define (gnu-home-page? package)
  182. (letrec-syntax ((>> (syntax-rules ()
  183. ((_ value proc)
  184. (and=> value proc))
  185. ((_ value proc rest ...)
  186. (and=> value
  187. (lambda (next)
  188. (>> (proc next) rest ...)))))))
  189. (>> package package-home-page
  190. string->uri uri-host
  191. (lambda (host)
  192. (member host '("www.gnu.org" "gnu.org"))))))
  193. (or (gnu-home-page? package)
  194. (match (package-source package)
  195. ((? origin? origin)
  196. (let ((url (origin-uri origin))
  197. (name (package-upstream-name package)))
  198. (case (and (string? url) (mirror-type url))
  199. ((gnu) #t)
  200. ((non-gnu) #f)
  201. (else
  202. (and (member name (map gnu-package-name (official-gnu-packages)))
  203. #t)))))
  204. (_ #f))))))
  205. ;;;
  206. ;;; Latest FTP release.
  207. ;;;
  208. (define (ftp-server/directory package)
  209. "Return the FTP server and directory where PACKAGE's tarball are stored."
  210. (let ((name (package-upstream-name package)))
  211. (values (or (assoc-ref (package-properties package) 'ftp-server)
  212. "ftp.gnu.org")
  213. (or (assoc-ref (package-properties package) 'ftp-directory)
  214. (string-append "/gnu/" name)))))
  215. (define %tarball-rx
  216. ;; The .zip extensions is notably used for freefont-ttf.
  217. ;; The "-src" pattern is for "TeXmacs-1.0.7.9-src.tar.gz".
  218. ;; The "-gnu[0-9]" pattern is for "icecat-38.4.0-gnu1.tar.bz2".
  219. ;; Accept underscores as in "PKG_1.2.tar.gz" for some non-GNU packages.
  220. (make-regexp "^([^.]+)[-_]([0-9]|[^-])+(-(src|gnu[0-9]))?\\.(tar\\.|zip$)"))
  221. (define %alpha-tarball-rx
  222. (make-regexp "^.*-.*[0-9](-|~)?(alpha|beta|rc|RC|cvs|svn|git)-?[0-9\\.]*\\.tar\\."))
  223. (define (release-file? project file)
  224. "Return #f if FILE is not a release tarball of PROJECT, otherwise return
  225. true."
  226. (and (not (member (file-extension file)
  227. '("sig" "sign" "asc"
  228. "md5sum" "sha1sum" "sha256sum")))
  229. (and=> (regexp-exec %tarball-rx file)
  230. (lambda (match)
  231. ;; Filter out unrelated files, like `guile-www-1.1.1'.
  232. ;; Case-insensitive for things like "TeXmacs" vs. "texmacs".
  233. ;; The "-src" suffix is for "freefont-src-20120503.tar.gz".
  234. (and=> (match:substring match 1)
  235. (lambda (name)
  236. (or (string-ci=? name project)
  237. (string-ci=? name
  238. (string-append project
  239. "-src")))))))
  240. (not (regexp-exec %alpha-tarball-rx file))
  241. (let ((s (tarball-sans-extension file)))
  242. (regexp-exec %package-name-rx s))))
  243. (define (tarball->version tarball)
  244. "Return the version TARBALL corresponds to. TARBALL is a file name like
  245. \"coreutils-8.23.tar.xz\"."
  246. (let-values (((name version)
  247. (gnu-package-name->name+version
  248. (tarball-sans-extension tarball))))
  249. version))
  250. (define* (releases project
  251. #:key
  252. (server "ftp.gnu.org")
  253. (directory (string-append "/gnu/" project)))
  254. "Return the list of <upstream-release> of PROJECT as a list of release
  255. name/directory pairs."
  256. ;; TODO: Parse something like fencepost.gnu.org:/gd/gnuorg/packages-ftp.
  257. (define conn (ftp-open server))
  258. (let loop ((directories (list directory))
  259. (result '()))
  260. (match directories
  261. (()
  262. (ftp-close conn)
  263. (coalesce-sources result))
  264. ((directory rest ...)
  265. (let* ((files (ftp-list conn directory))
  266. (subdirs (filter-map (match-lambda
  267. ((name 'directory . _) name)
  268. (_ #f))
  269. files)))
  270. (define (file->url file)
  271. (string-append "ftp://" server directory "/" file))
  272. (define (file->source file)
  273. (let ((url (file->url file)))
  274. (upstream-source
  275. (package project)
  276. (version (tarball->version file))
  277. (urls (list url))
  278. (signature-urls (list (string-append url ".sig"))))))
  279. (loop (append (map (cut string-append directory "/" <>)
  280. subdirs)
  281. rest)
  282. (append
  283. ;; Filter out signatures, deltas, and files which
  284. ;; are potentially not releases of PROJECT--e.g.,
  285. ;; in /gnu/guile, filter out guile-oops and
  286. ;; guile-www; in mit-scheme, filter out binaries.
  287. (filter-map (match-lambda
  288. ((file 'file . _)
  289. (and (release-file? project file)
  290. (file->source file)))
  291. (_ #f))
  292. files)
  293. result)))))))
  294. (define* (latest-ftp-release project
  295. #:key
  296. (server "ftp.gnu.org")
  297. (directory (string-append "/gnu/" project))
  298. (file->signature (cut string-append <> ".sig")))
  299. "Return an <upstream-source> for the latest release of PROJECT on SERVER
  300. under DIRECTORY, or #f. Use FTP-OPEN and FTP-CLOSE to open (resp. close) FTP
  301. connections; this can be useful to reuse connections.
  302. FILE->SIGNATURE must be a procedure; it is passed a source file URL and must
  303. return the corresponding signature URL, or #f it signatures are unavailable."
  304. (define (latest a b)
  305. (if (version>? a b) a b))
  306. (define (latest-release a b)
  307. (if (version>? (upstream-source-version a) (upstream-source-version b))
  308. a b))
  309. (define patch-directory-name?
  310. ;; Return #t for patch directory names such as 'bash-4.2-patches'.
  311. (cut string-suffix? "patches" <>))
  312. (define conn (ftp-open server #:timeout 5))
  313. (define (file->url directory file)
  314. (string-append "ftp://" server directory "/" file))
  315. (define (file->source directory file)
  316. (let ((url (file->url directory file)))
  317. (upstream-source
  318. (package project)
  319. (version (tarball->version file))
  320. (urls (list url))
  321. (signature-urls (match (file->signature url)
  322. (#f #f)
  323. (sig (list sig)))))))
  324. (let loop ((directory directory)
  325. (result #f))
  326. (let* ((entries (catch 'ftp-error
  327. (lambda _ (ftp-list conn directory))
  328. (const '())))
  329. ;; Filter out things like /gnupg/patches. Filter out "w32"
  330. ;; directories as found on ftp.gnutls.org.
  331. (subdirs (filter-map (match-lambda
  332. (((? patch-directory-name? dir)
  333. 'directory . _)
  334. #f)
  335. (("w32" 'directory . _)
  336. #f)
  337. (("unstable" 'directory . _)
  338. ;; As seen at ftp.gnupg.org/gcrypt/pinentry.
  339. #f)
  340. ((directory 'directory . _)
  341. directory)
  342. (_ #f))
  343. entries))
  344. ;; Whether or not SUBDIRS is empty, compute the latest releases
  345. ;; for the current directory. This is necessary for packages
  346. ;; such as 'sharutils' that have a sub-directory that contains
  347. ;; only an older release.
  348. (releases (filter-map (match-lambda
  349. ((file 'file . _)
  350. (and (release-file? project file)
  351. (file->source directory file)))
  352. (_ #f))
  353. entries)))
  354. ;; Assume that SUBDIRS correspond to versions, and jump into the
  355. ;; one with the highest version number.
  356. (let* ((release (reduce latest-release #f
  357. (coalesce-sources releases)))
  358. (result (if (and result release)
  359. (latest-release release result)
  360. (or release result)))
  361. (target (reduce latest #f subdirs)))
  362. (if target
  363. (loop (string-append directory "/" target)
  364. result)
  365. (begin
  366. (ftp-close conn)
  367. result))))))
  368. (define* (latest-release package
  369. #:key
  370. (server "ftp.gnu.org")
  371. (directory (string-append "/gnu/" package)))
  372. "Return the <upstream-source> for the latest version of PACKAGE or #f.
  373. PACKAGE must be the canonical name of a GNU package."
  374. (latest-ftp-release package
  375. #:server server
  376. #:directory directory))
  377. (define-syntax-rule (false-if-ftp-error exp)
  378. "Return #f if an FTP error is raise while evaluating EXP; return the result
  379. of EXP otherwise."
  380. (catch 'ftp-error
  381. (lambda ()
  382. exp)
  383. (lambda (key port . rest)
  384. (if (ftp-connection? port)
  385. (ftp-close port)
  386. (close-port port))
  387. #f)))
  388. (define (latest-release* package)
  389. "Like 'latest-release', but (1) take a <package> object, and (2) ignore FTP
  390. errors that might occur when PACKAGE is not actually a GNU package, or not
  391. hosted on ftp.gnu.org, or not under that name (this is the case for
  392. \"emacs-auctex\", for instance.)"
  393. (let-values (((server directory)
  394. (ftp-server/directory package)))
  395. (false-if-ftp-error (latest-release (package-upstream-name package)
  396. #:server server
  397. #:directory directory))))
  398. ;;;
  399. ;;; Latest HTTP release.
  400. ;;;
  401. (define (html-links sxml)
  402. "Return the list of links found in SXML, the SXML tree of an HTML page."
  403. (let loop ((sxml sxml)
  404. (links '()))
  405. (match sxml
  406. (('a ('@ attributes ...) body ...)
  407. (match (assq 'href attributes)
  408. (#f (fold loop links body))
  409. (('href url) (fold loop (cons url links) body))))
  410. ((tag ('@ _ ...) body ...)
  411. (fold loop links body))
  412. ((tag body ...)
  413. (fold loop links body))
  414. (_
  415. links))))
  416. (define* (latest-html-release package
  417. #:key
  418. (base-url "https://kernel.org/pub")
  419. (directory (string-append "/" package))
  420. file->signature)
  421. "Return an <upstream-source> for the latest release of PACKAGE (a string) on
  422. SERVER under DIRECTORY, or #f. BASE-URL should be the URL of an HTML page,
  423. typically a directory listing as found on 'https://kernel.org/pub'.
  424. When FILE->SIGNATURE is omitted or #f, guess the detached signature file name,
  425. if any. Otherwise, FILE->SIGNATURE must be a procedure; it is passed a source
  426. file URL and must return the corresponding signature URL, or #f it signatures
  427. are unavailable."
  428. (let* ((uri (string->uri (if (string-null? directory)
  429. base-url
  430. (string-append base-url directory "/"))))
  431. (port (http-fetch/cached uri #:ttl 3600))
  432. (sxml (html->sxml port))
  433. (links (delete-duplicates (html-links sxml))))
  434. (define (file->signature/guess url)
  435. (let ((base (basename url)))
  436. (any (lambda (link)
  437. (any (lambda (extension)
  438. (and (string=? (string-append base extension)
  439. (basename link))
  440. (string-append url extension)))
  441. '(".asc" ".sig" ".sign")))
  442. links)))
  443. (define (url->release url)
  444. (let* ((base (basename url))
  445. (url (if (string=? base url)
  446. (string-append base-url directory "/" url)
  447. url)))
  448. (and (release-file? package base)
  449. (let ((version (tarball->version base)))
  450. (upstream-source
  451. (package package)
  452. (version version)
  453. (urls (list url))
  454. (signature-urls
  455. (list ((or file->signature file->signature/guess) url))))))))
  456. (define candidates
  457. (filter-map url->release links))
  458. (close-port port)
  459. (match candidates
  460. (() #f)
  461. ((first . _)
  462. ;; Select the most recent release and return it.
  463. (reduce (lambda (r1 r2)
  464. (if (version>? (upstream-source-version r1)
  465. (upstream-source-version r2))
  466. r1 r2))
  467. first
  468. (coalesce-sources candidates))))))
  469. ;;;
  470. ;;; Updaters.
  471. ;;;
  472. (define %gnu-file-list-uri
  473. ;; URI of the file list for ftp.gnu.org.
  474. (string->uri "https://ftp.gnu.org/find.txt.gz"))
  475. (define ftp.gnu.org-files
  476. (mlambda ()
  477. "Return the list of files available at ftp.gnu.org."
  478. ;; XXX: Memoize the whole procedure to work around the fact that
  479. ;; 'http-fetch/cached' caches the gzipped version.
  480. (define (trim-leading-components str)
  481. ;; Trim the leading ".", if any, in "./gnu/foo".
  482. (string-trim str (char-set #\.)))
  483. (define (string->lines str)
  484. (string-tokenize str (char-set-complement (char-set #\newline))))
  485. ;; Since https://ftp.gnu.org honors 'If-Modified-Since', the hard-coded
  486. ;; TTL can be relatively short.
  487. (let ((port (http-fetch/cached %gnu-file-list-uri #:ttl (* 15 60))))
  488. (map trim-leading-components
  489. (call-with-gzip-input-port port
  490. (compose string->lines get-string-all))))))
  491. (define (latest-gnu-release package)
  492. "Return the latest release of PACKAGE, a GNU package available via
  493. ftp.gnu.org.
  494. This method does not rely on FTP access at all; instead, it browses the file
  495. list available from %GNU-FILE-LIST-URI over HTTP(S)."
  496. (let-values (((server directory)
  497. (ftp-server/directory package))
  498. ((name)
  499. (package-upstream-name package)))
  500. (let* ((files (ftp.gnu.org-files))
  501. (relevant (filter (lambda (file)
  502. (and (string-prefix? "/gnu" file)
  503. (string-contains file directory)
  504. (release-file? name (basename file))))
  505. files)))
  506. (match (sort relevant (lambda (file1 file2)
  507. (version>? (tarball-sans-extension
  508. (basename file1))
  509. (tarball-sans-extension
  510. (basename file2)))))
  511. ((and tarballs (reference _ ...))
  512. (let* ((version (tarball->version reference))
  513. (tarballs (filter (lambda (file)
  514. (string=? (tarball-sans-extension
  515. (basename file))
  516. (tarball-sans-extension
  517. (basename reference))))
  518. tarballs)))
  519. (upstream-source
  520. (package name)
  521. (version version)
  522. (urls (map (lambda (file)
  523. (string-append "mirror://gnu/"
  524. (string-drop file
  525. (string-length "/gnu/"))))
  526. tarballs))
  527. (signature-urls (map (cut string-append <> ".sig") urls)))))
  528. (()
  529. #f)))))
  530. (define %package-name-rx
  531. ;; Regexp for a package name, e.g., "foo-X.Y". Since TeXmacs uses
  532. ;; "TeXmacs-X.Y-src", the `-src' suffix is allowed.
  533. (make-regexp "^(.*)[-_](([0-9]|\\.)+)(-src)?"))
  534. (define (gnu-package-name->name+version name+version)
  535. "Return the package name and version number extracted from NAME+VERSION."
  536. (let ((match (regexp-exec %package-name-rx name+version)))
  537. (if (not match)
  538. (values name+version #f)
  539. (values (match:substring match 1) (match:substring match 2)))))
  540. (define gnome-package?
  541. (url-prefix-predicate "mirror://gnome/"))
  542. (define (pure-gnu-package? package)
  543. "Return true if PACKAGE is a non-Emacs and non-GNOME GNU package. This
  544. excludes AucTeX, for instance, whose releases are now uploaded to
  545. elpa.gnu.org, GNU Radio, which has releases at www.gnuradio.org, and all the
  546. GNOME packages; EMMS is included though, because its releases are on gnu.org."
  547. (and (or (not (string-prefix? "emacs-" (package-name package)))
  548. (gnu-hosted? package))
  549. (not (gnome-package? package))
  550. (not (string-prefix? "gnuradio" (package-name package)))
  551. (gnu-package? package)))
  552. (define gnu-hosted?
  553. (url-prefix-predicate "mirror://gnu/"))
  554. (define (url-prefix-rewrite old new)
  555. "Return a one-argument procedure that rewrites URL prefix OLD to NEW."
  556. (lambda (url)
  557. (if (and url (string-prefix? old url))
  558. (string-append new (string-drop url (string-length old)))
  559. url)))
  560. (define (adjusted-upstream-source source rewrite-url)
  561. "Rewrite URLs in SOURCE by apply REWRITE-URL to each of them."
  562. (upstream-source
  563. (inherit source)
  564. (urls (map rewrite-url (upstream-source-urls source)))
  565. (signature-urls (and=> (upstream-source-signature-urls source)
  566. (lambda (urls)
  567. (map rewrite-url urls))))))
  568. (define savannah-package?
  569. (url-prefix-predicate "mirror://savannah/"))
  570. (define %savannah-base
  571. ;; One of the Savannah mirrors listed at
  572. ;; <http://download0.savannah.gnu.org/mirmon/savannah/> that serves valid
  573. ;; HTML (unlike <https://download.savannah.nongnu.org/releases>.)
  574. "https://nongnu.freemirror.org/nongnu")
  575. (define (latest-savannah-release package)
  576. "Return the latest release of PACKAGE."
  577. (let* ((uri (string->uri
  578. (match (origin-uri (package-source package))
  579. ((? string? uri) uri)
  580. ((uri mirrors ...) uri))))
  581. (package (package-upstream-name package))
  582. (directory (dirname (uri-path uri)))
  583. (rewrite (url-prefix-rewrite %savannah-base
  584. "mirror://savannah")))
  585. ;; Note: We use the default 'file->signature', which adds ".sig", ".asc",
  586. ;; or whichever detached signature naming scheme PACKAGE uses.
  587. (and=> (latest-html-release package
  588. #:base-url %savannah-base
  589. #:directory directory)
  590. (cut adjusted-upstream-source <> rewrite))))
  591. (define (latest-xorg-release package)
  592. "Return the latest release of PACKAGE."
  593. (let ((uri (string->uri (origin-uri (package-source package)))))
  594. (false-if-ftp-error
  595. (latest-ftp-release
  596. (package-name package)
  597. #:server "ftp.freedesktop.org"
  598. #:directory
  599. (string-append "/pub/xorg/" (dirname (uri-path uri)))))))
  600. (define (latest-kernel.org-release package)
  601. "Return the latest release of PACKAGE, the name of a kernel.org package."
  602. (define %kernel.org-base
  603. ;; This URL and sub-directories thereof are nginx-generated directory
  604. ;; listings suitable for 'latest-html-release'.
  605. "https://mirrors.edge.kernel.org/pub")
  606. (define (file->signature file)
  607. (string-append (file-sans-extension file) ".sign"))
  608. (let* ((uri (string->uri
  609. (match (origin-uri (package-source package))
  610. ((? string? uri) uri)
  611. ((uri mirrors ...) uri))))
  612. (package (package-upstream-name package))
  613. (directory (dirname (uri-path uri)))
  614. (rewrite (url-prefix-rewrite %kernel.org-base
  615. "mirror://kernel.org")))
  616. (and=> (latest-html-release package
  617. #:base-url %kernel.org-base
  618. #:directory directory
  619. #:file->signature file->signature)
  620. (cut adjusted-upstream-source <> rewrite))))
  621. (define html-updatable-package?
  622. ;; Return true if the given package may be handled by the generic HTML
  623. ;; updater.
  624. (let ((hosting-sites '("github.com" "github.io" "gitlab.com"
  625. "notabug.org" "sr.ht"
  626. "gforge.inria.fr" "gitlab.inria.fr"
  627. "ftp.gnu.org" "download.savannah.gnu.org"
  628. "pypi.org" "crates.io" "rubygems.org"
  629. "bioconductor.org")))
  630. (url-predicate (lambda (url)
  631. (match (string->uri url)
  632. (#f #f)
  633. (uri
  634. (let ((scheme (uri-scheme uri))
  635. (host (uri-host uri)))
  636. (and (memq scheme '(http https))
  637. (not (member host hosting-sites))))))))))
  638. (define (latest-html-updatable-release package)
  639. "Return the latest release of PACKAGE. Do that by crawling the HTML page of
  640. the directory containing its source tarball."
  641. (let* ((uri (string->uri
  642. (match (origin-uri (package-source package))
  643. ((? string? url) url)
  644. ((url _ ...) url))))
  645. (custom (assoc-ref (package-properties package)
  646. 'release-monitoring-url))
  647. (base (or custom
  648. (string-append (symbol->string (uri-scheme uri))
  649. "://" (uri-host uri))))
  650. (directory (if custom
  651. ""
  652. (dirname (uri-path uri))))
  653. (package (package-upstream-name package)))
  654. (catch #t
  655. (lambda ()
  656. (guard (c ((http-get-error? c) #f))
  657. (latest-html-release package
  658. #:base-url base
  659. #:directory directory)))
  660. (lambda (key . args)
  661. ;; Return false and move on upon connection failures and bogus HTTP
  662. ;; servers.
  663. (unless (memq key '(gnutls-error tls-certificate-error
  664. system-error
  665. bad-header bad-header-component))
  666. (apply throw key args))
  667. #f))))
  668. (define %gnu-updater
  669. ;; This is for everything at ftp.gnu.org.
  670. (upstream-updater
  671. (name 'gnu)
  672. (description "Updater for GNU packages")
  673. (pred gnu-hosted?)
  674. (latest latest-gnu-release)))
  675. (define %gnu-ftp-updater
  676. ;; This is for GNU packages taken from alternate locations, such as
  677. ;; alpha.gnu.org, ftp.gnupg.org, etc. It is obsolescent.
  678. (upstream-updater
  679. (name 'gnu-ftp)
  680. (description "Updater for GNU packages only available via FTP")
  681. (pred (lambda (package)
  682. (and (not (gnu-hosted? package))
  683. (pure-gnu-package? package))))
  684. (latest latest-release*)))
  685. (define %savannah-updater
  686. (upstream-updater
  687. (name 'savannah)
  688. (description "Updater for packages hosted on savannah.gnu.org")
  689. (pred (url-prefix-predicate "mirror://savannah/"))
  690. (latest latest-savannah-release)))
  691. (define %xorg-updater
  692. (upstream-updater
  693. (name 'xorg)
  694. (description "Updater for X.org packages")
  695. (pred (url-prefix-predicate "mirror://xorg/"))
  696. (latest latest-xorg-release)))
  697. (define %kernel.org-updater
  698. (upstream-updater
  699. (name 'kernel.org)
  700. (description "Updater for packages hosted on kernel.org")
  701. (pred (url-prefix-predicate "mirror://kernel.org/"))
  702. (latest latest-kernel.org-release)))
  703. (define %generic-html-updater
  704. (upstream-updater
  705. (name 'generic-html)
  706. (description "Updater that crawls HTML pages.")
  707. (pred html-updatable-package?)
  708. (latest latest-html-updatable-release)))
  709. ;;; gnu-maintenance.scm ends here