summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Cournoyer <[email protected]>2024-11-13 14:21:16 +0900
committerMaxim Cournoyer <[email protected]>2025-02-28 13:36:44 +0900
commitf13f0769688493271f43f31a016957355dbecb30 (patch)
treeedd293eef86fd7fb40bab485bf859f4cbb032a6f
parent79a46d65370418dde29b303bebcb487355564f98 (diff)
refresh: Add support for partial target versions.
* guix/import/utils.scm (find-version): New procedure. * guix/scripts/refresh.scm (<update-spec>) [partial?]: New field. (update-spec-partial?): New accessor. (update-spec): Add a PARTIAL? optional argument. (update-specification->update-spec) <update-spec>: Call with its new PARTIAL? optional argument when FALLBACK-VERSION is provided, i.e. when '--target-version' was used. (update-package): Remove the PACKAGE and VERSION positional arguments, and replace them with UPDATE-SPEC. Update doc. Call `package-update' with its new #:partial-version? argument. (check-for-package-update) <package-latest-release>: Pass the new #:partial-version? argument to it. (guix-refresh) <update-package>: Adjust call accordingly. (show-help): Udate doc. * guix/upstream.scm (package-latest-release): Add #:partial-version? argument, and apply it to the importer call. (package-update): Add #:partial-version?> argument. Update doc. Pass it to the `package-latest-release' call. * guix/gnu-maintenance.scm (rewrite-url): Add #:partial-version? argument. Update doc. Crawl URL for newer compatible versions when provided. (import-html-release): Add #:partial-version? argument, and pass it to the `rewrite-url' call. Use `find-version' to find the best version. (import-release, import-ftp-release, import-gnu-release) (import-release*): Add #:partial-version? argument and honor it. (import-html-updatable-release): Add #:partial-version? argument, and pass it to the `import-html-release' call. * guix/import/gnome.scm (import-gnome-release) <#:partial-version?>: Add new argument and honor it. * guix/import/texlive.scm (latest-texlive-tag): Rename to... (texlive-tags): ... this, and have it return all tags. (texlive->guix-package): Adjust accordingly. (latest-release): Add a #:partial-version? argument. Update doc. * guix/import/stackage.scm (latest-lts-release): New #:partial-version? argument. * guix/import/pypi.scm (import-release): New #:partial-version? argument; pass it to `pypi-package->upstream-source'. * guix/import/opam.scm (latest-release): New #:partial-version? argument. * guix/import/minetest.scm (latest-minetest-release): New #:partial-version? argument. (pypi-package->upstream-source): New #:partial-version? argument. Update doc. * guix/import/launchpad.scm (latest-released-version): Rename to... (release-versions): ... this, making it return all versions. (import-release) <#:partial-version?>: New argument. * guix/import/kde.scm (import-kde-release) <#:partial-version?>: New argument. Update doc. Refactor to honor argument. * guix/import/hexpm.scm (lookup-hexpm): Update doc. (hexpm-latest-release): Rename to... (hexpm-releases): ... this; return all release strings. (hexpm->guix-package): Adjust accordingly. (import-release): Add and honor a #:partial-version? argument. Update doc. * guix/import/hackage.scm (import-release): New #:partial-version? argument. * guix/import/cpan.scm (latest-release): New #:partial-version? argument. * guix/import/crate.scm (max-crate-version-of-semver): Improve doc. (import-release): Add a #:partial-version? argument and honor it. * guix/import/egg.scm (find-latest-version): Rename to... (get-versions): ... this, returning all versions. (egg-metadata): Adjust accordingly. (egg->guix-package): Likewise. (import-release): Add a new #:partial-version? argument and honor it. * guix/import/elpa.scm (latest-release): New #:partial-version? argument. * guix/import/gem.scm (get-versions): New procedure. (import-release): Add a new #:partial-version? argument and honor it. * guix/import/git.scm (version-mapping): Update doc; streamline a bit. (latest-tag): Rename to... (get-tags): ... this, dropping the #:version keyword and returning the complete tags alist. Update doc. (latest-git-tag-version): Rename to... (get-package-tags): ... this, returning the complete tags alist of the package. Update doc. (import-git-release): Add a new #:partial-version? argument and honor it. Update doc. * guix/import/github.scm (latest-released-version): Rename to... (get-package-tags): ... this, returning all tags. Update doc. (import-release): Add a new #:partial-version? argument and honor it. * guix/import/cran.scm (latest-cran-release) (latest-bioconductor-release): Add #:partial-version? argument. * guix/import/composer.scm (latest-version): Delete procedure. (composer-fetch): Add #:partial-version? keyword and honor it. Update doc. (import-release): Likewise. * guix/import/test.scm (import-release): Add #:partial-version? argument. * tests/guix-refresh.sh: Add test. * tests/gem.scm (test-foo-versions-json): New variable. (package-latest-release): Mock new URL. * tests/import-git.scm (latest-git-tag-version): New procedure. * tests/gnu-maintenance.scm (libuv-dist-html) (libuv-dist-1.46.0-html, libuv-dist-1.44.2-html) (libuv-html-data): New variables. (mock-http-fetch/cached): New procedure. ("rewrite-url, without to-version"): Rewrite using the above. ("rewrite-url, partial to-version"): New test. * doc/guix.texi <"Invoking guix refresh">: Update doc. Series-to: [email protected] Change-Id: I092a58b57ac42e54a2fa55e7761e8c6993af8ad4
-rw-r--r--doc/guix.texi12
-rw-r--r--guix/gnu-maintenance.scm120
-rw-r--r--guix/import/composer.scm59
-rw-r--r--guix/import/cpan.scm2
-rw-r--r--guix/import/cran.scm4
-rw-r--r--guix/import/crate.scm51
-rw-r--r--guix/import/egg.scm33
-rw-r--r--guix/import/elpa.scm2
-rw-r--r--guix/import/gem.scm29
-rw-r--r--guix/import/git.scm103
-rw-r--r--guix/import/github.scm114
-rw-r--r--guix/import/gnome.scm50
-rw-r--r--guix/import/hackage.scm2
-rw-r--r--guix/import/hexpm.scm42
-rw-r--r--guix/import/kde.scm57
-rw-r--r--guix/import/launchpad.scm36
-rw-r--r--guix/import/minetest.scm2
-rw-r--r--guix/import/opam.scm2
-rw-r--r--guix/import/pypi.scm25
-rw-r--r--guix/import/stackage.scm2
-rw-r--r--guix/import/test.scm15
-rw-r--r--guix/import/texlive.scm38
-rw-r--r--guix/import/utils.scm20
-rw-r--r--guix/scripts/refresh.scm186
-rw-r--r--guix/upstream.scm26
-rw-r--r--tests/gem.scm22
-rw-r--r--tests/gnu-maintenance.scm65
-rw-r--r--tests/guix-refresh.sh10
-rw-r--r--tests/import-git.scm4
29 files changed, 630 insertions, 503 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index 62a4028659..93380dc30d 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -14975,6 +14975,7 @@ gnu/packages/guile.scm:147:2: guile: updating from version 2.0.10 to version 2.0
@dots{}
@end example
+@cindex target version, guix refresh
In some specific cases, you may have many packages specified via a
manifest or a module selection which should all be updated together; for
these cases, the @option{--target-version} option can be provided to have
@@ -14995,6 +14996,17 @@ gnu/packages/qt.scm:2070:13: qtquickcontrols2 would be upgraded from 5.15.8 to 5
@dots{}
@end example
+@cindex partial target version, guix refresh
+The @option{--target-version} option accepts partial version prefixes,
+which can be useful to update to the latest major or major-minor
+prefixed version:
+
+@example
+$ guix refresh qtbase@@5 qtdeclarative@@5 --target-version=5
+gnu/packages/qt.scm:1472:13: qtdeclarative would be upgraded from 5.15.8 to 5.15.10
+gnu/packages/qt.scm:452:13: qtbase would be upgraded from 5.15.8 to 5.15.10
+@end example
+
Sometimes the upstream name differs from the package name used in Guix,
and @command{guix refresh} needs a little help. Most updaters honor the
@code{upstream-name} property in package definitions, which can be used
diff --git a/guix/gnu-maintenance.scm b/guix/gnu-maintenance.scm
index ee4882326f..f26d8c5fbc 100644
--- a/guix/gnu-maintenance.scm
+++ b/guix/gnu-maintenance.scm
@@ -3,7 +3,7 @@
;;; Copyright © 2012, 2013 Nikita Karetnikov <[email protected]>
;;; Copyright © 2021 Simon Tournier <[email protected]>
;;; Copyright © 2022 Maxime Devos <[email protected]>
-;;; Copyright © 2023 Maxim Cournoyer <[email protected]>
+;;; Copyright © 2023, 2025 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -44,7 +44,7 @@
#:use-module (guix records)
#:use-module (guix upstream)
#:use-module (guix packages)
- #:autoload (guix import utils) (false-if-networking-error)
+ #:autoload (guix import utils) (false-if-networking-error find-version)
#:autoload (zlib) (call-with-gzip-input-port)
#:autoload (htmlprag) (html->sxml) ;from Guile-Lib
#:export (gnu-package-name
@@ -346,12 +346,15 @@ name/directory pairs."
(define* (import-ftp-release project
#:key
- (version #f)
+ version
+ partial-version?
(server "ftp.gnu.org")
(directory (string-append "/gnu/" project))
(file->signature (cut string-append <> ".sig")))
"Return an <upstream-source> for the latest release of PROJECT on SERVER
-under DIRECTORY, or #f. Optionally include a VERSION string to fetch a specific version.
+under DIRECTORY, or #f. Optionally include a VERSION string to fetch a
+specific version, which may be marked as partially specified via
+PARTIAL-VERSION?.
Use FTP-OPEN and FTP-CLOSE to open (resp. close) FTP connections; this can be
useful to reuse connections.
@@ -417,7 +420,9 @@ return the corresponding signature URL, or #f it signatures are unavailable."
(and (release-file? project file)
(file->source directory file)))
(_ #f))
- entries)))
+ entries))
+ (versions (map upstream-source-version releases))
+ (version (find-version versions version partial-version?)))
;; Assume that SUBDIRS correspond to versions, and jump into the
;; one with the highest version number.
@@ -440,14 +445,17 @@ return the corresponding signature URL, or #f it signatures are unavailable."
(define* (import-release package
#:key
- (version #f)
+ version
+ partial-version?
(server "ftp.gnu.org")
(directory (string-append "/gnu/" package)))
"Return the <upstream-source> for the latest version of PACKAGE or #f.
PACKAGE must be the canonical name of a GNU package. Optionally include a
-VERSION string to fetch a specific version."
+VERSION string to fetch a specific version, which may be marked as partially
+specified via PARTIAL-VERSION?."
(import-ftp-release package
#:version version
+ #:partial-version? partial-version?
#:server server
#:directory directory))
@@ -463,7 +471,7 @@ of EXP otherwise."
(close-port port))
#f)))
-(define* (import-release* package #:key (version #f))
+(define* (import-release* package #:key version partial-version?)
"Like 'import-release', but (1) take a <package> object, and (2) ignore FTP
errors that might occur when PACKAGE is not actually a GNU package, or not
hosted on ftp.gnu.org, or not under that name (this is the case for
@@ -474,6 +482,7 @@ hosted on ftp.gnu.org, or not under that name (this is the case for
(false-if-ftp-error
(import-release (package-upstream-name package)
#:version version
+ #:partial-version? partial-version?
#:server server
#:directory directory)))))
@@ -561,16 +570,23 @@ URL is a directory instead of a file, it should be suffixed with a slash (/)."
;;; TODO: Extend to support the RPM and GNOME version schemes?
(define %version-rx "[0-9.]+")
-(define* (rewrite-url url version #:key to-version)
+(define* (rewrite-url url version #:key to-version partial-version?)
"Rewrite URL so that the URL path components matching the current VERSION or
VERSION-MAJOR.VERSION-MINOR are updated with that of the latest version found
by crawling the corresponding URL directories. Alternatively, when TO-VERSION
-is specified, rewrite version matches directly to it without crawling URL.
+is specified, rewrite version matches directly to it without crawling URL. If
+TO-VERSION is provided and PARTIAL-VERSION? set to #t, then crawl URL to find
+the newest compatible release (one that is prefixed by TO-VERSION).
For example, the URL
\"https://dist.libuv.org/dist/v1.45.0/libuv-v1.45.0.tar.gz\" could be
rewritten to something like
-\"https://dist.libuv.org/dist/v1.46.0/libuv-v1.46.0.tar.gz\"."
+\"https://dist.libuv.org/dist/v1.46.0/libuv-v1.46.0.tar.gz\".
+
+With TO-VERSION set to \"1.49\" and PARTIAL-VERSION? set to #t, the URL
+\"https://dist.libuv.org/dist/v1.45.0/libuv-v1.45.0.tar.gz\" could be
+rewritten to something like
+\"https://dist.libuv.org/dist/v1.49.2/libuv-v1.49.2.tar.gz\"."
;; XXX: major-minor may be #f if version is not a triplet but a single
;; number such as "2".
(let* ((major-minor (false-if-exception (version-major+minor version)))
@@ -590,14 +606,15 @@ rewritten to something like
(reverse
(fold
(lambda (s parents)
- (if to-version
+ (if (and to-version (not partial-version?))
;; Direct rewrite case; the archive is assumed to exist.
(let ((u (string-replace-substring s version to-version)))
(cons (if (and major-minor to-major-minor)
(string-replace-substring u major-minor to-major-minor)
u)
parents))
- ;; More involved HTML crawl case.
+ ;; More involved HTML crawl case to get the latest version or a
+ ;; partial to-version.
(let* ((pattern (if major-minor
(format #f "(~a|~a)" version major-minor)
(format #f "(~a)" version)))
@@ -620,15 +637,14 @@ rewritten to something like
(m (string-match pattern l))
(v (match:substring m 1)))
(cons v l)))
- links)))
- ;; Retrieve the item having the largest version.
- (if (null? candidates)
- parents
- (cons (cdr (first (sort candidates
- (lambda (x y)
- (version>? (car x)
- (car y))))))
- parents)))
+ links))
+ (versions (map car candidates))
+ (version (find-version versions to-version
+ partial-version?)))
+ ;; Retrieve the item having the greatest version.
+ (if version
+ (cons (assoc-ref candidates version) parents)
+ parents)) ;XXX: bogus case; throw an error?
;; No version found in path component; continue.
(cons s parents)))))
(reverse url-prefix-components)
@@ -639,12 +655,14 @@ rewritten to something like
#:key
rewrite-url?
version
+ partial-version?
(directory (string-append
"/" (package-upstream-name package)))
file->signature)
"Return an <upstream-source> for the latest release of PACKAGE under
DIRECTORY at BASE-URL, or #f. Optionally include a VERSION string to fetch a
-specific version.
+specific version, which may be marked as partially specified via
+PARTIAL-VERSION?.
BASE-URL should be the URL of an HTML page, typically a directory listing as
found on 'https://kernel.org/pub'.
@@ -663,7 +681,8 @@ also updated to the latest version, as explained in the doc of the
base-url
(string-append base-url directory "/")))
(url (if rewrite-url?
- (rewrite-url url current-version #:to-version version)
+ (rewrite-url url current-version #:to-version version
+ #:partial-version? partial-version?)
url))
(links (map (cut canonicalize-url <> url) (url->links url))))
@@ -695,23 +714,18 @@ else #f. URL is assumed to fully specified."
(lambda (url) (list (uri-mirror-rewrite url))))))))))
(define candidates
- (filter-map url->release links))
-
- (match candidates
- (() #f)
- ((first . _)
- (if version
- ;; Find matching release version and return it.
- (find (lambda (upstream)
- (string=? (upstream-source-version upstream) version))
- (coalesce-sources candidates))
- ;; Select the most recent release and return it.
- (reduce (lambda (r1 r2)
- (if (version>? (upstream-source-version r1)
- (upstream-source-version r2))
- r1 r2))
- first
- (coalesce-sources candidates)))))))
+ (coalesce-sources (filter-map url->release links)))
+
+ (define versions
+ (map upstream-source-version candidates))
+
+ (define new-version
+ (find-version versions version partial-version?))
+
+ (and new-version
+ (find (compose (cut string=? new-version <>)
+ upstream-source-version)
+ candidates))))
;;;
@@ -743,7 +757,7 @@ else #f. URL is assumed to fully specified."
(call-with-gzip-input-port port
(compose string->lines get-string-all))))))
-(define* (import-gnu-release package #:key (version #f))
+(define* (import-gnu-release package #:key version partial-version?)
"Return the latest release of PACKAGE, a GNU package available via
ftp.gnu.org. Optionally include a VERSION string to fetch a specific version.
@@ -776,12 +790,15 @@ list available from %GNU-FILE-LIST-URI over HTTP(S)."
(string-contains file directory)
(release-file? name (basename file))))
files))
- ;; find latest version
- (version (or version
- (and (not (null? relevant))
- (tarball->version
- (find-latest-tarball-version relevant)))))
- ;; find tarballs matching this version
+ (versions (delay (sort (delete-duplicates
+ (map tarball->version relevant))
+ version>?)))
+ (version (or (and version partial-version?
+ (find (cut version-prefix? version <>)
+ (force versions)))
+ version
+ (first (force versions))))
+ ;; Find tarballs matching this version.
(tarballs (filter (lambda (file)
(string=? version (tarball->version file)))
relevant)))
@@ -998,11 +1015,11 @@ updater."
(or (assoc-ref (package-properties package) 'release-monitoring-url)
((url-predicate http-url?) package)))
-(define* (import-html-updatable-release package #:key (version #f))
+(define* (import-html-updatable-release package #:key version partial-version?)
"Return the latest release of PACKAGE else #f. Do that by crawling the HTML
page of the directory containing its source tarball. Optionally include a
-VERSION string to fetch a specific version."
-
+VERSION string to fetch a specific version; which may be partially provided
+when PARTIAL-VERSION? is #t."
(define (expand-uri uri)
(match uri
((and (? string?) (? (cut string-prefix? "mirror://" <>) url))
@@ -1029,6 +1046,7 @@ VERSION string to fetch a specific version."
(import-html-release base package
#:rewrite-url? #t
#:version version
+ #:partial-version? partial-version?
#:directory directory))))
(define %gnu-updater
diff --git a/guix/import/composer.scm b/guix/import/composer.scm
index abc9023be4..ba70e89c4f 100644
--- a/guix/import/composer.scm
+++ b/guix/import/composer.scm
@@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 Julien Lepiller <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -36,6 +37,7 @@
#:use-module (guix upstream)
#:use-module (guix utils)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-2)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:export (composer->guix-package
@@ -58,10 +60,6 @@
(substring version 1))
(else version)))
-(define (latest-version versions)
- (fold (lambda (a b) (if (version>? (fix-version a) (fix-version b)) a b))
- (car versions) versions))
-
(define (json->require dict)
(if dict
(let loop ((result '()) (require dict))
@@ -102,31 +100,25 @@
(not (string-contains d "beta"))
(not (string-contains d "rc")))))
-(define* (composer-fetch name #:key (version #f))
+(define* (composer-fetch name #:key version partial-version?)
"Return a composer-package representation of the Composer metadata for the
-package NAME with optional VERSION, or #f on failure."
- (let* ((url (string-append (%composer-base-url) "/p/" name ".json"))
- (packages (and=> (json-fetch url)
- (lambda (pkg)
- (let ((pkgs (assoc-ref pkg "packages")))
- (or (assoc-ref pkgs name) pkg))))))
- (if packages
- (json->composer-package
- (if version
- (assoc-ref packages version)
- (cdr
- (fold
- (lambda (new cur-max)
- (match new
- (((? valid-version? version) . tail)
- (if (version>? (fix-version version)
- (fix-version (car cur-max)))
- (cons* version tail)
- cur-max))
- (_ cur-max)))
- (cons* "0.0.0" #f)
- packages))))
- #f)))
+package NAME with optional VERSION, or #f on failure. VERSION may be gien as
+version prefix if PARTIAL-VERSION? is #t."
+ (and-let* ((url (string-append (%composer-base-url) "/p/" name ".json"))
+ (packages (and=> (json-fetch url)
+ (lambda (pkg)
+ (let ((pkgs (assoc-ref pkg "packages")))
+ (or (assoc-ref pkgs name) pkg)))))
+ (all-versions (map car packages))
+ (valid-versions (filter valid-version? all-versions))
+ (version (or (find-version valid-versions version partial-version?)
+ (and version
+ ;; If the user-provided VERSION could not be
+ ;; found, fallback to look through all
+ ;; versions.
+ (find-version all-versions version
+ partial-version?)))))
+ (json->composer-package (assoc-ref packages version))))
(define (php-package-name name)
"Given the NAME of a package on Packagist, return a Guix-compliant name for
@@ -246,10 +238,15 @@ package in Packagist."
(downstream-name (php-package-name dependency))
(type type)))
-(define* (import-release package #:key (version #f))
- "Return an <upstream-source> for VERSION or the latest release of PACKAGE."
+(define* (import-release package #:key version partial-version?)
+ "Return an <upstream-source> for VERSION or the latest release of PACKAGE.
+If PARTIAL-VERSION? is #t, the provided VERSION may be a partial version
+prefix."
(let* ((php-name (guix-package->composer-name package))
- (composer-package (composer-fetch php-name #:version version)))
+ (composer-package (composer-fetch php-name
+ #:version version
+ #:partial-version?
+ partial-version?)))
(if composer-package
(upstream-source
(package (composer-package-name composer-package))
diff --git a/guix/import/cpan.scm b/guix/import/cpan.scm
index 85e5e69098..5f06aaae90 100644
--- a/guix/import/cpan.scm
+++ b/guix/import/cpan.scm
@@ -328,7 +328,7 @@ in RELEASE, a <cpan-release> record."
")"))))
(url-predicate (cut regexp-exec cpan-rx <>))))
-(define* (latest-release package #:key (version #f))
+(define* (latest-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE."
(when version
(raise
diff --git a/guix/import/cran.scm b/guix/import/cran.scm
index 3bea9439e1..4825af12a5 100644
--- a/guix/import/cran.scm
+++ b/guix/import/cran.scm
@@ -1028,7 +1028,7 @@ s-expression corresponding to that package, or #f on failure."
(_ #f)))
(_ #f)))))
-(define* (latest-cran-release pkg #:key (version #f))
+(define* (latest-cran-release pkg #:key version partial-version?)
"Return an <upstream-source> for the latest release of the package PKG."
(when version
(error
@@ -1051,7 +1051,7 @@ s-expression corresponding to that package, or #f on failure."
(urls (cran-uri upstream-name version))
(inputs (cran-package-inputs meta 'cran))))))
-(define* (latest-bioconductor-release pkg #:key (version #f))
+(define* (latest-bioconductor-release pkg #:key version partial-version?)
"Return an <upstream-source> for the latest release of the package PKG."
(when version
(error
diff --git a/guix/import/crate.scm b/guix/import/crate.scm
index 822d2e8f94..5a8caeb3e1 100644
--- a/guix/import/crate.scm
+++ b/guix/import/crate.scm
@@ -7,6 +7,7 @@
;;; Copyright © 2023 Simon Tournier <[email protected]>
;;; Copyright © 2023, 2024 Efraim Flashner <[email protected]>
;;; Copyright © 2023, 2024 David Elsing <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;; Copyright © 2025 Herman Rimm <[email protected]>
;;;
;;; This file is part of GNU Guix.
@@ -278,8 +279,9 @@ and LICENSE."
(loop curr remaining)
(loop next remaining))))))
-(define (max-crate-version-of-semver semver-range range)
- "Returns a <crate-version> of the highest version within the semver range."
+(define (max-crate-version-of-semver semver-range versions)
+ "Returns the <crate-version> of the highest version found in VERSIONS that
+satisfies SEMVER-RANGE."
(define (crate->semver crate)
(string->semver (crate-version-number crate)))
@@ -287,7 +289,7 @@ and LICENSE."
(min-element
(filter (lambda (crate)
(semver-range-contains? semver-range (crate->semver crate)))
- range)
+ versions)
(lambda args
(apply semver>? (map crate->semver args)))))
@@ -491,25 +493,34 @@ look up the development dependencs for the given crate."
(define crate-package?
(url-predicate crate-url?))
-(define* (import-release package #:key (version #f))
- "Return an <upstream-source> for the latest release of PACKAGE. Optionally
-include a VERSION string to fetch a specific version."
+(define* (import-release package #:key version partial-version?)
+ "Return an <upstream-source> for the latest release of PACKAGE. Optionally
+include a VERSION string to fetch a specific version, which may be a partial
+prefix when PARTIAL-VERSION? is #t."
(let* ((crate-name (guix-package->crate-name package))
(crate (lookup-crate crate-name))
- (version (or version
- (let ((max-crate-version
- (max-crate-version-of-semver
- (string->semver-range
- (string-append "^" (package-version package)))
- (nonyanked-crate-versions crate))))
- (and=> max-crate-version
- crate-version-number)))))
- (if version
- (upstream-source
- (package (package-name package))
- (version version)
- (urls (list (crate-uri crate-name version))))
- #f)))
+ (versions (delay (nonyanked-crate-versions crate)))
+ (find-max-minor-patch-version (lambda (base-version)
+ (max-crate-version-of-semver
+ (string->semver-range
+ (string-append
+ "^" base-version))
+ (force versions))))
+ (version (cond
+ ((and version partial-version?) ;partial version
+ (and=> (find-max-minor-patch-version version)
+ crate-version-number))
+ ((and version (not partial-version?)) ;exact version
+ version)
+ (else ;latest version
+ (and=> (find-max-minor-patch-version
+ (package-version package))
+ crate-version-number)))))
+ (and version
+ (upstream-source
+ (package (package-name package))
+ (version version)
+ (urls (list (crate-uri crate-name version)))))))
(define %crate-updater
(upstream-updater
diff --git a/guix/import/egg.scm b/guix/import/egg.scm
index a87de1453e..849e559ad6 100644
--- a/guix/import/egg.scm
+++ b/guix/import/egg.scm
@@ -4,6 +4,7 @@
;;; Copyright © 2021 Sarah Morgensen <[email protected]>
;;; Copyright © 2022 Hartmut Goebel <[email protected]>
;;; Copyright © 2024 Ekaitz Zarraga <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -24,6 +25,7 @@
#:use-module (ice-9 ftw)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
#:use-module (srfi srfi-71)
#:use-module (gcrypt hash)
#:use-module (guix git)
@@ -99,12 +101,9 @@ to the repository."
(let ((eggs-directory (eggs-repository)))
(string-append eggs-directory "/" name)))
-(define (find-latest-version name)
- "Get the latest version of the egg NAME."
- (let ((directory (scandir (egg-directory name))))
- (if directory
- (last directory)
- #f)))
+(define (get-versions name)
+ "Get the release versions of the egg NAME."
+ (or (scandir (egg-directory name)) '()))
(define* (egg-metadata name #:key (version #f) (file #f))
"Return the package metadata file for the egg NAME at version VERSION, or if
@@ -112,7 +111,7 @@ FILE is specified, return the package metadata in FILE."
(call-with-input-file (or file
(string-append (egg-directory name) "/"
(or version
- (find-latest-version name))
+ (first (get-versions name)))
"/" name ".egg"))
read))
@@ -188,7 +187,7 @@ not work."
(if (not egg-content)
(values #f '()) ; egg doesn't exist
(let* ((version* (or (assoc-ref egg-content 'version)
- (find-latest-version name)))
+ (first (get-versions name))))
(version (if (list? version*) (first version*) version*))
(source-url (if source #f `(egg-uri ,name version)))
(tarball (if source
@@ -333,16 +332,18 @@ not work."
;;; Updater.
;;;
-(define* (import-release package #:key (version #f))
+(define* (import-release package #:key version partial-version?)
"Return an @code{<upstream-source>} for the latest release of PACKAGE.
-Optionally include a VERSION string to fetch a specific version."
+Optionally fetch a specific VERSION string, which may be a version prefix when
+PARTIAL-VERSION? is #t."
(let* ((egg-name (guix-package->egg-name package))
- (version (or version (find-latest-version egg-name)))
- (source-url (egg-uri egg-name version)))
- (upstream-source
- (package (package-name package))
- (version version)
- (urls (list source-url)))))
+ (versions (get-versions egg-name))
+ (version (find-version versions version partial-version?)))
+ (and version
+ (upstream-source
+ (package (package-name package))
+ (version version)
+ (urls (list (egg-uri egg-name version)))))))
(define %egg-updater
(upstream-updater
diff --git a/guix/import/elpa.scm b/guix/import/elpa.scm
index a05ba90cc2..ea16885d90 100644
--- a/guix/import/elpa.scm
+++ b/guix/import/elpa.scm
@@ -422,7 +422,7 @@ type '<elpa-package>'."
(string-drop (package-name package) 6)
(package-name package))))
-(define* (latest-release package #:key (version #f))
+(define* (latest-release package #:key version partial-version?)
"Return an <upstream-release> for the latest release of PACKAGE."
(define name (guix-package->elpa-name package))
(define repo (elpa-repository package))
diff --git a/guix/import/gem.scm b/guix/import/gem.scm
index 56cbc681a1..46024b9d6a 100644
--- a/guix/import/gem.scm
+++ b/guix/import/gem.scm
@@ -7,6 +7,7 @@
;;; Copyright © 2021 Sarah Morgensen <[email protected]>
;;; Copyright © 2022 Taiju HIGASHI <[email protected]>
;;; Copyright © 2022 Hartmut Goebel <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -26,6 +27,7 @@
(define-module (guix import gem)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
#:use-module (json)
#:use-module (guix import utils)
#:use-module (guix import json)
@@ -35,6 +37,7 @@
#:use-module (guix base16)
#:use-module (guix base32)
#:use-module ((guix build-system ruby) #:select (rubygems-uri))
+ #:use-module ((guix utils) #:select (version>? version-prefix?))
#:export (gem->guix-package
%gem-updater
gem-recursive-import))
@@ -90,6 +93,15 @@
(string-append "https://rubygems.org/api/v1/gems/" name ".json")))
json->gem))
+(define (get-versions name)
+ "Return all the versions for the gem NAME, sorted in decreasing order."
+ (let* ((url (string-append "https://rubygems.org/api/v1/versions/"
+ name ".json"))
+ (versions-data (json-fetch url)))
+ (sort (map (cut assoc-ref <> "number")
+ (vector->list versions-data))
+ version>?)))
+
(define (ruby-package-name name)
"Given the NAME of a package on RubyGems, return a Guix-compliant name for
the package."
@@ -172,7 +184,7 @@ package on RubyGems."
(define gem-package?
(url-prefix-predicate "https://rubygems.org/downloads/"))
-(define* (import-release package #:key (version #f))
+(define* (import-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE."
(let* ((gem-name (guix-package->gem-name package))
(gem (rubygems-fetch gem-name))
@@ -184,13 +196,14 @@ package on RubyGems."
(ruby-package-name name))
(type 'propagated))))
(gem-dependencies-runtime (gem-dependencies gem))))
- (version (or version (gem-version gem)))
- (url (rubygems-uri gem-name version)))
- (upstream-source
- (package (package-name package))
- (version version)
- (urls (list url))
- (inputs inputs))))
+ (versions (get-versions gem-name))
+ (version (find-version versions version partial-version?)))
+ (and version
+ (upstream-source
+ (package (package-name package))
+ (version version)
+ (urls (list (rubygems-uri gem-name version)))
+ (inputs inputs)))))
(define %gem-updater
(upstream-updater
diff --git a/guix/import/git.scm b/guix/import/git.scm
index 305b2fc43f..8d443895cf 100644
--- a/guix/import/git.scm
+++ b/guix/import/git.scm
@@ -26,7 +26,7 @@
#:use-module (guix git-download)
#:use-module (guix packages)
#:use-module (guix upstream)
- #:use-module (guix utils)
+ #:use-module ((guix import utils) #:select (find-version))
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:use-module (srfi srfi-1)
@@ -34,10 +34,7 @@
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (srfi srfi-71)
- #:export (%generic-git-updater
-
- ;; For tests.
- latest-git-tag-version))
+ #:export (%generic-git-updater))
;;; Commentary:
;;;
@@ -121,7 +118,9 @@ version corresponding to the tag, and the cdr is the name of the tag."
;; with "."
pre-release-rx suffix-rx))
-
+ (define (pre-release? tag)
+ (any (cut regexp-exec <> tag)
+ %pre-release-rx))
(define (get-version tag)
(let ((tag-match (regexp-exec (make-regexp tag-rx) tag)))
@@ -135,30 +134,20 @@ version corresponding to the tag, and the cdr is the name of the tag."
(string-append version (match:substring tag-match 3))
version)))))
- (define (entry<? a b)
- (eq? (version-compare (car a) (car b)) '<))
-
- (define (pre-release? tag)
- (any (cut regexp-exec <> tag)
- %pre-release-rx))
-
- (stable-sort (filter-map (lambda (tag)
- (let ((version (get-version tag)))
- (and version
- (or pre-releases?
- (not (pre-release? version)))
- (cons version tag))))
- tags)
- entry<?))
-
-(define* (latest-tag url
- #:key prefix suffix delim pre-releases? (version #f))
- "Return the latest version and corresponding tag available from the Git
-repository at URL. Optionally include a VERSION string to fetch a specific
-version."
+ (filter-map (lambda (tag)
+ (let ((version (get-version tag)))
+ (and version
+ (or pre-releases?
+ (not (pre-release? version)))
+ (cons version tag))))
+ tags))
+
+(define* (get-tags url #:key prefix suffix delim pre-releases?)
+ "Return a alist of the Git tags available from URL. The tags are keyed by
+their version, a mapping derived from their name."
(let* ((tags (map (cut string-drop <> (string-length "refs/tags/"))
(remote-refs url #:tags? #t)))
- (versions->tags
+ (versions+tags
(version-mapping tags
#:prefix prefix
#:suffix suffix
@@ -167,47 +156,38 @@ version."
(cond
((null? tags)
(git-no-tags-error))
- ((null? versions->tags)
+ ((null? versions+tags)
(git-no-valid-tags-error))
(else
- (let ((versions (if version
- (filter (match-lambda
- ((candidate-version . tag)
- (string=? version candidate-version)))
- versions->tags)
- versions->tags)))
- (if (null? versions)
- (values #f #f)
- (match (last versions)
- ((version . tag)
- (values version tag)))))))))
-
-(define* (latest-git-tag-version package #:key (version #f))
- "Given a PACKAGE, return the latest version of it and the corresponding git
-tag, or #false and #false if the latest version could not be determined.
-Optionally include a VERSION string to fetch a specific version."
+ versions+tags)))) ;already sorted
+
+(define* (get-package-tags package)
+ "Given a PACKAGE, return all its known tags, an alist keyed by the tags
+associated versions. "
(guard (c ((or (git-no-tags-error? c) (git-no-valid-tags-error? c))
(warning (or (package-field-location package 'source)
(package-location package))
(G_ "~a for ~a~%")
(condition-message c)
(package-name package))
- (values #f #f))
+ '())
((eq? (exception-kind c) 'git-error)
(warning (or (package-field-location package 'source)
(package-location package))
(G_ "failed to fetch Git repository for ~a~%")
(package-name package))
- (values #f #f)))
+ '()))
(let* ((source (package-source package))
(url (git-reference-url (origin-uri source)))
(property (cute assq-ref (package-properties package) <>)))
- (latest-tag url
- #:version version
- #:prefix (property 'release-tag-prefix)
- #:suffix (property 'release-tag-suffix)
- #:delim (property 'release-tag-version-delimiter)
- #:pre-releases? (property 'accept-pre-releases?)))))
+ (get-tags url
+ #:prefix (property 'release-tag-prefix)
+ #:suffix (property 'release-tag-suffix)
+ #:delim (property 'release-tag-version-delimiter)
+ #:pre-releases? (property 'accept-pre-releases?)))))
+
+;; Prevent Guile from inlining this procedure so we can use it in tests.
+(set! get-package-tags get-package-tags)
(define (git-package? package)
"Return true if PACKAGE is hosted on a Git repository."
@@ -217,21 +197,24 @@ Optionally include a VERSION string to fetch a specific version."
(git-reference? (origin-uri origin))))
(_ #f)))
-(define* (import-git-release package #:key (version #f))
+(define* (import-git-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE.
-Optionally include a VERSION string to fetch a specific version."
+Optionally include a VERSION string to fetch a specific version, which may be
+a version prefix when PARTIAL-VERSION? is #t."
(let* ((name (package-name package))
(old-version (package-version package))
(old-reference (origin-uri (package-source package)))
- (new-version new-version-tag
- (latest-git-tag-version package #:version version)))
- (and new-version new-version-tag
+ (tags (get-package-tags package))
+ (versions (map car tags))
+ (version (find-version versions version partial-version?))
+ (tag (assoc-ref tags version)))
+ (and version
(upstream-source
(package name)
- (version new-version)
+ (version version)
(urls (git-reference
(url (git-reference-url old-reference))
- (commit new-version-tag)
+ (commit tag)
(recursive? (git-reference-recursive? old-reference))))))))
(define %generic-git-updater
diff --git a/guix/import/github.scm b/guix/import/github.scm
index 7be29ca151..00d362822f 100644
--- a/guix/import/github.scm
+++ b/guix/import/github.scm
@@ -7,6 +7,7 @@
;;; Copyright © 2022 Maxime Devos <[email protected]>
;;; Copyright © 2022 Hartmut Goebel <[email protected]>
;;; Copyright © 2023 Giacomo Leidi <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -30,7 +31,8 @@
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-71)
- #:use-module (guix utils)
+ #:use-module ((guix import utils) #:select (find-version))
+ #:use-module ((guix utils) #:select (string-replace-substring))
#:use-module (guix i18n)
#:use-module (guix diagnostics)
#:use-module ((guix ui) #:select (display-hint))
@@ -246,40 +248,49 @@ Alternatively, you can wait until your rate limit is reset, or use the
#:headers headers))))
(match result
(#()
- ;; We got the empty list, presumably because the user didn't use GitHub's
- ;; "release" mechanism, but hopefully they did use Git tags.
+ ;; We got the empty list, presumably because the user
+ ;; didn't use GitHub's "release" mechanism, but hopefully
+ ;; they did use Git tags.
(json->scm (http-fetch tag-url
#:port connection
#:keep-alive? #t
#:headers headers)))
(x x)))))))))
-(define* (latest-released-version url package-name #:key (version #f))
- "Return the newest released version and its tag given a string URL like
-'https://github.com/arq5x/bedtools2/archive/v2.24.0.tar.gz' and the name of
-the package e.g. 'bedtools2'. Return #f (two values) if there are no
-releases.
+(define* (get-package-tags package)
+ "Return an alist of tags keyed by their version for PACKAGE, a <package>
+object."
+ (define (github-uri uri)
+ (match uri
+ ((? string? url)
+ url) ;surely a github.com URL
+ ((? download:git-reference? ref)
+ (download:git-reference-url ref))
+ ((urls ...)
+ (find (cut string-contains <> "github.com") urls))))
-Optionally include a VERSION string to fetch a specific version."
(define (pre-release? x)
(assoc-ref x "prerelease"))
+ (define source-uri
+ (github-uri (origin-uri (package-source package))))
+
;; This procedure returns (version . tag) pair, or #f
;; if RELEASE doesn't seyem to correspond to a version.
(define (release->version release)
- (let ((tag (or (assoc-ref release "tag_name") ;a "release"
- (assoc-ref release "name"))) ;a tag
- (name-length (string-length package-name)))
+ (let* ((tag (or (assoc-ref release "tag_name") ;a "release"
+ (assoc-ref release "name"))) ;a tag
+ (name (package-upstream-name package))
+ (name-length (string-length name)))
(cond
- ;; some tags include the name of the package e.g. "fdupes-1.51"
- ;; so remove these
+ ;; Some tags include the name of the package e.g. "fdupes-1.51"; remove
+ ;; these.
((and (< name-length (string-length tag))
- (string=? (string-append package-name "-")
+ (string=? (string-append name "-")
(substring tag 0 (+ name-length 1))))
(cons (substring tag (+ name-length 1)) tag))
- ;; some tags start with a "v" e.g. "v0.25.0"
- ;; or with the word "version" e.g. "version.2.1"
- ;; where some are just the version number
+ ;; Some tags start with a "v" e.g. "v0.25.0" or with the word "version"
+ ;; e.g. "version.2.1" where some are just the version number.
((string-prefix? "version" tag)
(cons (if (char-set-contains? char-set:digit (string-ref tag 7))
(substring tag 7)
@@ -294,53 +305,32 @@ Optionally include a VERSION string to fetch a specific version."
(cons tag tag))
(else #f))))
- (match (and=> (fetch-releases-or-tags url) vector->list)
- (#f (values #f #f))
+ (match (and=> (fetch-releases-or-tags source-uri) vector->list)
+ (#f '())
(json
- (let ((releases (filter-map release->version
- (match (remove pre-release? json)
- (() json) ; keep everything
- (releases releases)))))
- (match (if version
- ;; Find matching release version.
- (filter (match-lambda
- ((candidate-version . tag)
- (string=? version candidate-version)))
- releases)
- ;; Sort releases descending.
- (sort releases
- (lambda (x y) (version>? (car x) (car y)))))
- (((latest-version . tag) . _) (values latest-version tag))
- (() (values #f #f)))))))
+ (filter-map release->version
+ (match (remove pre-release? json)
+ (() json) ;keep everything
+ (releases releases))))))
-(define* (import-release pkg #:key (version #f))
+(define* (import-release pkg #:key version partial-version?)
"Return an <upstream-source> for the latest release of PKG.
-Optionally include a VERSION string to fetch a specific version."
- (define (github-uri uri)
- (match uri
- ((? string? url)
- url) ;surely a github.com URL
- ((? download:git-reference? ref)
- (download:git-reference-url ref))
- ((urls ...)
- (find (cut string-contains <> "github.com") urls))))
-
+Optionally include a VERSION string to fetch a specific version, which may be
+a partial version prefix if PARTIAL-VERSION? is #t."
(let* ((original-uri (origin-uri (package-source pkg)))
- (source-uri (github-uri original-uri))
- (name (package-upstream-name pkg))
- (newest-version version-tag
- (latest-released-version source-uri name
- #:version version)))
- (if newest-version
- (upstream-source
- (package name)
- (version newest-version)
- (urls (if (download:git-reference? original-uri)
- (download:git-reference
- (inherit original-uri)
- (commit version-tag))
- (list (updated-github-url pkg newest-version)))))
- #f))) ; On GitHub but no proper releases
+ (tags (get-package-tags pkg))
+ (versions (map car tags))
+ (version (find-version versions version partial-version?))
+ (tag (assoc-ref tags version)))
+ (and version
+ (upstream-source
+ (package (package-upstream-name pkg))
+ (version version)
+ (urls (if (download:git-reference? original-uri)
+ (download:git-reference
+ (inherit original-uri)
+ (commit tag))
+ (list (updated-github-url pkg version))))))))
(define %github-updater
(upstream-updater
@@ -348,5 +338,3 @@ Optionally include a VERSION string to fetch a specific version."
(description "Updater for GitHub packages")
(pred github-package?)
(import import-release)))
-
-
diff --git a/guix/import/gnome.scm b/guix/import/gnome.scm
index 60234292ec..5df4a84295 100644
--- a/guix/import/gnome.scm
+++ b/guix/import/gnome.scm
@@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017, 2019, 2021, 2024 Ludovic Courtès <[email protected]>
-;;; Copyright © 2022 Maxim Cournoyer <[email protected]>
+;;; Copyright © 2022, 2024 Maxim Cournoyer <[email protected]>
;;; Copyright © 2022 Hartmut Goebel <[email protected]>
;;;
;;; This file is part of GNU Guix.
@@ -19,14 +19,15 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix import gnome)
+ #:use-module ((guix import utils) #:select (find-version))
#:use-module (guix upstream)
- #:use-module (guix utils)
#:use-module (guix packages)
#:use-module (guix http-client)
#:use-module (guix diagnostics)
#:use-module (guix i18n)
#:use-module (json)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (web uri)
#:use-module (ice-9 match)
@@ -58,10 +59,10 @@ source for metadata."
name "/" relative-url))))
'("tar.lz" "tar.xz" "tar.bz2" "tar.gz")))))))
-(define* (import-gnome-release package #:key (version #f))
+(define* (import-gnome-release package #:key version partial-version?)
"Return the latest release of PACKAGE, a GNOME package, or #f if it could
not be determined. Optionally include a VERSION string to fetch a specific
-version."
+version, which may be partial if PARTIAL-VERSION? is #t."
(define %not-dot
(char-set-complement (char-set #\.)))
@@ -90,28 +91,6 @@ https://discourse.gnome.org/t/new-gnome-versioning-scheme/4235"
;; Some packages like "NetworkManager" have camel-case names.
(package-upstream-name package))
- (define (find-latest-release releases)
- (fold (match-lambda*
- (((key . value) result)
- (cond ((release-version? key)
- (match result
- (#f
- (cons key value))
- ((newest . _)
- (if (version>? key newest)
- (cons key value)
- result))))
- (else
- result))))
- #f
- releases))
-
- (define (find-version-release releases version)
- (find (match-lambda
- ((key . value)
- (string=? key version)))
- releases))
-
(guard (c ((http-get-error? c)
(unless (= 404 (http-get-error-code c))
(warning (G_ "failed to download from '~a': ~a (~s)~%")
@@ -135,11 +114,20 @@ https://discourse.gnome.org/t/new-gnome-versioning-scheme/4235"
(match json
(#(4 releases _ ...)
(let* ((releases (assoc-ref releases upstream-name))
- (latest (if version
- (find-version-release releases version)
- (find-latest-release releases))))
- (and latest
- (jsonish->upstream-source upstream-name latest))))))))
+ (all-versions (map car releases))
+ (release-versions (filter release-version? all-versions))
+ (version (or (find-version release-versions
+ version partial-version?)
+ (and version
+ ;; If the user-provided VERSION could not be
+ ;; found, fallback to look through all
+ ;; versions.
+ (find-version all-versions
+ version partial-version?)))))
+ (and version
+ (jsonish->upstream-source
+ upstream-name
+ (find (compose (cut string=? version <>) car) releases)))))))))
(define %gnome-updater
(upstream-updater
diff --git a/guix/import/hackage.scm b/guix/import/hackage.scm
index 422887d435..0186db014a 100644
--- a/guix/import/hackage.scm
+++ b/guix/import/hackage.scm
@@ -379,7 +379,7 @@ respectively."
(let ((hackage-rx (make-regexp "(https?://hackage.haskell.org|mirror://hackage/)")))
(url-predicate (cut regexp-exec hackage-rx <>))))
-(define* (import-release package #:key (version #f))
+(define* (import-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE."
(let* ((hackage-name (package-upstream-name* package))
(cabal-meta (hackage-fetch hackage-name version)))
diff --git a/guix/import/hexpm.scm b/guix/import/hexpm.scm
index 601d812680..96f67fe2ea 100644
--- a/guix/import/hexpm.scm
+++ b/guix/import/hexpm.scm
@@ -3,7 +3,7 @@
;;; Copyright © 2016 David Craven <[email protected]>
;;; Copyright © 2017, 2019-2021, 2024 Ludovic Courtès <[email protected]>
;;; Copyright © 2019 Martin Becze <[email protected]>
-;;; Copyright © 2019 Maxim Cournoyer <[email protected]>
+;;; Copyright © 2019, 2024 Maxim Cournoyer <[email protected]>
;;; Copyright © 2020-2022 Hartmut Goebel <[email protected]>
;;;
;;; This file is part of GNU Guix.
@@ -32,7 +32,7 @@
call-with-temporary-output-file))
#:use-module (guix packages)
#:use-module (guix upstream)
- #:autoload (guix utils) (version>? file-sans-extension)
+ #:autoload (guix utils) (file-sans-extension version>? version-prefix?)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
@@ -95,7 +95,7 @@
(define (lookup-hexpm name)
- "Look up NAME on hex.pm and return the corresponding <hexpm> record
+ "Look up NAME on hex.pm and return the corresponding <hexpm-pkgdef> record
or #f if it was not found."
(and=> (json-fetch (package-url name))
json->hexpm))
@@ -215,16 +215,11 @@ build-system, and DEPENDENCIES the inputs for the package."
license)))
strings))
-(define (hexpm-latest-release package)
- "Return the version string for the latest stable release of PACKAGE."
- ;; Use latest-stable if specified (see comment in hexpm-pkgdef above),
- ;; otherwise compare the lists of release versions.
- (let ((latest-stable (hexpm-latest-stable package)))
- (if (not (unspecified? latest-stable))
- latest-stable
- (let ((versions (map hexpm-version-number (hexpm-versions package))))
- (fold (lambda (a b)
- (if (version>? a b) a b)) (car versions) versions)))))
+(define (hexpm-releases package)
+ "Return the version strings for releases of PACKAGE, a <hexpm-pkgdef>
+object, ordered from newest to oldest."
+ (sort (map hexpm-version-number (hexpm-versions package))
+ version>?))
(define* (hexpm->guix-package package-name #:key version #:allow-other-keys)
"Fetch the metadata for PACKAGE-NAME from hexpms.io, and return the
@@ -238,7 +233,7 @@ latest version of PACKAGE-NAME."
(define version-number
(and package
(or version
- (hexpm-latest-release package))))
+ (first (hexpm-releases package)))))
(define version*
(and package
@@ -320,17 +315,20 @@ latest version of PACKAGE-NAME."
;;; Updater
;;;
-(define* (import-release package #:key (version #f))
+(define* (import-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE. Optionally
-include a VERSION string to fetch a specific version."
+include a VERSION string to fetch a specific version, which may be a version
+prefix when PARTIAL-VERSION? is #t."
(let* ((hexpm-name (guix-package->hexpm-name package))
(hexpm (lookup-hexpm hexpm-name))
- (version (or version (hexpm-latest-release hexpm)))
- (url (hexpm-uri hexpm-name version)))
- (upstream-source
- (package (package-name package))
- (version version)
- (urls (list url)))))
+ (latest-stable (hexpm-latest-stable hexpm))
+ (releases (hexpm-releases hexpm))
+ (version (find-version releases version partial-version?)))
+ (and version
+ (upstream-source
+ (package (package-name package))
+ (version version)
+ (urls (list (hexpm-uri hexpm-name version)))))))
(define %hexpm-updater
(upstream-updater
diff --git a/guix/import/kde.scm b/guix/import/kde.scm
index 0ae457ef3d..046bfc5a8e 100644
--- a/guix/import/kde.scm
+++ b/guix/import/kde.scm
@@ -2,6 +2,7 @@
;;; Copyright © 2016 David Craven <[email protected]>
;;; Copyright © 2016, 2017 Ludovic Courtès <[email protected]>
;;; Copyright © 2019 Hartmut Goebel <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -19,6 +20,7 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix import kde)
+ #:use-module ((guix import utils) #:select (find-version))
#:use-module (guix http-client)
#:use-module (guix gnu-maintenance)
#:use-module (guix packages)
@@ -149,48 +151,39 @@ Output:
(string-join (map version->pattern directory-parts) "/")
"/"))))
-(define* (import-kde-release package #:key (version #f))
+(define* (import-kde-release package #:key version partial-version?)
"Return the latest release of PACKAGE, a KDE package, or #f if it could
not be determined. Optionally include a VERSION string to fetch a specific
-version."
-
- (define (find-latest-archive-version archives)
- (fold (lambda (file1 file2)
- (if (and file2
- (version>? (tarball-sans-extension (basename file2))
- (tarball-sans-extension (basename file1))))
- file2
- file1))
- #f
- archives))
-
+version, which may be a partial prefix when PARTIAL-VERSION? is #t."
(let* ((uri (string->uri (origin-uri (package-source package))))
(path-rx (uri->kde-path-pattern uri))
(name (package-upstream-name package))
(files (download.kde.org-files))
- ;; select archives for this package
+ ;; Select archives for this package.
(relevant (filter (lambda (file)
(and (regexp-exec path-rx file)
(release-file? name (basename file))))
files))
- ;; Find latest version.
- (version (or version
- (and (not (null? relevant))
- (tarball->version (find-latest-archive-version relevant)))))
- ;; Find archives matching this version.
- (tarballs (filter (lambda (file)
- (string=? version (tarball->version file)))
- relevant)))
- (match tarballs
- (() #f)
- (_
- (upstream-source
- (package name)
- (version version)
- (urls (map (lambda (file)
- (string-append "mirror://kde/" file))
- tarballs)))))))
-
+ ;; Build an association list of file names keyed by their version.
+ (all-tarballs (map (lambda (x)
+ (cons (tarball->version x) x))
+ relevant))
+ (versions (map car all-tarballs))
+ ;; Find the latest version.
+ (version (find-version versions version partial-version?))
+ ;; Find all archives matching this version.
+ (tarballs (and version
+ (map cdr (filter (match-lambda
+ ((x . file-name)
+ (string=? version x)))
+ all-tarballs)))))
+ (and version tarballs
+ (upstream-source
+ (package name)
+ (version version)
+ (urls (map (lambda (file)
+ (string-append "mirror://kde/" file))
+ tarballs))))))
(define %kde-updater
(upstream-updater
diff --git a/guix/import/launchpad.scm b/guix/import/launchpad.scm
index 01953ea69c..75b474ead7 100644
--- a/guix/import/launchpad.scm
+++ b/guix/import/launchpad.scm
@@ -3,6 +3,7 @@
;;; Copyright © 2021 Matthew James Kraai <[email protected]>
;;; Copyright © 2020 Brice Waegeneire <[email protected]>
;;; Copyright © 2022 Hartmut Goebel <[email protected]>
+;;; Copyright © 2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -26,9 +27,10 @@
#:use-module (web uri)
#:use-module ((guix download) #:prefix download:)
#:use-module (guix import json)
+ #:use-module ((guix import utils) #:select (find-version))
#:use-module (guix packages)
#:use-module (guix upstream)
- #:use-module (guix utils)
+ #:use-module ((guix utils) #:select (version-major+minor))
#:export (%launchpad-updater))
(define (find-extension url)
@@ -103,9 +105,9 @@ URL of the form
(match (string-split (uri-path (string->uri url)) #\/)
((_ repo . rest) repo)))
-(define (latest-released-version repository)
- "Return a string of the newest released version name given the REPOSITORY,
-for example, 'linuxdcpp'. Return #f if there is no releases."
+(define (release-versions repository)
+ "Return a list of the release version strings available for REPOSITORY, a
+repository name such as 'linuxdcpp'."
(define (pre-release? x)
;; Versions containing anything other than digit characters and "." (for
;; example, "5.1.0-rc1") are assumed to be pre-releases.
@@ -116,31 +118,31 @@ for example, 'linuxdcpp'. Return #f if there is no releases."
(match (json-fetch
(string-append "https://api.launchpad.net/1.0/"
repository "/releases"))
- (#f #f) ;404 or similar
+ (#f #f) ;404 or similar
(json
- (assoc-ref
- (last (remove pre-release? (vector->list (assoc-ref json "entries"))))
- "version"))))
+ (let ((releases (remove pre-release?
+ (vector->list (assoc-ref json "entries")))))
+ (map (cut assoc-ref <> "version") releases)))))
-(define* (import-release pkg #:key (version #f))
+(define* (import-release pkg #:key version partial-version?)
"Return an <upstream-source> for the latest release of PKG. Optionally
-include a VERSION string to fetch a specific version."
+include a VERSION string to fetch a specific version. When PARTIAL-VERSION?
+is #t, update to the latest version prefixed by VERSION."
(define (origin-launchpad-uri origin)
(match (origin-uri origin)
- ((? string? url) url) ; surely a Launchpad URL
+ ((? string? url) url) ;surely a Launchpad URL
((urls ...)
(find (cut string-contains <> "launchpad.net") urls))))
(let* ((source-uri (origin-launchpad-uri (package-source pkg)))
(name (package-name pkg))
- (repository (launchpad-repository source-uri))
- (newest-version (or version (latest-released-version repository))))
- (if newest-version
+ (versions (release-versions (launchpad-repository source-uri)))
+ (version (find-version versions version partial-version?)))
+ (and version
(upstream-source
(package name)
- (version newest-version)
- (urls (list (updated-launchpad-url pkg newest-version))))
- #f))) ; On Launchpad but no proper releases
+ (version version)
+ (urls (list (updated-launchpad-url pkg version)))))))
(define %launchpad-updater
(upstream-updater
diff --git a/guix/import/minetest.scm b/guix/import/minetest.scm
index 5ea6e023ce..a46296cdc4 100644
--- a/guix/import/minetest.scm
+++ b/guix/import/minetest.scm
@@ -483,7 +483,7 @@ list of AUTHOR/NAME strings."
(and (string-prefix? "minetest-" (package:package-name pkg))
(assq-ref (package:package-properties pkg) 'upstream-name)))
-(define* (latest-minetest-release pkg #:key (version #f))
+(define* (latest-minetest-release pkg #:key version partial-version?)
"Return an <upstream-source> for the latest release of the package PKG,
or #false if the latest release couldn't be determined."
(define author/name
diff --git a/guix/import/opam.scm b/guix/import/opam.scm
index a7f8092507..4b69d50ceb 100644
--- a/guix/import/opam.scm
+++ b/guix/import/opam.scm
@@ -417,7 +417,7 @@ package in OPAM."
(member (build-system-name (package-build-system package)) '(dune ocaml))
(not (string-prefix? "ocaml4" (package-name package)))))
-(define* (latest-release package #:key (version #f))
+(define* (latest-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE."
(when version
(raise
diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm
index 4af02dd250..9a40dea1c1 100644
--- a/guix/import/pypi.scm
+++ b/guix/import/pypi.scm
@@ -35,6 +35,7 @@
#:use-module (ice-9 regex)
#:use-module ((ice-9 rdelim) #:select (read-line))
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-2)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
@@ -522,11 +523,17 @@ pypi-uri declaration in the generated package. You may need to replace ~s with
a substring of the PyPI URI that identifies the package.") pypi-url name))
name)))
-(define* (pypi-package->upstream-source pypi-package #:optional version)
+(define* (pypi-package->upstream-source pypi-package
+ #:optional version partial-version?)
"Return the upstream source for the given VERSION of PYPI-PACKAGE, a
-<pypi-project> record. If VERSION is omitted or #f, use the latest version."
+<pypi-project> record. If VERSION is omitted or #f, use the latest version.
+If PARTIAL-VERSION? is #t, use the latest version found that is prefixed by
+VERSION."
(let* ((info (pypi-project-info pypi-package))
- (version (or version (project-info-version info)))
+ (versions (map (match-lambda
+ ((version . _) version))
+ (pypi-project-releases pypi-package)))
+ (version (find-version versions version partial-version?))
(dist (source-release pypi-package version))
(source-url (distribution-url dist))
(wheel-url (and=> (wheel-release pypi-package version)
@@ -661,14 +668,14 @@ source. To build it from source, refer to the upstream repository at
(string-prefix? "https://pypi.org/packages" url)
(string-prefix? "https://files.pythonhosted.org/packages" url)))))
-(define* (import-release package #:key (version #f))
+(define* (import-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE. Optionally
include a VERSION string to fetch a specific version."
- (let* ((pypi-name (guix-package->pypi-name package))
- (pypi-package (pypi-fetch pypi-name)))
- (and pypi-package
- (guard (c ((missing-source-error? c) #f))
- (pypi-package->upstream-source pypi-package version)))))
+ (and-let* ((pypi-name (guix-package->pypi-name package))
+ (pypi-package (pypi-fetch pypi-name)))
+ (guard (c ((missing-source-error? c) #f))
+ (pypi-package->upstream-source pypi-package
+ version partial-version?))))
(define %pypi-updater
(upstream-updater
diff --git a/guix/import/stackage.scm b/guix/import/stackage.scm
index 9554c3d7a4..84aba8aead 100644
--- a/guix/import/stackage.scm
+++ b/guix/import/stackage.scm
@@ -142,7 +142,7 @@ included in the Stackage LTS release."
(mlambda ()
(stackage-lts-packages
(stackage-lts-info-fetch %default-lts-version)))))
- (lambda* (pkg #:key (version #f))
+ (lambda* (pkg #:key version partial-version?)
"Return an <upstream-source> for the latest Stackage LTS release of
PACKAGE or #f if the package is not included in the Stackage LTS release."
(when version
diff --git a/guix/import/test.scm b/guix/import/test.scm
index 4bd356bddc..7414cf5253 100644
--- a/guix/import/test.scm
+++ b/guix/import/test.scm
@@ -18,6 +18,8 @@
(define-module (guix import test)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module ((guix import utils) #:select (find-version))
#:use-module (guix packages)
#:use-module (guix upstream)
#:use-module ((guix utils) #:select (version-prefix?))
@@ -76,18 +78,17 @@
(and (not (vlist-null? (test-target-version))) ;cheap test
(pair? (available-updates package))))
-(define* (import-release package #:key (version #f))
+(define* (import-release package #:key version partial-version?)
"Return the <upstream-source> record denoting either the latest version of
PACKAGE or VERSION."
(match (available-updates package)
(() #f)
((sources ...)
- (if version
- (find (lambda (source)
- (string=? (upstream-source-version source)
- version))
- sources)
- (first sources)))))
+ (let* ((versions (map upstream-source-version sources))
+ (version (find-version versions version partial-version?)))
+ (and version
+ (find (compose (cut string=? version <>) upstream-source-version)
+ sources))))))
(define %test-updater
(upstream-updater
diff --git a/guix/import/texlive.scm b/guix/import/texlive.scm
index c11016853a..bac6a88168 100644
--- a/guix/import/texlive.scm
+++ b/guix/import/texlive.scm
@@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017, 2021, 2022, 2023 Ricardo Wurmus <[email protected]>
-;;; Copyright © 2021 Maxim Cournoyer <[email protected]>
+;;; Copyright © 2021, 2024 Maxim Cournoyer <[email protected]>
;;; Copyright © 2024 Nicolas Goaziou <[email protected]>
;;;
;;; This file is part of GNU Guix.
@@ -35,7 +35,8 @@
#:use-module (guix store)
#:use-module (guix svn-download)
#:use-module (guix upstream)
- #:use-module ((guix utils) #:select (downstream-package-name))
+ #:use-module ((guix utils) #:select (downstream-package-name
+ version>? version-prefix?))
#:use-module (ice-9 ftw)
#:use-module (ice-9 match)
#:use-module (ice-9 popen)
@@ -261,17 +262,21 @@ not succeed."
"Return number of days since Epoch."
(floor (/ (time-second (current-time)) (* 24 60 60))))
-(define latest-texlive-tag
- ;; Return the latest TeX Live tag in repository. The argument refers to
- ;; current day, so memoization is only active a single day, as the
- ;; repository may have been updated between two calls.
+(define texlive-tags
(memoize
(lambda* (#:key (day (current-day)))
- (let ((output
- (svn-command "ls" (string-append %texlive-repository "tags") "-v")))
- ;; E.g. "70951 karl april 15 18:11 texlive-2024.2/\n\n"
- (and=> (string-match "texlive-([^/]+)/\n*$" output)
- (cut match:substring <> 1))))))
+ "Return all tags found in for the TeX Live tags in repository, from
+latest to oldest. The argument refers to current day, so memoization is only
+active a single day, as the repository may have been updated between two
+calls."
+ (let* ((output (svn-command
+ "ls" (string-append %texlive-repository "tags") "-v"))
+ (lines (string-split output #\newline)))
+ ;; Each line look like "70951 karl april 15 18:11 texlive-2024.2/\n\n".
+ (filter-map (lambda (l)
+ (and=> (string-match "texlive-([^/]+)/\n*$" l)
+ (cut match:substring <> 1)))
+ lines)))))
(define string->license
(match-lambda
@@ -761,7 +766,7 @@ associated Guix package, or #f on failure. Fetch metadata for a specific
version whenever VERSION keyword is specified. Otherwise, grab package latest
release. When DATABASE is provided, fetch metadata from there, ignoring
VERSION."
- (let ((version (or version (latest-texlive-tag))))
+ (let ((version (or version (first (texlive-tags)))))
(tlpdb->package name version (or database (tlpdb/cached version))))))
(define* (texlive-recursive-import name #:key repo version)
@@ -785,13 +790,14 @@ VERSION."
(eq? 'texlive
(build-system-name (package-build-system package)))))))
-(define* (latest-release package #:key version)
+(define* (latest-release package #:key version partial-version?)
"Return an <upstream-source> for the latest release of PACKAGE. Optionally
-include a VERSION string to fetch a specific version."
- (let* ((version (or version (latest-texlive-tag)))
+include a VERSION string to fetch a specific version, which may be a partial
+prefix when PARTIAL-VERSION? is #t."
+ (let* ((version (find-version (texlive-tags) version partial-version?))
(database (tlpdb/cached version))
(upstream-name (package-upstream-name* package)))
- (and (assoc-ref database upstream-name)
+ (and version (assoc-ref database upstream-name)
(upstream-source
(package upstream-name)
(version version)
diff --git a/guix/import/utils.scm b/guix/import/utils.scm
index 16301f15f3..38c986b4d5 100644
--- a/guix/import/utils.scm
+++ b/guix/import/utils.scm
@@ -7,7 +7,7 @@
;;; Copyright © 2019 Robert Vollmert <[email protected]>
;;; Copyright © 2020 Helio Machado <[email protected]>
;;; Copyright © 2020 Martin Becze <[email protected]>
-;;; Copyright © 2021 Maxim Cournoyer <[email protected]>
+;;; Copyright © 2021, 2024 Maxim Cournoyer <[email protected]>
;;; Copyright © 2021 Sarah Morgensen <[email protected]>
;;; Copyright © 2021 Xinglu Chen <[email protected]>
;;; Copyright © 2022 Alice Brenon <[email protected]>
@@ -85,6 +85,8 @@
guix-name
+ find-version
+
recursive-import))
(define (factorize-uri uri version)
@@ -620,6 +622,22 @@ separated by PRED."
(define-deprecated/alias guix-name downstream-package-name)
+(define* (find-version versions #:optional version partial?)
+ "Find VERSION amongst VERSIONS. When VERSION is not provided, return the
+latest version. When PARTIAL? is #t, VERSION is treated as a version prefix;
+e.g. finding version \"0.1\" may return \"0.1.8\" if it is the newest \"0.1\"
+prefixed version found in VERSIONS. Return #f when VERSION could not be
+found."
+ (let ((versions (sort versions version>?)))
+ (cond
+ ((and version partial?) ;partial version
+ (find (cut version-prefix? version <>) versions))
+ ((and version (not partial?)) ;exact version
+ (find (cut string=? version <>) versions))
+ ((not (null? versions)) ;latest version
+ (first versions))
+ (else #f)))) ;should not happen
+
(define (topological-sort nodes
node-dependencies
node-name)
diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm
index 6532feef25..a6589ae315 100644
--- a/guix/scripts/refresh.scm
+++ b/guix/scripts/refresh.scm
@@ -10,7 +10,7 @@
;;; Copyright © 2020 Simon Tournier <[email protected]>
;;; Copyright © 2021 Sarah Morgensen <[email protected]>
;;; Copyright © 2022 Hartmut Goebel <[email protected]>
-;;; Copyright © 2023, 2025 Maxim Cournoyer [email protected]>
+;;; Copyright © 2023-2025 Maxim Cournoyer [email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -170,7 +170,9 @@ specified with `--select'.\n"))
-m, --manifest=FILE select all the packages from the manifest in FILE"))
(display (G_ "
--target-version=VERSION
- update the package or packages to VERSION"))
+ update the package or packages to VERSION
+ VERSION may be partially specified, e.g. as 6
+ or 6.4 instead of 6.4.3"))
(display (G_ "
-t, --type=UPDATER,... restrict to updates from the specified updaters
(e.g., 'gnu')"))
@@ -213,20 +215,22 @@ specified with `--select'.\n"))
;;;
(define-record-type <update-spec>
- (%update-spec package version)
+ (%update-spec package version partial?)
update?
(package update-spec-package)
- (version update-spec-version))
+ (version update-spec-version)
+ (partial? update-spec-partial?))
-(define* (update-spec package #:optional version)
- (%update-spec package version))
+(define* (update-spec package #:optional version partial?)
+ (%update-spec package version partial?))
(define (update-specification->update-spec spec fallback-version)
"Given SPEC, a package name like \"[email protected]=2.0.8\", return a <update>
record with two fields: the package to upgrade, and the target version. When
SPEC lacks a version, use FALLBACK-VERSION."
(match (string-rindex spec #\=)
- (#f (update-spec (specification->package spec) fallback-version))
+ (#f (update-spec (specification->package spec) fallback-version
+ (not (not fallback-version))))
(idx (update-spec (specification->package (substring spec 0 idx))
(substring spec (1+ idx))))))
@@ -282,9 +286,9 @@ update would trigger a complete rebuild."
spec target-version)))
(('expression . exp)
(list (update-spec (read/eval-package-expression exp)
- target-version)))
+ target-version #t)))
(('manifest . manifest)
- (map (cut update-spec <> target-version)
+ (map (cut update-spec <> target-version #t)
(packages-from-manifest manifest)))
(_
'()))
@@ -364,92 +368,97 @@ update would trigger a complete rebuild."
(G_ "no updater for ~a~%")
(package-name package)))
-(define* (update-package store package version updaters
+(define* (update-package store update-spec updaters
#:key (key-download 'auto) key-server
warn?)
- "Update the source file that defines PACKAGE with the new version.
-KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
-values: 'auto' (default), interactive', 'always', and 'never'. When WARN? is
-true, warn about packages that have no matching updater."
- (if (lookup-updater package updaters)
- (let ((version output source
- (package-update store package updaters
- #:version version
- #:key-download key-download
- #:key-server key-server))
- (loc (or (package-field-location package 'version)
- (package-location package))))
- (when version
- (if (and=> output file-exists?)
- (begin
- (info loc
- (G_ "~a: updating from version ~a to version ~a...~%")
- (package-name package)
- (package-version package) version)
- (let ((hash (file-hash* output)))
- (update-package-source package source hash)))
- (warning (G_ "~a: version ~a could not be \
+ "Update the source file that correspond to the package in UPDATE-SPEC,
+an <update-spec> object. KEY-DOWNLOAD specifies a download policy for
+missing OpenPGP keys; allowed values: 'auto' (default), 'interactive',
+'always', and 'never'. When WARN? is true, warn about packages that
+have no matching updater. PARTIAL-VERSION? is provided to the
+underlying `package-update' call; see its documentation for the
+details."
+ (match update-spec
+ (($ <update-spec> package version partial?)
+ (if (lookup-updater package updaters)
+ (let ((version output source
+ (package-update store package updaters
+ #:version version
+ #:partial-version? partial?
+ #:key-download key-download
+ #:key-server key-server))
+ (loc (or (package-field-location package 'version)
+ (package-location package))))
+ (when version
+ (if (and=> output file-exists?)
+ (begin
+ (info loc
+ (G_ "~a: updating from version ~a to version ~a...~%")
+ (package-name package)
+ (package-version package) version)
+ (let ((hash (file-hash* output)))
+ (update-package-source package source hash)))
+ (warning (G_ "~a: version ~a could not be \
downloaded and authenticated; not updating~%")
- (package-name package) version))))
- (when warn?
- (warn-no-updater package))))
+ (package-name package) version))))
+ (when warn?
+ (warn-no-updater package))))))
(define* (check-for-package-update update-spec updaters #:key warn?)
"Check whether UPDATE-SPEC is feasible, and print a message.
When WARN? is true and no updater exists for PACKAGE, print a warning."
- (define package
- (update-spec-package update-spec))
-
- (match (package-latest-release package updaters
- #:version
- (update-spec-version update-spec))
- ((? upstream-source? source)
- (let ((loc (or (package-field-location package 'version)
- (package-location package))))
- (case (version-compare (upstream-source-version source)
- (package-version package))
- ((>)
- (info loc
- (G_ "~a would be upgraded from ~a to ~a~%")
- (package-name package) (package-version package)
- (upstream-source-version source)))
- ((=)
- (when warn?
- (info loc
- (G_ "~a is already the latest version of ~a~%")
- (package-version package)
- (package-name package))))
- (else
- (if (update-spec-version update-spec)
- (info loc
- (G_ "~a would be downgraded from ~a to ~a~%")
- (package-name package)
- (package-version package)
- (upstream-source-version source))
- (when warn?
- (warning loc
- (G_ "~a is greater than \
+ (match update-spec
+ (($ <update-spec> package version partial?)
+ (match (package-latest-release package updaters
+ #:version version
+ #:partial-version? partial?)
+ ((? upstream-source? source)
+ (let ((loc (or (package-field-location package 'version)
+ (package-location package))))
+ (case (version-compare (upstream-source-version source)
+ (package-version package))
+ ((>)
+ (info loc
+ (G_ "~a would be upgraded from ~a to ~a~%")
+ (package-name package) (package-version package)
+ (upstream-source-version source)))
+ ((=)
+ (when warn?
+ (info loc
+ (G_ "~a is already the latest version of ~a~%")
+ (package-version package)
+ (package-name package))))
+ (else
+ (if version
+ (info loc
+ (G_ "~a would be downgraded from ~a to ~a~%")
+ (package-name package)
+ (package-version package)
+ (upstream-source-version source))
+ (when warn?
+ (warning loc
+ (G_ "~a is greater than \
the latest known version of ~a (~a)~%")
- (package-version package)
- (package-name package)
- (upstream-source-version source))))))))
- (#f
- (when warn?
- ;; Distinguish between "no updater" and "failing updater."
- (match (lookup-updater package updaters)
- ((? upstream-updater? updater)
- (if (update-spec-version update-spec)
- (warning (G_ "'~a' updater failed to find version ~a of '~a'~%")
- (upstream-updater-name updater)
- (update-spec-version update-spec)
- (package-name package))
- (warning (package-location package)
- (G_ "'~a' updater failed to determine available \
+ (package-version package)
+ (package-name package)
+ (upstream-source-version source))))))))
+ (#f
+ (when warn?
+ ;; Distinguish between "no updater" and "failing updater."
+ (match (lookup-updater package updaters)
+ ((? upstream-updater? updater)
+ (if version
+ (warning (G_ "'~a' updater failed to find version ~a of '~a'~%")
+ (upstream-updater-name updater)
+ version
+ (package-name package))
+ (warning (package-location package)
+ (G_ "'~a' updater failed to determine available \
releases for ~a~%")
- (upstream-updater-name updater)
- (package-name package))))
- (#f
- (warn-no-updater package)))))))
+ (upstream-updater-name updater)
+ (package-name package))))
+ (#f
+ (warn-no-updater package)))))))))
;;;
@@ -634,10 +643,9 @@ all are dependent packages: ~{~a~^ ~}~%")
(compose location-line
spec->location)))))
(for-each
- (lambda (update)
+ (lambda (spec)
(update-package store
- (update-spec-package update)
- (update-spec-version update)
+ spec
updaters
#:key-server (%openpgp-key-server)
#:key-download key-download
diff --git a/guix/upstream.scm b/guix/upstream.scm
index 169d260c2d..af09f62088 100644
--- a/guix/upstream.scm
+++ b/guix/upstream.scm
@@ -263,16 +263,17 @@ them matches."
(define* (package-latest-release package
#:optional
(updaters (force %updaters))
- #:key (version #f))
- "Return an upstream source to update PACKAGE, a <package> object, or #f if
-none of UPDATERS matches PACKAGE. When several updaters match PACKAGE, try
-them until one of them returns an upstream source. It is the caller's
-responsibility to ensure that the returned source is newer than the current
-one."
+ #:key version partial-version?)
+ "Return an <upstream-source> object to update PACKAGE, a <package> object,
+or #f if none of UPDATERS matches PACKAGE. When several updaters match
+PACKAGE, try them until one of them returns an upstream source. It is the
+caller's responsibility to ensure that the returned source is newer than the
+current one."
(any (match-lambda
(($ <upstream-updater> name description pred import)
(and (pred package)
- (import package #:version version))))
+ (import package #:version version
+ #:partial-version? partial-version?))))
updaters))
(define* (package-latest-release* package
@@ -511,7 +512,7 @@ SOURCE, an <upstream-source>."
(define* (package-update store package
#:optional (updaters (force %updaters))
- #:key (version #f)
+ #:key version partial-version?
(key-download 'auto) key-server)
"Return the new version, the file name of the new version tarball, and input
changes for PACKAGE; return #f (three values) when PACKAGE is up-to-date;
@@ -520,8 +521,13 @@ KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
values: 'always', 'auto' (default), 'never', and 'interactive'.
When VERSION is specified, update PACKAGE to that version, even if that is a
-downgrade."
- (match (package-latest-release package updaters #:version version)
+downgrade. When PARTIAL-VERSION? is true, treat VERSION as having been only
+partially specified, in which case the package will be updated to the newest
+compatible version if there are no exact match for VERSION. For example,
+providing \"46\" as the version may update the package to version \"46.6.4\"."
+ (match (package-latest-release package updaters
+ #:version version
+ #:partial-version? partial-version?)
((? upstream-source? source)
(if (or (version>? (upstream-source-version source)
(package-version package))
diff --git a/tests/gem.scm b/tests/gem.scm
index dae29437e5..beee150875 100644
--- a/tests/gem.scm
+++ b/tests/gem.scm
@@ -49,6 +49,25 @@
\"licenses\": [\"MIT\", \"Apache 2.0\"]
}")
+(define test-foo-versions-json
+ "[{\"authors\": \" Maxim \",
+ \"built_at\": \"2012-10-24T00:00:00.000Z\",
+ \"created_at\": \"2012-11-03T07:41:49.007Z\",
+ \"description\": \"test gem\",
+ \"downloads_count\" :9195,
+ \"metadata\": {\"homepage_uri\":\"\"},
+ \"number\": \"1.0.0\",
+ \"summary\": \"foo!!!\",
+ \"platform\": \"ruby\",
+ \"rubygems_version\": \"\u003e= 0\",
+ \"ruby_version\": null,
+ \"priceless\": false,
+ \"licenses\": null,
+ \"requirements\": null,
+ \"sha\": \"523009a5b977f79c8eaa79b521e416f26482bc4fbbcc04bd08580696e303a715\",
+ \"spec_sha\": \"c7cf42bac0d01eb12b68294d1cdb4e20e7cb222ca958ad70ed1e9a686b551819\"
+}]")
+
(define test-foo-v2-json
"{
\"name\": \"foo\",
@@ -273,6 +292,9 @@
("https://rubygems.org/api/v1/gems/foo.json"
(values (open-input-string test-foo-json)
(string-length test-foo-json)))
+ ("https://rubygems.org/api/v1/versions/foo.json"
+ (values (open-input-string test-foo-versions-json)
+ (string-length test-foo-versions-json)))
(_ (error "Unexpected URL: " url)))))
(let ((source (package-latest-release
(dummy-package "ruby-foo"
diff --git a/tests/gnu-maintenance.scm b/tests/gnu-maintenance.scm
index 6fde1eb8b1..82a02bec6f 100644
--- a/tests/gnu-maintenance.scm
+++ b/tests/gnu-maintenance.scm
@@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2015, 2021 Ludovic Courtès <[email protected]>
;;; Copyright © 2022 Maxime Devos <[email protected]>
+;;; Copyright © 2023-2024 Maxim Cournoyer <[email protected]>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -26,6 +27,7 @@
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-64)
#:use-module ((web client) #:select (current-http-proxy))
+ #:use-module ((web uri) #:select (uri? uri->string))
#:use-module (ice-9 match))
(test-begin "gnu-maintenance")
@@ -157,11 +159,17 @@ submodules/qtbase-everywhere-src-6.5.2.tar.xz"
(rewrite-url "https://download.qt.io/official_releases/qt/6.3/6.3.2/\
submodules/qtbase-everywhere-src-6.3.2.tar.xz" "6.3.2" #:to-version "6.5.2"))
-(test-equal "rewrite-url, without to-version"
- "http://dist.libuv.example.org/dist/v1.46.0/libuv-v1.46.0.tar.gz"
- (with-http-server
- ;; First reply, crawling http://dist.libuv.example.org/dist/.
- `((200 "\
+(define (mock-http-fetch/cached testcase)
+ (lambda (url . rest)
+ (let* ((url (if (uri? url)
+ (uri->string url)
+ url))
+ (body (assoc-ref testcase url)))
+ (if body
+ (open-input-string body)
+ (error "mocked http-fetch Unexpected URL: " url)))))
+
+(define libuv-dist-html "\
<!DOCTYPE html>
<html>
<head><title>Index of dist</title></head>
@@ -174,8 +182,8 @@ submodules/qtbase-everywhere-src-6.3.2.tar.xz" "6.3.2" #:to-version "6.5.2"))
<a href=\"v1.46.0/\" title=\"v1.46.0/\">v1.46.0/</a>
</body>
</html>")
- ;; Second reply, crawling http://dist.libuv.example.org/dist/v1.46.0/.
- (200 "\
+
+(define libuv-dist-1.46.0-html "\
<!DOCTYPE html>
<html>
<head><title>Index of dist/v1.46.0</title></head>
@@ -190,9 +198,44 @@ submodules/qtbase-everywhere-src-6.3.2.tar.xz" "6.3.2" #:to-version "6.5.2"))
<a href=\"libuv-v1.46.0.tar.gz.sign\" title=\"libuv-v1.46.0.tar.gz.sign\">
libuv-v1.46.0.tar.gz.sign</a>
</body>
-</html>"))
- (parameterize ((current-http-proxy (%local-url)))
- (rewrite-url "http://dist.libuv.example.org/dist/v1.45.0/libuv-v1.45.0.tar.gz"
- "1.45.0"))))
+</html>")
+
+(define libuv-dist-1.44.2-html "\
+<!DOCTYPE html>
+<html>
+<head><title>Index of dist/v1.44.2</title></head>
+<body>
+<a href=\"../\">../</a>
+<a href=\"libuv-v1.44.2-dist.tar.gz\" title=\"libuv-v1.44.2-dist.tar.gz\">
+ libuv-v1.44.2-dist.tar.gz</a>
+<a href=\"libuv-v1.44.2-dist.tar.gz.sign\" title=\"libuv-v1.44.2-dist.tar.gz.sign\">
+ libuv-v1.44.2-dist.tar.gz.sign</a>
+<a href=\"libuv-v1.44.2.tar.gz\" title=\"libuv-v1.44.2.tar.gz\">
+ libuv-v1.44.2.tar.gz</a>
+<a href=\"libuv-v1.44.2.tar.gz.sign\" title=\"libuv-v1.44.2.tar.gz.sign\">
+ libuv-v1.44.2.tar.gz.sign</a>
+</body>
+</html>")
+
+(define libuv-html-data
+ `(("http://dist.libuv.example.org/dist" . ,libuv-dist-html)
+ ("http://dist.libuv.example.org/dist/v1.44.2" . ,libuv-dist-1.44.2-html)
+ ("http://dist.libuv.example.org/dist/v1.46.0" . ,libuv-dist-1.46.0-html)))
+
+(test-equal "rewrite-url, without to-version"
+ "http://dist.libuv.example.org/dist/v1.46.0/libuv-v1.46.0.tar.gz"
+ (mock ((guix http-client) http-fetch/cached
+ (mock-http-fetch/cached libuv-html-data))
+ (rewrite-url
+ "http://dist.libuv.example.org/dist/v1.45.0/libuv-v1.45.0.tar.gz"
+ "1.45.0")))
+
+(test-equal "rewrite-url, partial to-version"
+ "http://dist.libuv.example.org/dist/v1.44.2/libuv-v1.44.2.tar.gz"
+ (mock ((guix http-client) http-fetch/cached
+ (mock-http-fetch/cached libuv-html-data))
+ (rewrite-url
+ "http://dist.libuv.example.org/dist/v1.45.0/libuv-v1.45.0.tar.gz"
+ "1.45.0" #:to-version "1.44" #:partial-version? #t)))
(test-end)
diff --git a/tests/guix-refresh.sh b/tests/guix-refresh.sh
index 2ce3c592ab..b5b38189cb 100644
--- a/tests/guix-refresh.sh
+++ b/tests/guix-refresh.sh
@@ -31,7 +31,8 @@ export GUIX_TEST_UPDATER_TARGETS
idutils_version="$(guix package -A ^idutils$ | cut -f2)"
GUIX_TEST_UPDATER_TARGETS='
(("guile" "3" (("12.5" "file:///dev/null")
- ("1.6.4" "file:///dev/null")))
+ ("1.6.4" "file:///dev/null")
+ ("3.13.3" "file:///dev/null")))
("libreoffice" "" (("1.0" "file:///dev/null")))
("idutils" "" (("'$idutils_version'" "file:///dev/null")))
("the-test-package" "" (("5.5" "file://'$PWD/$module_dir'/source"
@@ -116,6 +117,13 @@ case "$(guix refresh -t test guile --target-version=2.0.0 2>&1)" in
*) false;;
esac
+# Partial target version => select the newest release prefixed by it.
+guix refresh -t test guile --target-version=3 # XXX: should return non-zero?
+case "$(guix refresh -t test guile --target-version=3 2>&1)" in
+ *"would be upgraded"*"3.13.3"*) true;;
+ *) false;;
+esac
+
for spec in "guile=1.6.4" "guile@3=1.6.4"
do
guix refresh -t test "$spec"
diff --git a/tests/import-git.scm b/tests/import-git.scm
index 6dd8ad1649..a532070a8d 100644
--- a/tests/import-git.scm
+++ b/tests/import-git.scm
@@ -22,6 +22,7 @@
#:use-module (guix tests)
#:use-module (guix packages)
#:use-module (guix import git)
+ #:use-module ((guix import utils) #:select (find-version))
#:use-module (guix git-download)
#:use-module (guix tests git)
#:use-module (srfi srfi-1)
@@ -45,6 +46,9 @@
(base32
"0000000000000000000000000000000000000000000000000000"))))))
+(define (latest-git-tag-version package)
+ (find-version (map car ((@@ (guix import git) get-package-tags) package))))
+
(test-equal "latest-git-tag-version: no custom prefix, suffix, and delimiter"
"1.0.1"
(with-temporary-git-repository directory