guix-mirrors/guix/import/git.scm
Maxim Cournoyer f13f076968
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: 75871@debbugs.gnu.org
Change-Id: I092a58b57ac42e54a2fa55e7761e8c6993af8ad4
2025-02-28 13:36:44 +09:00

225 lines
8.8 KiB
Scheme
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
;;; Copyright © 2022 Maxime Devos <maximedevos@telenet.be>
;;; Copyright © 2022 Hartmut Goebel <h.goebel@crazy-compilers.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix import git)
#:use-module (guix i18n)
#:use-module (guix diagnostics)
#:use-module (guix git)
#:use-module (guix git-download)
#:use-module (guix packages)
#:use-module (guix upstream)
#:use-module ((guix import utils) #:select (find-version))
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (srfi srfi-71)
#:export (%generic-git-updater))
;;; Commentary:
;;;
;;; This module provides a generic package updater for packages hosted on Git
;;; repositories.
;;;
;;; It tries to be smart about tag names, but if it is not automatically able
;;; to parse the tag names correctly, users can set the `release-tag-prefix',
;;; `release-tag-suffix' and `release-tag-version-delimiter' properties of the
;;; package to make the updater parse the Git tag name correctly.
;;;
;;; Possible improvements:
;;;
;;; * More robust method for trying to guess the delimiter. Maybe look at the
;;; previous version/tag combo to determine the delimiter.
;;;
;;; * Differentiate between "normal" versions, e.g., 1.2.3, and dates, e.g.,
;;; 2021.12.31. Honor a `release-tag-date-scheme?' property?
;;;
;;; Code:
;;; Errors & warnings
(define-condition-type &git-no-valid-tags-error &error
git-no-valid-tags-error?)
(define (git-no-valid-tags-error)
(raise (condition (&message (message "no valid tags found"))
(&git-no-valid-tags-error))))
(define-condition-type &git-no-tags-error &error
git-no-tags-error?)
(define (git-no-tags-error)
(raise (condition (&message (message "no tags were found"))
(&git-no-tags-error))))
;;; Updater
(define %pre-release-words
'("alpha" "beta" "rc" "dev" "test" "pre"))
(define %pre-release-rx
(map (lambda (word)
(make-regexp (string-append ".+" word) regexp/icase))
%pre-release-words))
(define* (version-mapping tags #:key prefix suffix delim pre-releases?)
"Given a list of Git TAGS, return an association list where the car is the
version corresponding to the tag, and the cdr is the name of the tag."
(define (guess-delimiter)
(let ((total (length tags))
(dots (reduce + 0 (map (cut string-count <> #\.) tags)))
(dashes (reduce + 0 (map (cut string-count <> #\-) tags)))
(underscores (reduce + 0 (map (cut string-count <> #\_) tags))))
(cond
((>= dots (* total 0.35)) ".")
((>= dashes (* total 0.8)) "-")
((>= underscores (* total 0.8)) "_")
(else ""))))
(define delim-rx (regexp-quote (or delim (guess-delimiter))))
(define suffix-rx (string-append (or suffix "") "$"))
(define prefix-rx (string-append "^" (or prefix "[^[:digit:]]*")))
(define pre-release-rx
(if pre-releases?
(string-append "(.*(" (string-join %pre-release-words "|") ").*)")
""))
(define tag-rx
(string-append prefix-rx "([[:digit:]][^" delim-rx "[:punct:]]*"
"(" delim-rx "[^[:punct:]" delim-rx "]+)"
;; If there are no delimiters, it could mean that the
;; version just contains one number (e.g., "2"), thus, use
;; "*" instead of "+" to match zero or more numbers.
(if (string=? delim-rx "") "*" "+") ")"
;; We don't want the pre-release stuff (e.g., "-alpha") be
;; part of the first group; otherwise, the "-" in "-alpha"
;; might be interpreted as a delimiter, and thus replaced
;; 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)))
(and=> (and tag-match
(regexp-substitute/global
#f delim-rx (match:substring tag-match 1)
;; If there were no delimiters, don't insert ".".
'pre (if (string=? delim-rx "") "" ".") 'post))
(lambda (version)
(if pre-releases?
(string-append version (match:substring tag-match 3))
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
(version-mapping tags
#:prefix prefix
#:suffix suffix
#:delim delim
#:pre-releases? pre-releases?)))
(cond
((null? tags)
(git-no-tags-error))
((null? versions+tags)
(git-no-valid-tags-error))
(else
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))
'())
((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))
'()))
(let* ((source (package-source package))
(url (git-reference-url (origin-uri source)))
(property (cute assq-ref (package-properties package) <>)))
(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."
(match (package-source package)
((? origin? origin)
(and (eq? (origin-method origin) git-fetch)
(git-reference? (origin-uri origin))))
(_ #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, 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)))
(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 version)
(urls (git-reference
(url (git-reference-url old-reference))
(commit tag)
(recursive? (git-reference-recursive? old-reference))))))))
(define %generic-git-updater
(upstream-updater
(name 'generic-git)
(description "Updater for packages hosted on Git repositories")
(pred git-package?)
(import import-git-release)))