1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2017, 2018 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
5 ;;; This file is part of GNU Guix.
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;;; GNU General Public License for more details.
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
22 ;;; This scripts updates the definition of the 'guix' package in Guix for the
23 ;;; current commit. It requires Git to be installed.
35 (gnu packages package-management)
45 (string-append (current-source-directory) "/.."))
47 (define (package-definition-location)
48 "Return the source properties of the definition of the 'guix' package."
49 (call-with-input-file (location-file (package-location guix))
54 (error "definition of 'guix' package could not be found"
55 (port-filename port)))
56 (('define-public 'guix value)
57 (source-properties value))
61 (define* (update-definition commit hash
62 #:key version old-hash)
63 "Return a one-argument procedure that takes a string, the definition of the
64 'guix' package, and returns a string, the update definition for VERSION,
66 (define (linear-offset str line column)
67 ;; Return the offset in characters to reach LINE and COLUMN (both
68 ;; zero-indexed) in STR.
69 (call-with-input-string str
71 (let loop ((offset 0))
72 (cond ((and (= (port-column port) column)
73 (= (port-line port) line))
75 ((eof-object? (read-char port))
76 (error "line and column not reached!"
79 (loop (+ 1 offset))))))))
81 (define (update-hash str)
82 ;; Replace OLD-HASH with HASH in STR.
83 (string-replace-substring str
84 (bytevector->nix-base32-string old-hash)
85 (bytevector->nix-base32-string hash)))
88 (match (call-with-input-string str read)
89 (('let (('version old-version)
91 ('revision old-revision))
93 (let* ((location (source-properties defn))
94 (line (assq-ref location 'line))
96 (offset (linear-offset str line column)))
97 (string-append (format #f "(let ((version \"~a\")
100 (or version old-version)
103 (not (string=? version old-version)))
106 (string-drop (update-hash str) offset))))
108 (error "'guix' package definition is not as expected" exp)))))
110 (define (git-add-worktree directory commit)
111 "Create a new git worktree at DIRECTORY, detached on commit COMMIT."
112 (invoke "git" "worktree" "add" "--detach" directory commit))
114 (define (call-with-temporary-git-worktree commit proc)
115 "Execute PROC in the context of a temporary git worktree created from
116 COMMIT. PROC receives the temporary directory file name as an argument."
117 (call-with-temporary-directory
118 (lambda (tmp-directory)
123 (git-add-worktree tmp-directory commit)
124 (proc tmp-directory))
126 (invoke "git" "worktree" "remove" "--force" tmp-directory))))))
128 (define %savannah-guix-git-repo-push-url-regexp
129 "git.(savannah|sv).gnu.org:?/srv/git/guix.git \\(push\\)")
131 (define-syntax-rule (with-input-pipe-to-string prog arg ...)
132 (let* ((input-pipe (open-pipe* OPEN_READ prog arg ...))
133 (output (get-string-all input-pipe))
134 (exit-val (status:exit-val (close-pipe input-pipe))))
135 (unless (zero? exit-val)
136 (error (format #f "Command ~s exited with non-zero exit status: ~s"
137 (string-join (list prog arg ...)) exit-val)))
138 (string-trim-both output)))
140 (define (find-origin-remote)
141 "Find the name of the git remote with the Savannah Guix git repo URL."
142 (and-let* ((remotes (string-split (with-input-pipe-to-string
145 (origin-entry (find (cut string-match
146 %savannah-guix-git-repo-push-url-regexp
149 (first (string-split origin-entry #\tab))))
151 (define (commit-already-pushed? remote commit)
152 "True if COMMIT is found in the REMOTE repository."
153 (not (string-null? (with-input-pipe-to-string
154 "git" "branch" "-r" "--contains" commit
155 (string-append remote "/master")))))
157 (define (keep-source-in-store store source)
158 "Add SOURCE to the store under the name that the 'guix' package expects."
160 ;; Add SOURCE to the store, but this time under the real name used in the
161 ;; 'origin'. This allows us to build the package without having to make a
162 ;; real checkout; thus, it also works when working on a private branch.
164 (resolve-module '(gnu packages package-management)))
166 (let* ((source (add-to-store store
167 (origin-file-name (package-source guix))
169 #:select? (git-predicate source)))
170 (root (store-path-package-name source)))
172 ;; Add an indirect GC root for SOURCE in the current directory.
173 (false-if-exception (delete-file root))
174 (symlink source root)
175 (add-indirect-root store
176 (string-append (getcwd) "/" root))
178 (info (G_ "source code kept in ~a (GC root: ~a)~%")
182 (define (main . args)
185 (with-directory-excursion %top-srcdir
186 (or (getenv "GUIX_ALLOW_ME_TO_USE_PRIVATE_COMMIT")
187 (let ((remote (find-origin-remote)))
189 (leave (G_ "Failed to find the origin git remote.~%")))
190 (commit-already-pushed? remote commit))
191 (leave (G_ "Commit ~a is not pushed upstream. Aborting.~%") commit))
192 (call-with-temporary-git-worktree commit
193 (lambda (tmp-directory)
194 (let* ((hash (nix-base32-string->bytevector
196 (with-output-to-string
198 (guix-hash "-rx" tmp-directory))))))
199 (location (package-definition-location))
200 (old-hash (content-hash-value
201 (origin-hash (package-source guix)))))
202 (edit-expression location
203 (update-definition commit hash
206 ;; When GUIX_ALLOW_ME_TO_USE_PRIVATE_COMMIT is set, the sources are
207 ;; added to the store. This is used as part of 'make release'.
208 (when (getenv "GUIX_ALLOW_ME_TO_USE_PRIVATE_COMMIT")
210 (keep-source-in-store store tmp-directory))))))))
212 ;; Automatically deduce the version and revision numbers.
215 (apply main (cdr (command-line)))