summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--doc/guix.texi33
-rw-r--r--guix/channels.scm122
-rw-r--r--tests/channels.scm139
4 files changed, 279 insertions, 16 deletions
diff --git a/Makefile.am b/Makefile.am
index a7a67e81cfc..4a190c40957 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -329,6 +329,7 @@ SCM_TESTS = \
329 tests/base16.scm \ 329 tests/base16.scm \
330 tests/base32.scm \ 330 tests/base32.scm \
331 tests/base64.scm \ 331 tests/base64.scm \
332 tests/channels.scm \
332 tests/cpan.scm \ 333 tests/cpan.scm \
333 tests/cpio.scm \ 334 tests/cpio.scm \
334 tests/crate.scm \ 335 tests/crate.scm \
diff --git a/doc/guix.texi b/doc/guix.texi
index 4ef2601579a..20b5013fd9f 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -3037,6 +3037,39 @@ the new and upgraded packages that are listed, some like @code{my-gimp} and
3037@code{my-emacs-with-cool-features} might come from 3037@code{my-emacs-with-cool-features} might come from
3038@code{my-personal-packages}, while others come from the Guix default channel. 3038@code{my-personal-packages}, while others come from the Guix default channel.
3039 3039
3040@cindex dependencies, channels
3041@cindex meta-data, channels
3042@subsection Declaring Channel Dependencies
3043
3044Channel authors may decide to augment a package collection provided by other
3045channels. They can declare their channel to be dependent on other channels in
3046a meta-data file @file{.guix-channel}, which is to be placed in the root of
3047the channel repository.
3048
3049The meta-data file should contain a simple S-expression like this:
3050
3051@lisp
3052(channel
3053 (version 0)
3054 (dependencies
3055 (channel
3056 (name 'some-collection)
3057 (url "https://example.org/first-collection.git"))
3058 (channel
3059 (name 'some-other-collection)
3060 (url "https://example.org/second-collection.git")
3061 (branch "testing"))))
3062@end lisp
3063
3064In the above example this channel is declared to depend on two other channels,
3065which will both be fetched automatically. The modules provided by the channel
3066will be compiled in an environment where the modules of all these declared
3067channels are available.
3068
3069For the sake of reliability and maintainability, you should avoid dependencies
3070on channels that you don't control, and you should aim to keep the number of
3071dependencies to a minimum.
3072
3040@subsection Replicating Guix 3073@subsection Replicating Guix
3041 3074
3042@cindex pinning, channels 3075@cindex pinning, channels
diff --git a/guix/channels.scm b/guix/channels.scm
index e57da68149a..75503bb0aeb 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -1,5 +1,6 @@
1;;; GNU Guix --- Functional package management for GNU 1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> 2;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
3;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
3;;; 4;;;
4;;; This file is part of GNU Guix. 5;;; This file is part of GNU Guix.
5;;; 6;;;
@@ -27,6 +28,7 @@
27 #:use-module (guix store) 28 #:use-module (guix store)
28 #:use-module (guix i18n) 29 #:use-module (guix i18n)
29 #:use-module (srfi srfi-1) 30 #:use-module (srfi srfi-1)
31 #:use-module (srfi srfi-2)
30 #:use-module (srfi srfi-9) 32 #:use-module (srfi srfi-9)
31 #:use-module (srfi srfi-11) 33 #:use-module (srfi srfi-11)
32 #:autoload (guix self) (whole-package) 34 #:autoload (guix self) (whole-package)
@@ -73,7 +75,6 @@
73 (commit channel-commit (default #f)) 75 (commit channel-commit (default #f))
74 (location channel-location 76 (location channel-location
75 (default (current-source-location)) (innate))) 77 (default (current-source-location)) (innate)))
76;; TODO: Add a way to express dependencies among channels.
77 78
78(define %default-channels 79(define %default-channels
79 ;; Default list of channels. 80 ;; Default list of channels.
@@ -93,6 +94,12 @@
93 (commit channel-instance-commit) 94 (commit channel-instance-commit)
94 (checkout channel-instance-checkout)) 95 (checkout channel-instance-checkout))
95 96
97(define-record-type <channel-metadata>
98 (channel-metadata version dependencies)
99 channel-metadata?
100 (version channel-metadata-version)
101 (dependencies channel-metadata-dependencies))
102
96(define (channel-reference channel) 103(define (channel-reference channel)
97 "Return the \"reference\" for CHANNEL, an sexp suitable for 104 "Return the \"reference\" for CHANNEL, an sexp suitable for
98'latest-repository-commit'." 105'latest-repository-commit'."
@@ -100,20 +107,90 @@
100 (#f `(branch . ,(channel-branch channel))) 107 (#f `(branch . ,(channel-branch channel)))
101 (commit `(commit . ,(channel-commit channel))))) 108 (commit `(commit . ,(channel-commit channel)))))
102 109
103(define (latest-channel-instances store channels) 110(define (read-channel-metadata instance)
111 "Return a channel-metadata record read from the channel INSTANCE's
112description file, or return #F if the channel instance does not include the
113file."
114 (let* ((source (channel-instance-checkout instance))
115 (meta-file (string-append source "/.guix-channel")))
116 (and (file-exists? meta-file)
117 (and-let* ((raw (call-with-input-file meta-file read))
118 (version (and=> (assoc-ref raw 'version) first))
119 (dependencies (or (assoc-ref raw 'dependencies) '())))
120 (channel-metadata
121 version
122 (map (lambda (item)
123 (let ((get (lambda* (key #:optional default)
124 (or (and=> (assoc-ref item key) first) default))))
125 (and-let* ((name (get 'name))
126 (url (get 'url))
127 (branch (get 'branch "master")))
128 (channel
129 (name name)
130 (branch branch)
131 (url url)
132 (commit (get 'commit))))))
133 dependencies))))))
134
135(define (channel-instance-dependencies instance)
136 "Return the list of channels that are declared as dependencies for the given
137channel INSTANCE."
138 (match (read-channel-metadata instance)
139 (#f '())
140 (($ <channel-metadata> version dependencies)
141 dependencies)))
142
143(define* (latest-channel-instances store channels #:optional (previous-channels '()))
104 "Return a list of channel instances corresponding to the latest checkouts of 144 "Return a list of channel instances corresponding to the latest checkouts of
105CHANNELS." 145CHANNELS and the channels on which they depend. PREVIOUS-CHANNELS is a list
106 (map (lambda (channel) 146of previously processed channels."
107 (format (current-error-port) 147 ;; Only process channels that are unique, or that are more specific than a
108 (G_ "Updating channel '~a' from Git repository at '~a'...~%") 148 ;; previous channel specification.
109 (channel-name channel) 149 (define (ignore? channel others)
110 (channel-url channel)) 150 (member channel others
111 (let-values (((checkout commit) 151 (lambda (a b)
112 (latest-repository-commit store (channel-url channel) 152 (and (eq? (channel-name a) (channel-name b))
113 #:ref (channel-reference 153 (or (channel-commit b)
114 channel)))) 154 (not (or (channel-commit a)
115 (channel-instance channel commit checkout))) 155 (channel-commit b))))))))
116 channels)) 156 ;; Accumulate a list of instances. A list of processed channels is also
157 ;; accumulated to decide on duplicate channel specifications.
158 (match (fold (lambda (channel acc)
159 (match acc
160 ((#:channels previous-channels #:instances instances)
161 (if (ignore? channel previous-channels)
162 acc
163 (begin
164 (format (current-error-port)
165 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
166 (channel-name channel)
167 (channel-url channel))
168 (let-values (((checkout commit)
169 (latest-repository-commit store (channel-url channel)
170 #:ref (channel-reference
171 channel))))
172 (let ((instance (channel-instance channel commit checkout)))
173 (let-values (((new-instances new-channels)
174 (latest-channel-instances
175 store
176 (channel-instance-dependencies instance)
177 previous-channels)))
178 `(#:channels
179 ,(append (cons channel new-channels)
180 previous-channels)
181 #:instances
182 ,(append (cons instance new-instances)
183 instances))))))))))
184 `(#:channels ,previous-channels #:instances ())
185 channels)
186 ((#:channels channels #:instances instances)
187 (let ((instance-name (compose channel-name channel-instance-channel)))
188 ;; Remove all earlier channel specifications if they are followed by a
189 ;; more specific one.
190 (values (delete-duplicates instances
191 (lambda (a b)
192 (eq? (instance-name a) (instance-name b))))
193 channels)))))
117 194
118(define* (checkout->channel-instance checkout 195(define* (checkout->channel-instance checkout
119 #:key commit 196 #:key commit
@@ -235,8 +312,21 @@ INSTANCES."
235 (lambda (instance) 312 (lambda (instance)
236 (if (eq? instance core-instance) 313 (if (eq? instance core-instance)
237 (return core) 314 (return core)
238 (build-channel-instance instance 315 (match (channel-instance-dependencies instance)
239 (cons core dependencies)))) 316 (()
317 (build-channel-instance instance
318 (cons core dependencies)))
319 (channels
320 (mlet %store-monad ((dependencies-derivation
321 (latest-channel-derivation
322 ;; %default-channels is used here to
323 ;; ensure that the core channel is
324 ;; available for channels declared as
325 ;; dependencies.
326 (append channels %default-channels))))
327 (build-channel-instance instance
328 (cons dependencies-derivation
329 (cons core dependencies))))))))
240 instances))) 330 instances)))
241 331
242(define (whole-package-for-legacy name modules) 332(define (whole-package-for-legacy name modules)
diff --git a/tests/channels.scm b/tests/channels.scm
new file mode 100644
index 00000000000..f3fc383ac31
--- /dev/null
+++ b/tests/channels.scm
@@ -0,0 +1,139 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU 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;;; GNU 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (test-channels)
20 #:use-module (guix channels)
21 #:use-module ((guix build syscalls) #:select (mkdtemp!))
22 #:use-module (guix tests)
23 #:use-module (srfi srfi-1)
24 #:use-module (srfi srfi-64)
25 #:use-module (ice-9 match))
26
27(test-begin "channels")
28
29(define* (make-instance #:key
30 (name 'fake)
31 (commit "cafebabe")
32 (spec #f))
33 (define instance-dir (mkdtemp! "/tmp/checkout.XXXXXX"))
34 (and spec
35 (with-output-to-file (string-append instance-dir "/.guix-channel")
36 (lambda _ (format #t "~a" spec))))
37 ((@@ (guix channels) channel-instance)
38 name commit instance-dir))
39
40(define instance--boring (make-instance))
41(define instance--no-deps
42 (make-instance #:spec
43 '(channel
44 (version 0)
45 (dependencies
46 (channel
47 (name test-channel)
48 (url "https://example.com/test-channel"))))))
49(define instance--simple
50 (make-instance #:spec
51 '(channel
52 (version 0)
53 (dependencies
54 (channel
55 (name test-channel)
56 (url "https://example.com/test-channel"))))))
57(define instance--with-dupes
58 (make-instance #:spec
59 '(channel
60 (version 0)
61 (dependencies
62 (channel
63 (name test-channel)
64 (url "https://example.com/test-channel"))
65 (channel
66 (name test-channel)
67 (url "https://example.com/test-channel")
68 (commit "abc1234"))
69 (channel
70 (name test-channel)
71 (url "https://example.com/test-channel-elsewhere"))))))
72
73(define read-channel-metadata
74 (@@ (guix channels) read-channel-metadata))
75
76
77(test-equal "read-channel-metadata returns #f if .guix-channel does not exist"
78 #f
79 (read-channel-metadata instance--boring))
80
81(test-assert "read-channel-metadata returns <channel-metadata>"
82 (every (@@ (guix channels) channel-metadata?)
83 (map read-channel-metadata
84 (list instance--no-deps
85 instance--simple
86 instance--with-dupes))))
87
88(test-assert "read-channel-metadata dependencies are channels"
89 (let ((deps ((@@ (guix channels) channel-metadata-dependencies)
90 (read-channel-metadata instance--simple))))
91 (match deps
92 (((? channel? dep)) #t)
93 (_ #f))))
94
95(test-assert "latest-channel-instances includes channel dependencies"
96 (let* ((channel (channel
97 (name 'test)
98 (url "test")))
99 (test-dir (channel-instance-checkout instance--simple)))
100 (mock ((guix git) latest-repository-commit
101 (lambda* (store url #:key ref)
102 (match url
103 ("test" (values test-dir 'whatever))
104 (_ (values "/not-important" 'not-important)))))
105 (let ((instances (latest-channel-instances #f (list channel))))
106 (and (eq? 2 (length instances))
107 (lset= eq?
108 '(test test-channel)
109 (map (compose channel-name channel-instance-channel)
110 instances)))))))
111
112(test-assert "latest-channel-instances excludes duplicate channel dependencies"
113 (let* ((channel (channel
114 (name 'test)
115 (url "test")))
116 (test-dir (channel-instance-checkout instance--with-dupes)))
117 (mock ((guix git) latest-repository-commit
118 (lambda* (store url #:key ref)
119 (match url
120 ("test" (values test-dir 'whatever))
121 (_ (values "/not-important" 'not-important)))))
122 (let ((instances (latest-channel-instances #f (list channel))))
123 (and (eq? 2 (length instances))
124 (lset= eq?
125 '(test test-channel)
126 (map (compose channel-name channel-instance-channel)
127 instances))
128 ;; only the most specific channel dependency should remain,
129 ;; i.e. the one with a specified commit.
130 (find (lambda (instance)
131 (and (eq? (channel-name
132 (channel-instance-channel instance))
133 'test-channel)
134 (eq? (channel-commit
135 (channel-instance-channel instance))
136 'abc1234)))
137 instances))))))
138
139(test-end "channels")