summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2012-11-01 01:46:15 +0100
committerLudovic Courtès <ludo@gnu.org>2012-11-01 01:46:15 +0100
commit0afdc48532ea7d8eea32b3e3b78ba3832e7f18b2 (patch)
tree666db7447c4b7e96c16241b6c6647bbac72290d0
parente3d741065e29b6f0d050592da853b641205c21bc (diff)
Add a preliminary `guix-package' command-line tool.
* guix-package.in, tests/guix-package.sh: New files. * configure.ac: Output `guix-package'. * Makefile.am (TESTS): Add `tests/guix-package.sh'. (bin_SCRIPTS): Add `guix-package'.
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am6
-rw-r--r--configure.ac3
-rw-r--r--guix-package.in392
-rw-r--r--tests/guix-package.sh43
5 files changed, 442 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 816660ecd12..e6b254ae44e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,3 +47,4 @@ config.cache
47/distro/packages/bootstrap/x86_64-linux/guile-bootstrap-2.0.6.tar.xz 47/distro/packages/bootstrap/x86_64-linux/guile-bootstrap-2.0.6.tar.xz
48/guix-download 48/guix-download
49/distro/packages/bootstrap/i686-linux/guile-bootstrap-2.0.6.tar.xz 49/distro/packages/bootstrap/i686-linux/guile-bootstrap-2.0.6.tar.xz
50/guix-package
diff --git a/Makefile.am b/Makefile.am
index d3a3dbf69a0..8a564624b93 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,7 +18,8 @@
18 18
19bin_SCRIPTS = \ 19bin_SCRIPTS = \
20 guix-build \ 20 guix-build \
21 guix-download 21 guix-download \
22 guix-package
22 23
23MODULES = \ 24MODULES = \
24 guix/utils.scm \ 25 guix/utils.scm \
@@ -120,7 +121,8 @@ TESTS = \
120 tests/build-utils.scm \ 121 tests/build-utils.scm \
121 tests/packages.scm \ 122 tests/packages.scm \
122 tests/union.scm \ 123 tests/union.scm \
123 tests/guix-build.sh 124 tests/guix-build.sh \
125 tests/guix-package.sh
124 126
125TEST_EXTENSIONS = .scm .sh 127TEST_EXTENSIONS = .scm .sh
126 128
diff --git a/configure.ac b/configure.ac
index a95d2b80bef..d9a5f07f4f8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -82,9 +82,10 @@ AC_CONFIG_FILES([Makefile
82 po/Makefile.in 82 po/Makefile.in
83 guix-build 83 guix-build
84 guix-download 84 guix-download
85 guix-package
85 pre-inst-env]) 86 pre-inst-env])
86 87
87AC_CONFIG_COMMANDS([commands-exec], 88AC_CONFIG_COMMANDS([commands-exec],
88 [chmod +x guix-build guix-download pre-inst-env]) 89 [chmod +x guix-build guix-download guix-package pre-inst-env])
89 90
90AC_OUTPUT 91AC_OUTPUT
diff --git a/guix-package.in b/guix-package.in
new file mode 100644
index 00000000000..5b10149d9fd
--- /dev/null
+++ b/guix-package.in
@@ -0,0 +1,392 @@
1#!/bin/sh
2# aside from this initial boilerplate, this is actually -*- scheme -*- code
3
4prefix="@prefix@"
5datarootdir="@datarootdir@"
6
7GUILE_LOAD_COMPILED_PATH="@guilemoduledir@:$GUILE_LOAD_COMPILED_PATH"
8export GUILE_LOAD_COMPILED_PATH
9
10main='(module-ref (resolve-interface '\''(guix-package)) '\'guix-package')'
11exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0" \
12 -c "(apply $main (cdr (command-line)))" "$@"
13!#
14;;; Guix --- Nix package management from Guile. -*- coding: utf-8 -*-
15;;; Copyright (C) 2012 Ludovic Courtès <ludo@gnu.org>
16;;;
17;;; This file is part of Guix.
18;;;
19;;; Guix is free software; you can redistribute it and/or modify it
20;;; under the terms of the GNU General Public License as published by
21;;; the Free Software Foundation; either version 3 of the License, or (at
22;;; your option) any later version.
23;;;
24;;; Guix is distributed in the hope that it will be useful, but
25;;; WITHOUT ANY WARRANTY; without even the implied warranty of
26;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27;;; GNU General Public License for more details.
28;;;
29;;; You should have received a copy of the GNU General Public License
30;;; along with Guix. If not, see <http://www.gnu.org/licenses/>.
31
32(define-module (guix-package)
33 #:use-module (guix store)
34 #:use-module (guix derivations)
35 #:use-module (guix packages)
36 #:use-module (guix utils)
37 #:use-module (ice-9 ftw)
38 #:use-module (ice-9 format)
39 #:use-module (ice-9 match)
40 #:use-module (ice-9 regex)
41 #:use-module (srfi srfi-1)
42 #:use-module (srfi srfi-11)
43 #:use-module (srfi srfi-26)
44 #:use-module (srfi srfi-34)
45 #:use-module (srfi srfi-37)
46 #:autoload (distro) (find-packages-by-name)
47 #:use-module (distro packages base)
48 #:export (guix-package))
49
50(define _ (cut gettext <> "guix"))
51(define N_ (cut ngettext <> <> <> "guix"))
52
53(define %store
54 (open-connection))
55
56
57;;;
58;;; User environment.
59;;;
60
61(define %user-environment-directory
62 (and=> (getenv "HOME")
63 (cut string-append <> "/.guix-profile")))
64
65(define %profile-directory
66 (string-append "/nix/var/nix/profiles/"
67 "guix/"
68 (or (and=> (getenv "USER")
69 (cut string-append "per-user/" <>))
70 "default")))
71
72(define %current-profile
73 (string-append %profile-directory "/profile"))
74
75(define (profile-manifest profile)
76 "Return the PROFILE's manifest."
77 (let ((manifest (string-append profile "/manifest")))
78 (if (file-exists? manifest)
79 (call-with-input-file manifest read)
80 '(manifest (version 0) (packages ())))))
81
82(define (manifest-packages manifest)
83 "Return the packages listed in MANIFEST."
84 (match manifest
85 (('manifest ('version 0) ('packages packages))
86 packages)
87 (_
88 (error "unsupported manifest format" manifest))))
89
90(define (latest-profile-number profile)
91 "Return the identifying number of the latest generation of PROFILE.
92PROFILE is the name of the symlink to the current generation."
93 (define %profile-rx
94 (make-regexp (string-append "^" (regexp-quote (basename profile))
95 "-([0-9]+)")))
96
97 (define* (scandir name #:optional (select? (const #t))
98 (entry<? (@ (ice-9 i18n) string-locale<?)))
99 ;; XXX: Bug-fix version introduced in Guile v2.0.6-62-g139ce19.
100 (define (enter? dir stat result)
101 (and stat (string=? dir name)))
102
103 (define (visit basename result)
104 (if (select? basename)
105 (cons basename result)
106 result))
107
108 (define (leaf name stat result)
109 (and result
110 (visit (basename name) result)))
111
112 (define (down name stat result)
113 (visit "." '()))
114
115 (define (up name stat result)
116 (visit ".." result))
117
118 (define (skip name stat result)
119 ;; All the sub-directories are skipped.
120 (visit (basename name) result))
121
122 (define (error name* stat errno result)
123 (if (string=? name name*) ; top-level NAME is unreadable
124 result
125 (visit (basename name*) result)))
126
127 (and=> (file-system-fold enter? leaf down up skip error #f name lstat)
128 (lambda (files)
129 (sort files entry<?))))
130
131 (match (scandir (dirname profile)
132 (cut regexp-exec %profile-rx <>))
133 (#f ; no profile directory
134 0)
135 (() ; no profiles
136 0)
137 ((profiles ...) ; former profiles around
138 (let ((numbers (map (compose string->number
139 (cut match:substring <> 1)
140 (cut regexp-exec %profile-rx <>))
141 profiles)))
142 (fold (lambda (number highest)
143 (if (> number highest)
144 number
145 highest))
146 0
147 numbers)))))
148
149(define (profile-derivation store packages)
150 "Return a derivation that builds a profile (a user environment) with
151all of PACKAGES, a list of name/version/output/path tuples."
152 (define builder
153 `(begin
154 (use-modules (ice-9 pretty-print)
155 (guix build union))
156
157 (setvbuf (current-output-port) _IOLBF)
158 (setvbuf (current-error-port) _IOLBF)
159
160 (let ((output (assoc-ref %outputs "out"))
161 (inputs (map cdr %build-inputs)))
162 (format #t "building user environment `~a' with ~a packages...~%"
163 output (length inputs))
164 (union-build output inputs)
165 (call-with-output-file (string-append output "/manifest")
166 (lambda (p)
167 (pretty-print '(manifest (version 0)
168 (packages ,packages))
169 p))))))
170
171 (build-expression->derivation store "user-environment"
172 (%current-system)
173 builder
174 (map (match-lambda
175 ((name version output path)
176 `(,name ,path)))
177 packages)
178 #:modules '((guix build union))))
179
180
181;;;
182;;; Command-line options.
183;;;
184
185(define %default-options
186 ;; Alist of default option values.
187 `((profile . ,%current-profile)))
188
189(define-syntax-rule (leave fmt args ...)
190 "Format FMT and ARGS to the error port and exit."
191 (begin
192 (format (current-error-port) fmt args ...)
193 (exit 1)))
194
195(define (show-version)
196 (display "guix-package (@PACKAGE_NAME@) @PACKAGE_VERSION@\n"))
197
198(define (show-help)
199 (display (_ "Usage: guix-package [OPTION]... PACKAGES...
200Install, remove, or upgrade PACKAGES in a single transaction.\n"))
201 (display (_ "
202 -i, --install=PACKAGE install PACKAGE"))
203 (display (_ "
204 -r, --remove=PACKAGE remove PACKAGE"))
205 (display (_ "
206 -u, --upgrade=REGEXP upgrade all the installed packages matching REGEXP"))
207 (newline)
208 (display (_ "
209 -p, --profile=PROFILE use PROFILE instead of the user's default profile"))
210 (display (_ "
211 -n, --dry-run show what would be done without actually doing it"))
212 (display (_ "
213 -b, --bootstrap use the bootstrap Guile to build the profile"))
214 (newline)
215 (display (_ "
216 -h, --help display this help and exit"))
217 (display (_ "
218 -V, --version display version information and exit"))
219 (newline)
220 (format #t (_ "
221Report bugs to: ~a.~%") "@PACKAGE_BUGREPORT@"))
222
223(define %options
224 ;; Specification of the command-line options.
225 (list (option '(#\h "help") #f #f
226 (lambda args
227 (show-help)
228 (exit 0)))
229 (option '(#\V "version") #f #f
230 (lambda args
231 (show-version)
232 (exit 0)))
233
234 (option '(#\i "install") #t #f
235 (lambda (opt name arg result)
236 (alist-cons 'install arg result)))
237 (option '(#\r "remove") #t #f
238 (lambda (opt name arg result)
239 (alist-cons 'remove arg result)))
240 (option '(#\p "profile") #t #f
241 (lambda (opt name arg result)
242 (alist-cons 'profile arg
243 (alist-delete 'profile result))))
244 (option '(#\n "dry-run") #f #f
245 (lambda (opt name arg result)
246 (alist-cons 'dry-run? #t result)))
247 (option '(#\b "bootstrap") #f #f
248 (lambda (opt name arg result)
249 (alist-cons 'bootstrap? #t result)))))
250
251
252;;;
253;;; Entry point.
254;;;
255
256(define (guix-package . args)
257 (define (parse-options)
258 ;; Return the alist of option values.
259 (args-fold args %options
260 (lambda (opt name arg result)
261 (leave (_ "~A: unrecognized option~%") name))
262 (lambda (arg result)
263 (alist-cons 'argument arg result))
264 %default-options))
265
266 (define (show-what-to-build drv dry-run?)
267 ;; Show what will/would be built in realizing the derivations listed
268 ;; in DRV.
269 (let* ((req (append-map (lambda (drv-path)
270 (let ((d (call-with-input-file drv-path
271 read-derivation)))
272 (derivation-prerequisites-to-build %store d)))
273 drv))
274 (req* (delete-duplicates
275 (append (remove (compose (cut valid-path? %store <>)
276 derivation-path->output-path)
277 drv)
278 (map derivation-input-path req)))))
279 (if dry-run?
280 (format (current-error-port)
281 (N_ "~:[the following derivation would be built:~%~{ ~a~%~}~;~]"
282 "~:[the following derivations would be built:~%~{ ~a~%~}~;~]"
283 (length req*))
284 (null? req*) req*)
285 (format (current-error-port)
286 (N_ "~:[the following derivation will be built:~%~{ ~a~%~}~;~]"
287 "~:[the following derivations will be built:~%~{ ~a~%~}~;~]"
288 (length req*))
289 (null? req*) req*))))
290
291 (define (find-package name)
292 ;; Find the package NAME; NAME may contain a version number and a
293 ;; sub-derivation name.
294 (define request name)
295 (define versioned-rx
296 (make-regexp "^(.*)-([0-9][^-]*)$"))
297
298 (let*-values (((name sub-drv)
299 (match (string-rindex name #\:)
300 (#f (values name "out"))
301 (colon (values (substring name (+ 1 colon))
302 (substring name colon)))))
303 ((name version)
304 (match (regexp-exec versioned-rx name)
305 (#f (values name #f))
306 (m (values (match:substring m 1)
307 (match:substring m 2))))))
308 (match (find-packages-by-name name version)
309 ((p)
310 (list name version sub-drv p))
311 ((p _ ...)
312 (format (current-error-port)
313 (_ "warning: ambiguous package specification `~a'~%")
314 request)
315 (format (current-error-port)
316 (_ "warning: choosing ~s~%")
317 p)
318 (list name version sub-drv p))
319 (()
320 (leave (_ "~a: package not found~%") request)))))
321
322 (setlocale LC_ALL "")
323 (textdomain "guix")
324 (setvbuf (current-output-port) _IOLBF)
325 (setvbuf (current-error-port) _IOLBF)
326
327 (let ((opts (parse-options)))
328 (parameterize ((%guile-for-build
329 (package-derivation %store
330 (if (assoc-ref opts 'bootstrap?)
331 (@@ (distro packages base)
332 %bootstrap-guile)
333 guile-2.0))))
334 (let* ((dry-run? (assoc-ref opts 'dry-run?))
335 (profile (assoc-ref opts 'profile))
336 (install (filter-map (match-lambda
337 (('install . (? store-path?))
338 #f)
339 (('install . package)
340 (find-package package))
341 (_ #f))
342 opts))
343 (drv (filter-map (match-lambda
344 ((name version sub-drv (? package? package))
345 (package-derivation %store package))
346 (_ #f))
347 install))
348 (install* (append
349 (filter-map (match-lambda
350 (('install . (? store-path? path))
351 `(,(store-path-package-name path)
352 #f #f ,path))
353 (_ #f))
354 opts)
355 (map (lambda (tuple drv)
356 (match tuple
357 ((name version sub-drv _)
358 (let ((output-path
359 (derivation-path->output-path drv
360 sub-drv)))
361 `(,name ,version ,sub-drv ,output-path)))))
362 install drv)))
363 (remove (filter-map (match-lambda
364 (('remove . package)
365 package)
366 (_ #f))
367 opts))
368 (packages (append install*
369 (fold alist-delete
370 (manifest-packages (profile-manifest profile))
371 remove))))
372
373 (show-what-to-build drv dry-run?)
374
375 (or dry-run?
376 (and (build-derivations %store drv)
377 (let* ((prof-drv (profile-derivation %store packages))
378 (prof (derivation-path->output-path prof-drv))
379 (number (latest-profile-number profile))
380 (name (format #f "~a/~a-~a-link"
381 (dirname profile)
382 (basename profile) (+ 1 number))))
383 (and (build-derivations %store (list prof-drv))
384 (begin
385 (symlink prof name)
386 (when (file-exists? profile)
387 (delete-file profile))
388 (symlink name profile))))))))))
389
390;; Local Variables:
391;; eval: (put 'guard 'scheme-indent-function 1)
392;; End:
diff --git a/tests/guix-package.sh b/tests/guix-package.sh
new file mode 100644
index 00000000000..9c50f4dfebb
--- /dev/null
+++ b/tests/guix-package.sh
@@ -0,0 +1,43 @@
1# Guix --- Nix package management from Guile. -*- coding: utf-8 -*-
2# Copyright (C) 2012 Ludovic Courtès <ludo@gnu.org>
3#
4# This file is part of Guix.
5#
6# Guix is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 3 of the License, or (at
9# your option) any later version.
10#
11# Guix is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with Guix. If not, see <http://www.gnu.org/licenses/>.
18
19#
20# Test the `guix-package' command-line utility.
21#
22
23guix-package --version
24
25profile="t-profile-$$"
26rm -f "$profile"
27
28guix-package -b -p "$profile" \
29 -i `guix-build -e '(@@ (distro packages base) %bootstrap-guile)'`
30test -L "$profile" && test -L "$profile-1-link"
31test -f "$profile/bin/guile"
32
33
34guix-package -b -p "$profile" \
35 -i `guix-build -e '(@@ (distro packages base) gnu-make-boot0)'`
36test -L "$profile-2-link"
37test -f "$profile/bin/make" && test -f "$profile/bin/guile"
38
39guix-package -b -p "$profile" -r "guile-bootstrap-2.0"
40test -L "$profile-3-link"
41test -f "$profile/bin/make" && ! test -f "$profile/bin/guile"
42
43rm "$profile" "$profile-"[0-9]*