gnu-maintenance.scm 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2010-2023 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. ;;; Copyright © 2022 Maxime Devos <maximedevos@telenet.be>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (guix gnu-maintenance)
  22. #:use-module (web uri)
  23. #:use-module (web client)
  24. #:use-module (web response)
  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 (rnrs io ports)
  31. #:use-module ((guix http-client) #:hide (open-socket-for-uri))
  32. ;; not required in many cases, so autoloaded to reduce start-up costs.
  33. #:autoload (guix download) (%mirrors)
  34. #:use-module (guix ftp-client)
  35. #:use-module (guix utils)
  36. #:use-module (guix diagnostics)
  37. #:use-module (guix i18n)
  38. #:use-module (guix memoization)
  39. #:use-module (guix records)
  40. #:use-module (guix upstream)
  41. #:use-module (guix packages)
  42. #:autoload (guix import utils) (false-if-networking-error)
  43. #:autoload (zlib) (call-with-gzip-input-port)
  44. #:autoload (htmlprag) (html->sxml) ;from Guile-Lib
  45. #:export (gnu-package-name
  46. gnu-package-mundane-name
  47. gnu-package-copyright-holder
  48. gnu-package-savannah
  49. gnu-package-fsd
  50. gnu-package-language
  51. gnu-package-logo
  52. gnu-package-doc-category
  53. gnu-package-doc-summary
  54. gnu-package-doc-description
  55. gnu-package-doc-urls
  56. gnu-package-download-url
  57. official-gnu-packages
  58. find-package
  59. gnu-package?
  60. uri-mirror-rewrite
  61. release-file?
  62. releases
  63. import-release
  64. gnu-release-archive-types
  65. gnu-package-name->name+version
  66. %gnu-updater
  67. %gnu-ftp-updater
  68. %savannah-updater
  69. %sourceforge-updater
  70. %xorg-updater
  71. %kernel.org-updater
  72. %generic-html-updater))
  73. ;;; Commentary:
  74. ;;;
  75. ;;; Code for dealing with the maintenance of GNU packages, such as
  76. ;;; auto-updates.
  77. ;;;
  78. ;;; Code:
  79. ;;;
  80. ;;; List of GNU packages.
  81. ;;;
  82. (define %gnumaint-base-url
  83. "https://web.cvs.savannah.gnu.org/viewvc/*checkout*/www/www/prep/gnumaint/")
  84. (define %package-list-url
  85. (string->uri
  86. (string-append %gnumaint-base-url "rec/gnupackages.rec")))
  87. (define %package-description-url
  88. ;; This file contains package descriptions in recutils format.
  89. ;; See <https://lists.gnu.org/archive/html/guix-devel/2013-10/msg00071.html>
  90. ;; and <https://lists.gnu.org/archive/html/guix-devel/2018-06/msg00362.html>.
  91. (string->uri
  92. (string-append %gnumaint-base-url "rec/pkgblurbs.rec")))
  93. (define-record-type* <gnu-package-descriptor>
  94. gnu-package-descriptor
  95. make-gnu-package-descriptor
  96. gnu-package-descriptor?
  97. (name gnu-package-name)
  98. (mundane-name gnu-package-mundane-name)
  99. (copyright-holder gnu-package-copyright-holder)
  100. (savannah gnu-package-savannah)
  101. (fsd gnu-package-fsd)
  102. (language gnu-package-language) ; list of strings
  103. (logo gnu-package-logo)
  104. (doc-category gnu-package-doc-category)
  105. (doc-summary gnu-package-doc-summary)
  106. (doc-description gnu-package-doc-description) ; taken from 'pkgdescr.txt'
  107. (doc-urls gnu-package-doc-urls) ; list of strings
  108. (download-url gnu-package-download-url))
  109. (define* (official-gnu-packages
  110. #:optional (fetch http-fetch/cached))
  111. "Return a list of records, which are GNU packages. Use FETCH,
  112. to fetch the list of GNU packages over HTTP."
  113. (define (read-records port)
  114. ;; Return a list of alists. Each alist contains fields of a GNU
  115. ;; package.
  116. (let loop ((alist (recutils->alist port))
  117. (result '()))
  118. (if (null? alist)
  119. (reverse result)
  120. (loop (recutils->alist port)
  121. ;; Ignore things like "%rec" (info "(recutils) Record
  122. ;; Descriptors").
  123. (if (assoc-ref alist "package")
  124. (cons alist result)
  125. result)))))
  126. (define official-description
  127. (let ((db (read-records (fetch %package-description-url #:text? #t))))
  128. (lambda (name)
  129. ;; Return the description found upstream for package NAME, or #f.
  130. (and=> (find (lambda (alist)
  131. (equal? name (assoc-ref alist "package")))
  132. db)
  133. (lambda (record)
  134. (let ((field (assoc-ref record "blurb")))
  135. ;; The upstream description file uses "redirect PACKAGE" as
  136. ;; a blurb in cases where the description of the two
  137. ;; packages should be considered the same (e.g., GTK+ has
  138. ;; "redirect gnome".) This is usually not acceptable for
  139. ;; us because we prefer to have distinct descriptions in
  140. ;; such cases. Thus, ignore the 'blurb' field when that
  141. ;; happens.
  142. (and field
  143. (not (string-prefix? "redirect " field))
  144. field)))))))
  145. (map (lambda (alist)
  146. (let ((name (assoc-ref alist "package")))
  147. (alist->record `(("description" . ,(official-description name))
  148. ,@alist)
  149. make-gnu-package-descriptor
  150. (list "package" "mundane_name" "copyright_holder"
  151. "savannah" "fsd" "language" "logo"
  152. "doc_category" "doc_summary" "description"
  153. "doc_url"
  154. "download_url")
  155. '("doc_url" "language"))))
  156. (let* ((port (fetch %package-list-url #:text? #t))
  157. (lst (read-records port)))
  158. (close-port port)
  159. lst)))
  160. (define (find-package name)
  161. "Find GNU package called NAME and return it. Return #f if it was not
  162. found."
  163. (find (lambda (package)
  164. (string=? name (gnu-package-name package)))
  165. (official-gnu-packages)))
  166. (define gnu-package?
  167. (let ((official-gnu-packages (memoize official-gnu-packages)))
  168. (mlambdaq (package)
  169. "Return true if PACKAGE is a GNU package. This procedure may access the
  170. network to check in GNU's database."
  171. (define (mirror-type url)
  172. (let ((uri (string->uri url)))
  173. (and (eq? (uri-scheme uri) 'mirror)
  174. (cond
  175. ((member (uri-host uri)
  176. '("gnu" "gnupg" "gcc" "gnome"))
  177. ;; Definitely GNU.
  178. 'gnu)
  179. ((equal? (uri-host uri) "cran")
  180. ;; Possibly GNU: mirror://cran could be either GNU R itself
  181. ;; or a non-GNU package.
  182. #f)
  183. (else
  184. ;; Definitely non-GNU.
  185. 'non-gnu)))))
  186. (define (gnu-home-page? package)
  187. (letrec-syntax ((>> (syntax-rules ()
  188. ((_ value proc)
  189. (and=> value proc))
  190. ((_ value proc rest ...)
  191. (and=> value
  192. (lambda (next)
  193. (>> (proc next) rest ...)))))))
  194. (>> package package-home-page
  195. string->uri uri-host
  196. (lambda (host)
  197. (member host '("www.gnu.org" "gnu.org"))))))
  198. (or (gnu-home-page? package)
  199. (match (package-source package)
  200. ((? origin? origin)
  201. (let ((url (origin-uri origin))
  202. (name (package-upstream-name package)))
  203. (case (and (string? url) (mirror-type url))
  204. ((gnu) #t)
  205. ((non-gnu) #f)
  206. (else
  207. (and (member name (map gnu-package-name (official-gnu-packages)))
  208. #t)))))
  209. (_ #f))))))
  210. ;;;
  211. ;;; Latest FTP release.
  212. ;;;
  213. (define (ftp-server/directory package)
  214. "Return the FTP server and directory where PACKAGE's tarball are stored."
  215. (let ((name (package-upstream-name package)))
  216. (values (or (assoc-ref (package-properties package) 'ftp-server)
  217. "ftp.gnu.org")
  218. (or (assoc-ref (package-properties package) 'ftp-directory)
  219. (string-append "/gnu/" name)))))
  220. (define %tarball-rx
  221. ;; The .zip extensions is notably used for freefont-ttf.
  222. ;; The "-src" pattern is for "TeXmacs-1.0.7.9-src.tar.gz".
  223. ;; The "-gnu[0-9]" pattern is for "icecat-38.4.0-gnu1.tar.bz2".
  224. ;; Accept underscores as in "PKG_1.2.tar.gz" for some non-GNU packages.
  225. ;; Accept 'v' or 'V' prefix as in 'PKG-v2.3.tgz'.
  226. (make-regexp "^([^.]+)[-_][vV]?([0-9]|[^-])+(-(src|[sS]ource|gnu[0-9]))?\\.(tar\\.|tgz|zip$)"))
  227. (define %alpha-tarball-rx
  228. (make-regexp "^.*-.*[0-9](-|~|\\.)?(alpha|beta|rc|RC|cvs|svn|git)-?[0-9\\.]*\\.tar\\."))
  229. (define (release-file? project file)
  230. "Return #f if FILE is not a release tarball of PROJECT, otherwise return
  231. true."
  232. (and (not (member (file-extension file)
  233. '("sig" "sign" "asc"
  234. "md5sum" "sha1sum" "sha256sum")))
  235. (and=> (regexp-exec %tarball-rx file)
  236. (lambda (match)
  237. ;; Filter out unrelated files, like `guile-www-1.1.1'.
  238. ;; Case-insensitive for things like "TeXmacs" vs. "texmacs".
  239. ;; The "-src" suffix is for "freefont-src-20120503.tar.gz".
  240. (and=> (match:substring match 1)
  241. (lambda (name)
  242. (or (string-ci=? name project)
  243. (string-ci=? name
  244. (string-append project
  245. "-src")))))))
  246. (not (regexp-exec %alpha-tarball-rx file))
  247. (let ((s (tarball-sans-extension file)))
  248. (regexp-exec %package-name-rx s))))
  249. (define (tarball->version tarball)
  250. "Return the version TARBALL corresponds to. TARBALL is a file name like
  251. \"coreutils-8.23.tar.xz\"."
  252. (let-values (((name version)
  253. (gnu-package-name->name+version
  254. (tarball-sans-extension tarball))))
  255. version))
  256. (define* (releases project
  257. #:key
  258. (server "ftp.gnu.org")
  259. (directory (string-append "/gnu/" project)))
  260. "Return the list of <upstream-release> of PROJECT as a list of release
  261. name/directory pairs."
  262. ;; TODO: Parse something like fencepost.gnu.org:/gd/gnuorg/packages-ftp.
  263. (define conn (ftp-open server))
  264. (let loop ((directories (list directory))
  265. (result '()))
  266. (match directories
  267. (()
  268. (ftp-close conn)
  269. (coalesce-sources result))
  270. ((directory rest ...)
  271. (let* ((files (ftp-list conn directory))
  272. (subdirs (filter-map (match-lambda
  273. ((name 'directory . _) name)
  274. (_ #f))
  275. files)))
  276. (define (file->url file)
  277. (string-append "ftp://" server directory "/" file))
  278. (define (file->source file)
  279. (let ((url (file->url file)))
  280. (upstream-source
  281. (package project)
  282. (version (tarball->version file))
  283. (urls (list url))
  284. (signature-urls (list (string-append url ".sig"))))))
  285. (loop (append (map (cut string-append directory "/" <>)
  286. subdirs)
  287. rest)
  288. (append
  289. ;; Filter out signatures, deltas, and files which
  290. ;; are potentially not releases of PROJECT--e.g.,
  291. ;; in /gnu/guile, filter out guile-oops and
  292. ;; guile-www; in mit-scheme, filter out binaries.
  293. (filter-map (match-lambda
  294. ((file 'file . _)
  295. (and (release-file? project file)
  296. (file->source file)))
  297. (_ #f))
  298. files)
  299. result)))))))
  300. (define* (import-ftp-release project
  301. #:key
  302. (version #f)
  303. (server "ftp.gnu.org")
  304. (directory (string-append "/gnu/" project))
  305. (file->signature (cut string-append <> ".sig")))
  306. "Return an <upstream-source> for the latest release of PROJECT on SERVER
  307. under DIRECTORY, or #f. Optionally include a VERSION string to fetch a specific version.
  308. Use FTP-OPEN and FTP-CLOSE to open (resp. close) FTP connections; this can be
  309. useful to reuse connections.
  310. FILE->SIGNATURE must be a procedure; it is passed a source file URL and must
  311. return the corresponding signature URL, or #f it signatures are unavailable."
  312. (define (latest a b)
  313. (if (version>? a b) a b))
  314. (define (latest-release a b)
  315. (if (version>? (upstream-source-version a) (upstream-source-version b))
  316. a b))
  317. (define patch-directory-name?
  318. ;; Return #t for patch directory names such as 'bash-4.2-patches'.
  319. (cut string-suffix? "patches" <>))
  320. (define conn (ftp-open server #:timeout 5))
  321. (define (file->url directory file)
  322. (string-append "ftp://" server directory "/" file))
  323. (define (file->source directory file)
  324. (let ((url (file->url directory file)))
  325. (upstream-source
  326. (package project)
  327. (version (tarball->version file))
  328. ;; uri-mirror-rewrite: Don't turn nice mirror:// URIs into ftp://
  329. ;; URLs during "guix refresh -u".
  330. (urls (list (uri-mirror-rewrite url)))
  331. (signature-urls (match (file->signature url)
  332. (#f #f)
  333. (sig (list (uri-mirror-rewrite sig))))))))
  334. (let loop ((directory directory)
  335. (result #f))
  336. (let* ((entries (catch 'ftp-error
  337. (lambda _ (ftp-list conn directory))
  338. (const '())))
  339. ;; Filter out things like /gnupg/patches. Filter out "w32"
  340. ;; directories as found on ftp.gnutls.org.
  341. (subdirs (filter-map (match-lambda
  342. (((? patch-directory-name? dir)
  343. 'directory . _)
  344. #f)
  345. (("w32" 'directory . _)
  346. #f)
  347. (("unstable" 'directory . _)
  348. ;; As seen at ftp.gnupg.org/gcrypt/pinentry.
  349. #f)
  350. ((directory 'directory . _)
  351. directory)
  352. (_ #f))
  353. entries))
  354. ;; Whether or not SUBDIRS is empty, compute the latest releases
  355. ;; for the current directory. This is necessary for packages
  356. ;; such as 'sharutils' that have a sub-directory that contains
  357. ;; only an older release.
  358. (releases (filter-map (match-lambda
  359. ((file 'file . _)
  360. (and (release-file? project file)
  361. (file->source directory file)))
  362. (_ #f))
  363. entries)))
  364. ;; Assume that SUBDIRS correspond to versions, and jump into the
  365. ;; one with the highest version number.
  366. (let* ((release (if version
  367. (find (lambda (upstream)
  368. (string=? (upstream-source-version upstream) version))
  369. (coalesce-sources releases))
  370. (reduce latest-release #f
  371. (coalesce-sources releases))))
  372. (result (if (and result release)
  373. (latest-release release result)
  374. (or release result)))
  375. (target (reduce latest #f subdirs)))
  376. (if target
  377. (loop (string-append directory "/" target)
  378. result)
  379. (begin
  380. (ftp-close conn)
  381. result))))))
  382. (define* (import-release package
  383. #:key
  384. (version #f)
  385. (server "ftp.gnu.org")
  386. (directory (string-append "/gnu/" package)))
  387. "Return the <upstream-source> for the latest version of PACKAGE or #f.
  388. PACKAGE must be the canonical name of a GNU package. Optionally include a
  389. VERSION string to fetch a specific version."
  390. (import-ftp-release package
  391. #:version version
  392. #:server server
  393. #:directory directory))
  394. (define-syntax-rule (false-if-ftp-error exp)
  395. "Return #f if an FTP error is raise while evaluating EXP; return the result
  396. of EXP otherwise."
  397. (catch 'ftp-error
  398. (lambda ()
  399. exp)
  400. (lambda (key port . rest)
  401. (if (ftp-connection? port)
  402. (ftp-close port)
  403. (close-port port))
  404. #f)))
  405. (define* (import-release* package #:key (version #f))
  406. "Like 'import-release', but (1) take a <package> object, and (2) ignore FTP
  407. errors that might occur when PACKAGE is not actually a GNU package, or not
  408. hosted on ftp.gnu.org, or not under that name (this is the case for
  409. \"emacs-auctex\", for instance.)"
  410. (let-values (((server directory)
  411. (ftp-server/directory package)))
  412. (false-if-ftp-error (import-release (package-upstream-name package)
  413. #:version version
  414. #:server server
  415. #:directory directory))))
  416. ;;;
  417. ;;; Latest HTTP release.
  418. ;;;
  419. (define (html-links sxml)
  420. "Return the list of links found in SXML, the SXML tree of an HTML page."
  421. (let loop ((sxml sxml)
  422. (links '()))
  423. (match sxml
  424. (('a ('@ attributes ...) body ...)
  425. (match (assq 'href attributes)
  426. (#f (fold loop links body))
  427. (('href url) (fold loop (cons url links) body))))
  428. ((tag ('@ _ ...) body ...)
  429. (fold loop links body))
  430. ((tag body ...)
  431. (fold loop links body))
  432. (_
  433. links))))
  434. (define* (import-html-release package
  435. #:key
  436. (version #f)
  437. (base-url "https://kernel.org/pub")
  438. (directory (string-append "/" package))
  439. file->signature)
  440. "Return an <upstream-source> for the latest release of PACKAGE (a string) on
  441. SERVER under DIRECTORY, or #f. Optionally include a VERSION string to fetch a
  442. specific version.
  443. BASE-URL should be the URL of an HTML page, typically a directory listing as
  444. found on 'https://kernel.org/pub'.
  445. When FILE->SIGNATURE is omitted or #f, guess the detached signature file name,
  446. if any. Otherwise, FILE->SIGNATURE must be a procedure; it is passed a source
  447. file URL and must return the corresponding signature URL, or #f it signatures
  448. are unavailable."
  449. (let* ((uri (string->uri (if (string-null? directory)
  450. base-url
  451. (string-append base-url directory "/"))))
  452. (port (http-fetch/cached uri #:ttl 3600))
  453. (sxml (html->sxml port))
  454. (links (delete-duplicates (html-links sxml))))
  455. (define (file->signature/guess url)
  456. (let ((base (basename url)))
  457. (any (lambda (link)
  458. (any (lambda (extension)
  459. (and (string=? (string-append base extension)
  460. (basename link))
  461. (string-append url extension)))
  462. '(".asc" ".sig" ".sign")))
  463. links)))
  464. (define (url->release url)
  465. (let* ((base (basename url))
  466. (base-url (string-append base-url directory))
  467. (url (cond ((and=> (string->uri url) uri-scheme) ;full URL?
  468. url)
  469. ;; full URL, except for URI scheme. Reuse the URI
  470. ;; scheme of the document that contains the link.
  471. ((string-prefix? "//" url)
  472. (string-append
  473. (symbol->string (uri-scheme (string->uri base-url)))
  474. ":" url))
  475. ((string-prefix? "/" url) ;absolute path?
  476. (let ((uri (string->uri base-url)))
  477. (uri->string
  478. (build-uri (uri-scheme uri)
  479. #:host (uri-host uri)
  480. #:port (uri-port uri)
  481. #:path url))))
  482. ;; URL is a relative path and BASE-URL may or may not
  483. ;; end in slash.
  484. ((string-suffix? "/" base-url)
  485. (string-append base-url url))
  486. (else
  487. ;; If DIRECTORY is non-empty, assume BASE-URL
  488. ;; denotes a directory; otherwise, assume BASE-URL
  489. ;; denotes a file within a directory, and that URL
  490. ;; is relative to that directory.
  491. (string-append (if (string-null? directory)
  492. (dirname base-url)
  493. base-url)
  494. "/" url)))))
  495. (and (release-file? package base)
  496. (let ((version (tarball->version base)))
  497. (upstream-source
  498. (package package)
  499. (version version)
  500. ;; uri-mirror-rewrite: Don't turn nice mirror:// URIs into ftp://
  501. ;; URLs during "guix refresh -u".
  502. (urls (list (uri-mirror-rewrite url)))
  503. (signature-urls
  504. (and=> ((or file->signature file->signature/guess) url)
  505. (lambda (url) (list (uri-mirror-rewrite url))))))))))
  506. (define candidates
  507. (filter-map url->release links))
  508. (close-port port)
  509. (match candidates
  510. (() #f)
  511. ((first . _)
  512. (if version
  513. ;; find matching release version and return it
  514. (find (lambda (upstream)
  515. (string=? (upstream-source-version upstream) version))
  516. (coalesce-sources candidates))
  517. ;; Select the most recent release and return it.
  518. (reduce (lambda (r1 r2)
  519. (if (version>? (upstream-source-version r1)
  520. (upstream-source-version r2))
  521. r1 r2))
  522. first
  523. (coalesce-sources candidates)))))))
  524. ;;;
  525. ;;; Updaters.
  526. ;;;
  527. (define %gnu-file-list-uri
  528. ;; URI of the file list for ftp.gnu.org.
  529. (string->uri "https://ftp.gnu.org/find.txt.gz"))
  530. (define ftp.gnu.org-files
  531. (mlambda ()
  532. "Return the list of files available at ftp.gnu.org."
  533. ;; XXX: Memoize the whole procedure to work around the fact that
  534. ;; 'http-fetch/cached' caches the gzipped version.
  535. (define (trim-leading-components str)
  536. ;; Trim the leading ".", if any, in "./gnu/foo".
  537. (string-trim str (char-set #\.)))
  538. (define (string->lines str)
  539. (string-tokenize str (char-set-complement (char-set #\newline))))
  540. ;; Since https://ftp.gnu.org honors 'If-Modified-Since', the hard-coded
  541. ;; TTL can be relatively short.
  542. (let ((port (http-fetch/cached %gnu-file-list-uri #:ttl (* 15 60))))
  543. (map trim-leading-components
  544. (call-with-gzip-input-port port
  545. (compose string->lines get-string-all))))))
  546. (define* (import-gnu-release package #:key (version #f))
  547. "Return the latest release of PACKAGE, a GNU package available via
  548. ftp.gnu.org. Optionally include a VERSION string to fetch a specific version.
  549. This method does not rely on FTP access at all; instead, it browses the file
  550. list available from %GNU-FILE-LIST-URI over HTTP(S)."
  551. (define archive-type
  552. (package-archive-type package))
  553. (define (better-tarball? tarball1 tarball2)
  554. (string=? (file-extension tarball1) archive-type))
  555. (define (find-latest-tarball-version tarballs)
  556. (fold (lambda (file1 file2)
  557. (if (and file2
  558. (version>? (tarball-sans-extension (basename file2))
  559. (tarball-sans-extension (basename file1))))
  560. file2
  561. file1))
  562. #f
  563. tarballs))
  564. (let-values (((server directory)
  565. (ftp-server/directory package))
  566. ((name)
  567. (package-upstream-name package)))
  568. (let* ((files (ftp.gnu.org-files))
  569. ;; select tarballs for this package
  570. (relevant (filter (lambda (file)
  571. (and (string-prefix? "/gnu" file)
  572. (string-contains file directory)
  573. (release-file? name (basename file))))
  574. files))
  575. ;; find latest version
  576. (version (or version
  577. (and (not (null? relevant))
  578. (tarball->version
  579. (find-latest-tarball-version relevant)))))
  580. ;; find tarballs matching this version
  581. (tarballs (filter (lambda (file)
  582. (string=? version (tarball->version file)))
  583. relevant)))
  584. (match tarballs
  585. (() #f)
  586. (_
  587. (upstream-source
  588. (package name)
  589. (version version)
  590. (urls (map (lambda (file)
  591. (string-append "mirror://gnu/"
  592. (string-drop file
  593. (string-length "/gnu/"))))
  594. ;; Sort so that the tarball with the same compression
  595. ;; format as currently used in PACKAGE comes first.
  596. (sort tarballs better-tarball?)))
  597. (signature-urls (map (cut string-append <> ".sig") urls))))))))
  598. (define %package-name-rx
  599. ;; Regexp for a package name, e.g., "foo-X.Y". Since TeXmacs uses
  600. ;; "TeXmacs-X.Y-src", the `-src' suffix is allowed.
  601. (make-regexp "^(.*)[-_][vV]?(([0-9]|\\.)+)(-src|\\.src|\\.orig)?"))
  602. (define (gnu-package-name->name+version name+version)
  603. "Return the package name and version number extracted from NAME+VERSION."
  604. (let ((match (regexp-exec %package-name-rx name+version)))
  605. (if (not match)
  606. (values name+version #f)
  607. (values (match:substring match 1) (match:substring match 2)))))
  608. (define gnome-package?
  609. (url-prefix-predicate "mirror://gnome/"))
  610. (define (pure-gnu-package? package)
  611. "Return true if PACKAGE is a non-Emacs and non-GNOME GNU package. This
  612. excludes AucTeX, for instance, whose releases are now uploaded to
  613. elpa.gnu.org, GNU Radio, which has releases at www.gnuradio.org, and all the
  614. GNOME packages; EMMS is included though, because its releases are on gnu.org."
  615. (and (or (not (string-prefix? "emacs-" (package-name package)))
  616. (gnu-hosted? package))
  617. (not (gnome-package? package))
  618. (not (string-prefix? "gnuradio" (package-name package)))
  619. (gnu-package? package)))
  620. (define gnu-hosted?
  621. (url-prefix-predicate "mirror://gnu/"))
  622. (define (uri-mirror-rewrite uri)
  623. "Rewrite URI to a mirror:// URI if possible, or return URI unmodified."
  624. (if (string-prefix? "mirror://" uri)
  625. uri ;nothing to do, it's already a mirror URI
  626. (let loop ((mirrors %mirrors))
  627. (match mirrors
  628. (()
  629. uri)
  630. (((mirror-id mirror-urls ...) rest ...)
  631. (match (find (cut string-prefix? <> uri) mirror-urls)
  632. (#f
  633. (loop rest))
  634. (prefix
  635. (format #f "mirror://~a/~a"
  636. mirror-id
  637. (string-drop uri (string-length prefix))))))))))
  638. (define %savannah-base
  639. ;; One of the Savannah mirrors listed at
  640. ;; <https://download.savannah.gnu.org/mirmon/savannah/> that serves valid
  641. ;; HTML (unlike <https://download.savannah.nongnu.org/releases>.)
  642. "https://de.freedif.org/savannah/")
  643. (define* (import-savannah-release package #:key (version #f))
  644. "Return the latest release of PACKAGE. Optionally include a VERSION string
  645. to fetch a specific version."
  646. (let* ((uri (string->uri
  647. (match (origin-uri (package-source package))
  648. ((? string? uri) uri)
  649. ((uri mirrors ...) uri))))
  650. (package (package-upstream-name package))
  651. (directory (dirname (uri-path uri))))
  652. ;; Note: We use the default 'file->signature', which adds ".sig", ".asc",
  653. ;; or whichever detached signature naming scheme PACKAGE uses.
  654. (import-html-release package
  655. #:version version
  656. #:base-url %savannah-base
  657. #:directory directory)))
  658. (define* (latest-sourceforge-release package #:key (version #f))
  659. "Return the latest release of PACKAGE. Optionally include a VERSION string
  660. to fetch a specific version."
  661. (define (uri-append uri extension)
  662. ;; Return URI with EXTENSION appended.
  663. (build-uri (uri-scheme uri)
  664. #:host (uri-host uri)
  665. #:path (string-append (uri-path uri) extension)))
  666. (define (valid-uri? uri port)
  667. ;; Return true if URI is reachable.
  668. (false-if-exception
  669. (case (response-code (http-head uri #:port port #:keep-alive? #t))
  670. ((200 302) #t)
  671. (else #f))))
  672. (when version
  673. (error
  674. (formatted-message
  675. (G_ "Updating to a specific version is not yet implemented for ~a, sorry.")
  676. "sourceforge")))
  677. (let* ((name (package-upstream-name package))
  678. (base (string-append "https://sourceforge.net/projects/"
  679. name "/files"))
  680. (url (string-append base "/latest/download"))
  681. (uri (string->uri url))
  682. (port (false-if-exception (open-socket-for-uri uri)))
  683. (response (and port
  684. (http-head uri #:port port #:keep-alive? #t))))
  685. (dynamic-wind
  686. (const #t)
  687. (lambda ()
  688. (and response
  689. (= 302 (response-code response))
  690. (response-location response)
  691. (match (string-tokenize (uri-path (response-location response))
  692. (char-set-complement (char-set #\/)))
  693. ((_ components ...)
  694. (let* ((path (string-join components "/"))
  695. (url (string-append "mirror://sourceforge/" path)))
  696. (and (release-file? name (basename path))
  697. ;; Take the heavy-handed approach of probing 3 additional
  698. ;; URLs. XXX: Would be nicer if this could be avoided.
  699. (let* ((loc (response-location response))
  700. (sig (any (lambda (extension)
  701. (let ((uri (uri-append loc extension)))
  702. (and (valid-uri? uri port)
  703. (string-append url extension))))
  704. '(".asc" ".sig" ".sign"))))
  705. (upstream-source
  706. (package name)
  707. (version (tarball->version (basename path)))
  708. (urls (list url))
  709. (signature-urls (and sig (list sig)))))))))))
  710. (lambda ()
  711. (when port
  712. (close-port port))))))
  713. (define* (import-xorg-release package #:key (version #f))
  714. "Return the latest release of PACKAGE. Optionally include a VERSION string
  715. to fetch a specific version."
  716. (let ((uri (string->uri (origin-uri (package-source package)))))
  717. (false-if-ftp-error
  718. (import-ftp-release
  719. (package-name package)
  720. #:version version
  721. #:server "ftp.freedesktop.org"
  722. #:directory
  723. (string-append "/pub/xorg/" (dirname (uri-path uri)))))))
  724. (define* (import-kernel.org-release package #:key (version #f))
  725. "Return the latest release of PACKAGE, the name of a kernel.org package.
  726. Optionally include a VERSION string to fetch a specific version."
  727. (define %kernel.org-base
  728. ;; This URL and sub-directories thereof are nginx-generated directory
  729. ;; listings suitable for 'import-html-release'.
  730. "https://mirrors.edge.kernel.org/pub")
  731. (define (file->signature file)
  732. (string-append (file-sans-extension file) ".sign"))
  733. (let* ((uri (string->uri
  734. (match (origin-uri (package-source package))
  735. ((? string? uri) uri)
  736. ((uri mirrors ...) uri))))
  737. (package (package-upstream-name package))
  738. (directory (dirname (uri-path uri))))
  739. (import-html-release package
  740. #:version version
  741. #:base-url %kernel.org-base
  742. #:directory directory
  743. #:file->signature file->signature)))
  744. (define html-updatable-package?
  745. ;; Return true if the given package may be handled by the generic HTML
  746. ;; updater.
  747. (let ((hosting-sites '("github.com" "github.io" "gitlab.com"
  748. "notabug.org" "sr.ht" "gitlab.inria.fr"
  749. "ftp.gnu.org" "download.savannah.gnu.org"
  750. "pypi.org" "crates.io" "rubygems.org"
  751. "bioconductor.org")))
  752. (define http-url?
  753. (url-predicate (lambda (url)
  754. (match (string->uri url)
  755. (#f #f)
  756. (uri
  757. (let ((scheme (uri-scheme uri))
  758. (host (uri-host uri)))
  759. (and (memq scheme '(http https))
  760. (not (member host hosting-sites)))))))))
  761. (lambda (package)
  762. (or (assoc-ref (package-properties package) 'release-monitoring-url)
  763. (http-url? package)))))
  764. (define* (import-html-updatable-release package #:key (version #f))
  765. "Return the latest release of PACKAGE. Do that by crawling the HTML page of
  766. the directory containing its source tarball. Optionally include a VERSION
  767. string to fetch a specific version."
  768. (let* ((uri (string->uri
  769. (match (origin-uri (package-source package))
  770. ((? string? url) url)
  771. ((url _ ...) url))))
  772. (custom (assoc-ref (package-properties package)
  773. 'release-monitoring-url))
  774. (base (or custom
  775. (string-append (symbol->string (uri-scheme uri))
  776. "://" (uri-host uri))))
  777. (directory (if custom
  778. ""
  779. (dirname (uri-path uri))))
  780. (package (package-upstream-name package)))
  781. (false-if-networking-error
  782. (import-html-release package
  783. #:version version
  784. #:base-url base
  785. #:directory directory))))
  786. (define %gnu-updater
  787. ;; This is for everything at ftp.gnu.org.
  788. (upstream-updater
  789. (name 'gnu)
  790. (description "Updater for GNU packages")
  791. (pred (lambda (package)
  792. (false-if-networking-error (gnu-hosted? package))))
  793. (import import-gnu-release)))
  794. (define %gnu-ftp-updater
  795. ;; This is for GNU packages taken from alternate locations, such as
  796. ;; alpha.gnu.org, ftp.gnupg.org, etc. It is obsolescent.
  797. (upstream-updater
  798. (name 'gnu-ftp)
  799. (description "Updater for GNU packages only available via FTP")
  800. (pred (lambda (package)
  801. (false-if-networking-error
  802. (and (not (gnu-hosted? package))
  803. (pure-gnu-package? package)))))
  804. (import import-release*)))
  805. (define %savannah-updater
  806. (upstream-updater
  807. (name 'savannah)
  808. (description "Updater for packages hosted on savannah.gnu.org")
  809. (pred (url-prefix-predicate "mirror://savannah/"))
  810. (import import-savannah-release)))
  811. (define %sourceforge-updater
  812. (upstream-updater
  813. (name 'sourceforge)
  814. (description "Updater for packages hosted on sourceforge.net")
  815. (pred (url-prefix-predicate "mirror://sourceforge/"))
  816. (import latest-sourceforge-release)))
  817. (define %xorg-updater
  818. (upstream-updater
  819. (name 'xorg)
  820. (description "Updater for X.org packages")
  821. (pred (url-prefix-predicate "mirror://xorg/"))
  822. (import import-xorg-release)))
  823. (define %kernel.org-updater
  824. (upstream-updater
  825. (name 'kernel.org)
  826. (description "Updater for packages hosted on kernel.org")
  827. (pred (url-prefix-predicate "mirror://kernel.org/"))
  828. (import import-kernel.org-release)))
  829. (define %generic-html-updater
  830. (upstream-updater
  831. (name 'generic-html)
  832. (description "Updater that crawls HTML pages.")
  833. (pred html-updatable-package?)
  834. (import import-html-updatable-release)))
  835. ;;; gnu-maintenance.scm ends here