unofficial mirror of guix-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Channel dependencies
@ 2018-10-13  6:54 Ricardo Wurmus
  2018-10-13 11:09 ` Pierre Neidhardt
                   ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: Ricardo Wurmus @ 2018-10-13  6:54 UTC (permalink / raw)
  To: guix-devel

[-- Attachment #1: Type: text/plain, Size: 680 bytes --]

Hi,

the attached patch allows channel authors to declare other channels as
dependencies of their own channel.  In addition to explicitly requested
channels, “guix pull” will now also download and build channels that
have been declared as dependencies in the file ‘.guix-channel’ in the
repository root.

An example of a simple .guix-channel file is this:

--8<---------------cut here---------------start------------->8---
(channel
 (version 0)
 (dependencies
  (channel
   (name guix-bimsb)
   (url "https://github.com/BIMSBbioinfo/guix-bimsb"))))
--8<---------------cut here---------------end--------------->8---

What do you think?

--
Ricardo


[-- Attachment #2: 0001-guix-Add-support-for-channel-dependencies.patch --]
[-- Type: text/x-patch, Size: 8051 bytes --]

From 1783c17582906df970c7e68e89d761619a35caeb Mon Sep 17 00:00:00 2001
From: Ricardo Wurmus <rekado@elephly.net>
Date: Sat, 13 Oct 2018 08:39:23 +0200
Subject: [PATCH] guix: Add support for channel dependencies.

* guix/channels.scm (%channel-meta-file): New variable.
(channel-meta, channel-instance-dependencies): New procedures.
(latest-channel-instances): Include channel dependencies.
(channel-instance-derivations): Build derivation for additional channels and
add it as dependency to the channel instance derivation.
* doc/guix.texi (Channels): Add subsection "Declaring Channel Dependencies".
---
 doc/guix.texi     | 33 ++++++++++++++++++++
 guix/channels.scm | 79 ++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 97 insertions(+), 15 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 5ae80917a..a4d5477f6 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -3020,6 +3020,39 @@ the new and upgraded packages that are listed, some like @code{my-gimp} and
 @code{my-emacs-with-cool-features} might come from
 @code{my-personal-packages}, while others come from the Guix default channel.
 
+@cindex dependencies, channels
+@cindex meta-data, channels
+@subsection Declaring Channel Dependencies
+
+Channel authors may decide to augment a package collection provided by other
+channels.  They can declare their channel to be dependent on other channels in
+a meta-data file @file{.guix-channel}, which is to be placed in the root of
+the channel repository.
+
+The meta-data file should contain a simple S-expression like this:
+
+@lisp
+(channel
+ (version 0)
+ (dependencies
+  (channel
+   (name 'some-collection)
+   (url "https://example.org/first-collection.git"))
+  (channel
+   (name 'some-other-collection)
+   (url "https://example.org/second-collection.git")
+   (branch "testing"))))
+@end lisp
+
+In the above example this channel is declared to depend on two other channels,
+which will both be fetched automatically.  The modules provided by the channel
+will be compiled in an environment where the modules of all these declared
+channels are available.
+
+For the sake of reliability and maintainability, you should avoid dependencies
+on channels that you don't control, and you should aim to keep the number of
+dependencies to a minimum.
+
 @subsection Replicating Guix
 
 @cindex pinning, channels
diff --git a/guix/channels.scm b/guix/channels.scm
index 82389eb58..fbe1b62bb 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -72,7 +73,6 @@
   (commit    channel-commit (default #f))
   (location  channel-location
              (default (current-source-location)) (innate)))
-;; TODO: Add a way to express dependencies among channels.
 
 (define %default-channels
   ;; Default list of channels.
@@ -81,6 +81,10 @@
          (branch "master")
          (url "https://git.savannah.gnu.org/git/guix.git"))))
 
+(define %channel-meta-file
+  ;; The file containing information about the channel.
+  ".guix-channel")
+
 (define (guix-channel? channel)
   "Return true if CHANNEL is the 'guix' channel."
   (eq? 'guix (channel-name channel)))
@@ -99,20 +103,52 @@
     (#f      `(branch . ,(channel-branch channel)))
     (commit  `(commit . ,(channel-commit channel)))))
 
+(define (channel-meta instance)
+  "Return an S-expression read from the channel INSTANCE's description file,
+or return #F if the channel instance does not include the file."
+  (let* ((source (channel-instance-checkout instance))
+         (meta-file (string-append source "/" %channel-meta-file)))
+    (and (file-exists? meta-file)
+         (call-with-input-file meta-file read))))
+
+(define (channel-instance-dependencies instance)
+  "Return the list of channels that are declared as dependencies for the given
+channel INSTANCE."
+  (or (and=> (assoc-ref (channel-meta instance) 'dependencies)
+             (lambda (dependencies)
+               (map (lambda (item)
+                      (let ((get (lambda* (key #:optional default)
+                                   (or (and=> (assoc-ref item key) car) default))))
+                        (let ((name (get 'name))
+                              (url (get 'url))
+                              (branch (get 'branch "master"))
+                              (commit (get 'commit)))
+                          (and name url branch
+                               (channel
+                                (name name)
+                                (branch branch)
+                                (url url)
+                                (commit commit))))))
+                    dependencies)))
+      '()))
+
 (define (latest-channel-instances store channels)
   "Return a list of channel instances corresponding to the latest checkouts of
-CHANNELS."
-  (map (lambda (channel)
-         (format (current-error-port)
-                 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
-                 (channel-name channel)
-                 (channel-url channel))
-         (let-values (((checkout commit)
-                       (latest-repository-commit store (channel-url channel)
-                                                 #:ref (channel-reference
-                                                        channel))))
-           (channel-instance channel commit checkout)))
-       channels))
+CHANNELS and the channels on which they depend."
+  (append-map (lambda (channel)
+                (format (current-error-port)
+                        (G_ "Updating channel '~a' from Git repository at '~a'...~%")
+                        (channel-name channel)
+                        (channel-url channel))
+                (let-values (((checkout commit)
+                              (latest-repository-commit store (channel-url channel)
+                                                        #:ref (channel-reference
+                                                               channel))))
+                  (let ((instance (channel-instance channel commit checkout)))
+                    (cons instance (latest-channel-instances
+                                    store
+                                    (channel-instance-dependencies instance))))))
+              channels))
 
 (define %self-build-file
   ;; The file containing code to build Guix.  This serves the same purpose as
@@ -223,8 +259,21 @@ INSTANCES."
           (lambda (instance)
             (if (eq? instance core-instance)
                 (return core)
-                (build-channel-instance instance
-                                        (cons core dependencies))))
+                (match (channel-instance-dependencies instance)
+                  (()
+                   (build-channel-instance instance
+                                           (cons core dependencies)))
+                  (channels
+                   (mlet %store-monad ((dependencies-derivation
+                                        (latest-channel-derivation
+                                         ;; %default-channels is used here to
+                                         ;; ensure that the core channel is
+                                         ;; available for channels declared as
+                                         ;; dependencies.
+                                         (append channels %default-channels))))
+                     (build-channel-instance instance
+                                             (cons dependencies-derivation
+                                                   (cons core dependencies))))))))
           instances)))
 
 (define (whole-package-for-legacy name modules)
-- 
2.19.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-13  6:54 Channel dependencies Ricardo Wurmus
@ 2018-10-13 11:09 ` Pierre Neidhardt
  2018-10-14  2:16 ` Chris Marusich
  2018-10-15  9:41 ` Ludovic Courtès
  2 siblings, 0 replies; 14+ messages in thread
From: Pierre Neidhardt @ 2018-10-13 11:09 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 69 bytes --]

Sounds very useful!

-- 
Pierre Neidhardt
https://ambrevar.xyz/

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 487 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-13  6:54 Channel dependencies Ricardo Wurmus
  2018-10-13 11:09 ` Pierre Neidhardt
@ 2018-10-14  2:16 ` Chris Marusich
  2018-10-14  9:49   ` Ricardo Wurmus
  2018-10-15  9:41 ` Ludovic Courtès
  2 siblings, 1 reply; 14+ messages in thread
From: Chris Marusich @ 2018-10-14  2:16 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 4659 bytes --]

Hi Ricardo,

Ricardo Wurmus <ricardo.wurmus@mdc-berlin.de> writes:

> the attached patch allows channel authors to declare other channels as
> dependencies of their own channel.
>
> [...]
>
> What do you think?

It's very cool!

> +(define (channel-instance-dependencies instance)
> +  "Return the list of channels that are declared as dependencies for the given
> +channel INSTANCE."
> +  (or (and=> (assoc-ref (channel-meta instance) 'dependencies)
> +             (lambda (dependencies)
> +               (map (lambda (item)
> +                      (let ((get (lambda* (key #:optional default)
> +                                   (or (and=> (assoc-ref item key) car) default))))

Nitpick: to improve readability, is it possible to use pattern matching
here instead of using procedures like "car"?

>  (define (latest-channel-instances store channels)
>    "Return a list of channel instances corresponding to the latest checkouts of
> -CHANNELS."
> -  (map (lambda (channel)
> -         (format (current-error-port)
> -                 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
> -                 (channel-name channel)
> -                 (channel-url channel))
> -         (let-values (((checkout commit)
> -                       (latest-repository-commit store (channel-url channel)
> -                                                 #:ref (channel-reference
> -                                                        channel))))
> -           (channel-instance channel commit checkout)))
> -       channels))
> +CHANNELS and the channels on which they depend."
> +  (append-map (lambda (channel)
> +                (format (current-error-port)
> +                        (G_ "Updating channel '~a' from Git repository at '~a'...~%")
> +                        (channel-name channel)
> +                        (channel-url channel))
> +                (let-values (((checkout commit)
> +                              (latest-repository-commit store (channel-url channel)
> +                                                        #:ref (channel-reference
> +                                                               channel))))
> +                  (let ((instance (channel-instance channel commit checkout)))
> +                    (cons instance (latest-channel-instances
> +                                    store
> +                                    (channel-instance-dependencies instance))))))
> +              channels))

What happens if the dependency list contains duplicate channels?  This
might happen if two unrelated channels mutually depend upon a third
channel.

What happens if the dependency list contains two channels that differ
only in their branch (or commit), and are the same in every other way?
This might happen if two unrelated channels mutually depend upon two
different versions of the same third channel.

>  (define %self-build-file ;; The file containing code to build Guix.
> This serves the same purpose as @@ -223,8 +259,21 @@ INSTANCES."
> (lambda (instance) (if (eq? instance core-instance) (return core)
> -                (build-channel-instance instance
> -                                        (cons core dependencies))))
> +                (match (channel-instance-dependencies instance)
> +                  (()
> +                   (build-channel-instance instance
> +                                           (cons core dependencies)))
> +                  (channels
> +                   (mlet %store-monad ((dependencies-derivation
> +                                        (latest-channel-derivation
> +                                         ;; %default-channels is used here to
> +                                         ;; ensure that the core channel is
> +                                         ;; available for channels declared as
> +                                         ;; dependencies.
> +                                         (append channels %default-channels))))
> +                     (build-channel-instance instance
> +                                             (cons dependencies-derivation
> +                                                   (cons core dependencies))))))))

I think we should clarify in the manual the fact that channel authors do
not need to specify the core "guix" channel in their dependencies, since
it is an implicit dependency of all non-core channels.

Thank you for working on this!  I'm catching up on developments over the
last few months, and it's really exciting to see channels become a
reality.

-- 
Chris

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-14  2:16 ` Chris Marusich
@ 2018-10-14  9:49   ` Ricardo Wurmus
  2018-10-15  9:29     ` Ludovic Courtès
  0 siblings, 1 reply; 14+ messages in thread
From: Ricardo Wurmus @ 2018-10-14  9:49 UTC (permalink / raw)
  To: Chris Marusich; +Cc: guix-devel, Ricardo Wurmus


Hi Chris,

>>  (define (latest-channel-instances store channels)
>>    "Return a list of channel instances corresponding to the latest checkouts of
>> -CHANNELS."
>> -  (map (lambda (channel)
>> -         (format (current-error-port)
>> -                 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
>> -                 (channel-name channel)
>> -                 (channel-url channel))
>> -         (let-values (((checkout commit)
>> -                       (latest-repository-commit store (channel-url channel)
>> -                                                 #:ref (channel-reference
>> -                                                        channel))))
>> -           (channel-instance channel commit checkout)))
>> -       channels))
>> +CHANNELS and the channels on which they depend."
>> +  (append-map (lambda (channel)
>> +                (format (current-error-port)
>> +                        (G_ "Updating channel '~a' from Git repository at '~a'...~%")
>> +                        (channel-name channel)
>> +                        (channel-url channel))
>> +                (let-values (((checkout commit)
>> +                              (latest-repository-commit store (channel-url channel)
>> +                                                        #:ref (channel-reference
>> +                                                               channel))))
>> +                  (let ((instance (channel-instance channel commit checkout)))
>> +                    (cons instance (latest-channel-instances
>> +                                    store
>> +                                    (channel-instance-dependencies instance))))))
>> +              channels))
>
> What happens if the dependency list contains duplicate channels?  This
> might happen if two unrelated channels mutually depend upon a third
> channel.
>
> What happens if the dependency list contains two channels that differ
> only in their branch (or commit), and are the same in every other way?
> This might happen if two unrelated channels mutually depend upon two
> different versions of the same third channel.

I was going to write an email about this just now.  This is indeed a
problem with this naive implementation.

For reproducibility a user may want to pin all channels to a certain
commit, but channels that are dependencies are exempt from that
mechanism as they are added to the list of channels to be instantiated
during the run of “guix pull”.

It should be possible to pin *all* channels, even if they are
dependencies of other channels.  One way is to treat all channels and
their dependencies as a unique set where duplicates are replaced with
the most specific description (e.g. those specifying a commit).  This
way users could fully specify dependencies, which would then be used
instead of the channel’s declared dependency.

This needs some more thought.

--
Ricardo

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-14  9:49   ` Ricardo Wurmus
@ 2018-10-15  9:29     ` Ludovic Courtès
  2018-10-15  9:35       ` Ricardo Wurmus
  0 siblings, 1 reply; 14+ messages in thread
From: Ludovic Courtès @ 2018-10-15  9:29 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel, Ricardo Wurmus

Hello,

Ricardo Wurmus <rekado@elephly.net> skribis:

>>>  (define (latest-channel-instances store channels)
>>>    "Return a list of channel instances corresponding to the latest checkouts of
>>> -CHANNELS."
>>> -  (map (lambda (channel)
>>> -         (format (current-error-port)
>>> -                 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
>>> -                 (channel-name channel)
>>> -                 (channel-url channel))
>>> -         (let-values (((checkout commit)
>>> -                       (latest-repository-commit store (channel-url channel)
>>> -                                                 #:ref (channel-reference
>>> -                                                        channel))))
>>> -           (channel-instance channel commit checkout)))
>>> -       channels))
>>> +CHANNELS and the channels on which they depend."
>>> +  (append-map (lambda (channel)
>>> +                (format (current-error-port)
>>> +                        (G_ "Updating channel '~a' from Git repository at '~a'...~%")
>>> +                        (channel-name channel)
>>> +                        (channel-url channel))
>>> +                (let-values (((checkout commit)
>>> +                              (latest-repository-commit store (channel-url channel)
>>> +                                                        #:ref (channel-reference
>>> +                                                               channel))))
>>> +                  (let ((instance (channel-instance channel commit checkout)))
>>> +                    (cons instance (latest-channel-instances
>>> +                                    store
>>> +                                    (channel-instance-dependencies instance))))))
>>> +              channels))
>>
>> What happens if the dependency list contains duplicate channels?  This
>> might happen if two unrelated channels mutually depend upon a third
>> channel.
>>
>> What happens if the dependency list contains two channels that differ
>> only in their branch (or commit), and are the same in every other way?
>> This might happen if two unrelated channels mutually depend upon two
>> different versions of the same third channel.

We should error out in this case because at run time, there can be only
one Guile module with a given name.  Thus, conflicting dependencies
cannot be honored.  (Not like Node.js, for better or worse.  ;-))

> I was going to write an email about this just now.  This is indeed a
> problem with this naive implementation.
>
> For reproducibility a user may want to pin all channels to a certain
> commit, but channels that are dependencies are exempt from that
> mechanism as they are added to the list of channels to be instantiated
> during the run of “guix pull”.
>
> It should be possible to pin *all* channels, even if they are
> dependencies of other channels.  One way is to treat all channels and
> their dependencies as a unique set where duplicates are replaced with
> the most specific description (e.g. those specifying a commit).  This
> way users could fully specify dependencies, which would then be used
> instead of the channel’s declared dependency.

Of course it’s easier to pin all channels when they are directly
expressed in ~/.config/guix/channels.scm.

Say, I need channel A, which depends on B.

What we could do is that if B is already in ~/.config/guix/channels.scm,
then we take this particular B; if it’s not there, we take the latest
version.

In terms of the dependency code, it means that dependencies found in
.guix-channels file are added if and only if they are not already
present in the user-provided channel list.

Does that make sense?

Thanks!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-15  9:29     ` Ludovic Courtès
@ 2018-10-15  9:35       ` Ricardo Wurmus
  2018-10-15 11:49         ` Ludovic Courtès
  0 siblings, 1 reply; 14+ messages in thread
From: Ricardo Wurmus @ 2018-10-15  9:35 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel


Ludovic Courtès <ludo@gnu.org> writes:

> Of course it’s easier to pin all channels when they are directly
> expressed in ~/.config/guix/channels.scm.
>
> Say, I need channel A, which depends on B.
>
> What we could do is that if B is already in ~/.config/guix/channels.scm,
> then we take this particular B; if it’s not there, we take the latest
> version.

Yes, this sounds good to me.  I’ll implement this soon and hope that we
can merge it before Friday when I’ll present this to cluster users at
the MDC :)

--
Ricardo

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-13  6:54 Channel dependencies Ricardo Wurmus
  2018-10-13 11:09 ` Pierre Neidhardt
  2018-10-14  2:16 ` Chris Marusich
@ 2018-10-15  9:41 ` Ludovic Courtès
  2018-10-18 20:24   ` Ricardo Wurmus
  2 siblings, 1 reply; 14+ messages in thread
From: Ludovic Courtès @ 2018-10-15  9:41 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

Hello!

Ricardo Wurmus <ricardo.wurmus@mdc-berlin.de> skribis:

> the attached patch allows channel authors to declare other channels as
> dependencies of their own channel.  In addition to explicitly requested
> channels, “guix pull” will now also download and build channels that
> have been declared as dependencies in the file ‘.guix-channel’ in the
> repository root.
>
> An example of a simple .guix-channel file is this:
>
> (channel
>  (version 0)
>  (dependencies
>   (channel
>    (name guix-bimsb)
>    (url "https://github.com/BIMSBbioinfo/guix-bimsb"))))
>
> What do you think?

I think that’s great.  :-)

> From 1783c17582906df970c7e68e89d761619a35caeb Mon Sep 17 00:00:00 2001
> From: Ricardo Wurmus <rekado@elephly.net>
> Date: Sat, 13 Oct 2018 08:39:23 +0200
> Subject: [PATCH] guix: Add support for channel dependencies.
>
> * guix/channels.scm (%channel-meta-file): New variable.
> (channel-meta, channel-instance-dependencies): New procedures.
> (latest-channel-instances): Include channel dependencies.
> (channel-instance-derivations): Build derivation for additional channels and
> add it as dependency to the channel instance derivation.
> * doc/guix.texi (Channels): Add subsection "Declaring Channel Dependencies".

[...]

> +(define (channel-meta instance)
> +  "Return an S-expression read from the channel INSTANCE's description file,
> +or return #F if the channel instance does not include the file."
> +  (let* ((source (channel-instance-checkout instance))
> +         (meta-file (string-append source "/" %channel-meta-file)))
> +    (and (file-exists? meta-file)
> +         (call-with-input-file meta-file read))))

As a general pattern, I’d suggest declaring <channel-metadata> record
type along with a ‘read-channel-metadata’ procedure that takes care of
“parsing” and metadata version handling.  That way parsing code is in
just one place and the rest of the code can happily deal with
well-formed records.

> +(define (channel-instance-dependencies instance)
> +  "Return the list of channels that are declared as dependencies for the given
> +channel INSTANCE."
> +  (or (and=> (assoc-ref (channel-meta instance) 'dependencies)
> +             (lambda (dependencies)
> +               (map (lambda (item)
> +                      (let ((get (lambda* (key #:optional default)
> +                                   (or (and=> (assoc-ref item key) car) default))))
> +                        (let ((name (get 'name))
> +                              (url (get 'url))
> +                              (branch (get 'branch "master"))
> +                              (commit (get 'commit)))
> +                          (and name url branch
> +                               (channel
> +                                (name name)
> +                                (branch branch)
> +                                (url url)
> +                                (commit commit))))))
> +                    dependencies)))
> +      '()))

I’d recommend ‘match’ for the outer sexp, and then something like the
‘alist-let*’ macro from (gnu services herd) in places where you’d like
to leave field ordering unspecified.

Then I think it would make sense to add the ‘dependencies’ field to
<channel-instance> directly (and keep <channel-metadata> internal.)
Each element of the ‘dependencies’ field would be another
<channel-instance>.

Actually ‘dependencies’ could be a promise that reads channel meta-data
and looks up the channel instances for the given dependencies.
Something like that.

Thoughts?

Chris raises interesting issues.  I think it’s OK to first come up with
an implementation that has some limitations but works with the simple
use cases we have in mind.

Thanks for working on it!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-15  9:35       ` Ricardo Wurmus
@ 2018-10-15 11:49         ` Ludovic Courtès
  0 siblings, 0 replies; 14+ messages in thread
From: Ludovic Courtès @ 2018-10-15 11:49 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

Ricardo Wurmus <ricardo.wurmus@mdc-berlin.de> skribis:

> Ludovic Courtès <ludo@gnu.org> writes:
>
>> Of course it’s easier to pin all channels when they are directly
>> expressed in ~/.config/guix/channels.scm.
>>
>> Say, I need channel A, which depends on B.
>>
>> What we could do is that if B is already in ~/.config/guix/channels.scm,
>> then we take this particular B; if it’s not there, we take the latest
>> version.
>
> Yes, this sounds good to me.  I’ll implement this soon and hope that we
> can merge it before Friday when I’ll present this to cluster users at
> the MDC :)

Sounds good!  :-)

I’ll be paying attention but feel free to ping me if needed!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-15  9:41 ` Ludovic Courtès
@ 2018-10-18 20:24   ` Ricardo Wurmus
  2018-10-20 20:52     ` Chris Marusich
  2018-10-22 12:14     ` Ludovic Courtès
  0 siblings, 2 replies; 14+ messages in thread
From: Ricardo Wurmus @ 2018-10-18 20:24 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 4467 bytes --]


Hi Ludo,

thanks for your helpful comments!

>> +(define (channel-meta instance)
>> +  "Return an S-expression read from the channel INSTANCE's description file,
>> +or return #F if the channel instance does not include the file."
>> +  (let* ((source (channel-instance-checkout instance))
>> +         (meta-file (string-append source "/" %channel-meta-file)))
>> +    (and (file-exists? meta-file)
>> +         (call-with-input-file meta-file read))))
>
> As a general pattern, I’d suggest declaring <channel-metadata> record
> type along with a ‘read-channel-metadata’ procedure that takes care of
> “parsing” and metadata version handling.  That way parsing code is in
> just one place and the rest of the code can happily deal with
> well-formed records.

Yes, that’s a good idea.  I added a record <channel-metadata> and a
procedure “read-channel-metadata” that produces values of this type
given a channel instance (or #F).

>> +(define (channel-instance-dependencies instance)
>> +  "Return the list of channels that are declared as dependencies for the given
>> +channel INSTANCE."
>> +  (or (and=> (assoc-ref (channel-meta instance) 'dependencies)
>> +             (lambda (dependencies)
>> +               (map (lambda (item)
>> +                      (let ((get (lambda* (key #:optional default)
>> +                                   (or (and=> (assoc-ref item key) car) default))))
>> +                        (let ((name (get 'name))
>> +                              (url (get 'url))
>> +                              (branch (get 'branch "master"))
>> +                              (commit (get 'commit)))
>> +                          (and name url branch
>> +                               (channel
>> +                                (name name)
>> +                                (branch branch)
>> +                                (url url)
>> +                                (commit commit))))))
>> +                    dependencies)))
>> +      '()))
>
> I’d recommend ‘match’ for the outer sexp, and then something like the
> ‘alist-let*’ macro from (gnu services herd) in places where you’d like
> to leave field ordering unspecified.

I keep forgetting about alist-let* (from srfi-2, not herd), even though
it’s so useful!  “channel-instance-dependencies” now uses the
<channel-metadata> record via “read-channel-metadata” and uses match.

> Then I think it would make sense to add the ‘dependencies’ field to
> <channel-instance> directly (and keep <channel-metadata> internal.)
> Each element of the ‘dependencies’ field would be another
> <channel-instance>.
>
> Actually ‘dependencies’ could be a promise that reads channel meta-data
> and looks up the channel instances for the given dependencies.
> Something like that.

This sounds good, but I don’t know how to make it work well, because
there’s a circular relationship here if we want to keep the abstractions
pretty.  I can’t simply define the “dependencies” field of
<channel-instance> to have a default thunked procedure like this:

   (match (read-channel-metadata checkout)
     (#f '())
     (($ <channel-metadata> _ dependencies)
      dependencies))

Because record fields cannot access other record fields such as
“checkout”.  This makes the code look rather silly as we’re creating an
instance with an explicit dependencies value only to read it from that
same record in the next expression.

In light of these complications I’d prefer to have a procedure
“channel-instance-dependencies” that handles this for us, and do without
a “dependencies” field on the <channel-instance> record.

What do you think?

> Chris raises interesting issues.  I think it’s OK to first come up with
> an implementation that has some limitations but works with the simple
> use cases we have in mind.

I’ve fixed this according to what we’ve discussed: when more than one of
the user-provided or channel-required channels have the same name we
ignore the more recent specification unless it is more specific
(i.e. the new channel specification mentions a commit while the former
did not).

This is a little verbose because I replaced the simple “append-map” with
a more complex “fold” with a composite accumulator to avoid mutation.

Suggestions on how to simplify this are welcome!

--
Ricardo


[-- Attachment #2: 0001-guix-Add-support-for-channel-dependencies.patch --]
[-- Type: text/x-patch, Size: 10644 bytes --]

From e23225640e723988de215d110e377c93c8108245 Mon Sep 17 00:00:00 2001
From: Ricardo Wurmus <rekado@elephly.net>
Date: Sat, 13 Oct 2018 08:39:23 +0200
Subject: [PATCH] guix: Add support for channel dependencies.

* guix/channels.scm (<channel-metadata>): New record.
(read-channel-metadata, channel-instance-dependencies): New procedures.
(latest-channel-instances): Include channel dependencies; add optional
argument PREVIOUS-CHANNELS.
(channel-instance-derivations): Build derivation for additional channels and
add it as dependency to the channel instance derivation.
* doc/guix.texi (Channels): Add subsection "Declaring Channel Dependencies".
---
 doc/guix.texi     |  33 +++++++++++++
 guix/channels.scm | 122 ++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 139 insertions(+), 16 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index f4f19949f..7291a88ba 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -3020,6 +3020,39 @@ the new and upgraded packages that are listed, some like @code{my-gimp} and
 @code{my-emacs-with-cool-features} might come from
 @code{my-personal-packages}, while others come from the Guix default channel.
 
+@cindex dependencies, channels
+@cindex meta-data, channels
+@subsection Declaring Channel Dependencies
+
+Channel authors may decide to augment a package collection provided by other
+channels.  They can declare their channel to be dependent on other channels in
+a meta-data file @file{.guix-channel}, which is to be placed in the root of
+the channel repository.
+
+The meta-data file should contain a simple S-expression like this:
+
+@lisp
+(channel
+ (version 0)
+ (dependencies
+  (channel
+   (name 'some-collection)
+   (url "https://example.org/first-collection.git"))
+  (channel
+   (name 'some-other-collection)
+   (url "https://example.org/second-collection.git")
+   (branch "testing"))))
+@end lisp
+
+In the above example this channel is declared to depend on two other channels,
+which will both be fetched automatically.  The modules provided by the channel
+will be compiled in an environment where the modules of all these declared
+channels are available.
+
+For the sake of reliability and maintainability, you should avoid dependencies
+on channels that you don't control, and you should aim to keep the number of
+dependencies to a minimum.
+
 @subsection Replicating Guix
 
 @cindex pinning, channels
diff --git a/guix/channels.scm b/guix/channels.scm
index 82389eb58..6393179a4 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -27,6 +28,7 @@
   #:use-module (guix store)
   #:use-module (guix i18n)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-2)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-11)
   #:autoload   (guix self) (whole-package)
@@ -72,7 +74,6 @@
   (commit    channel-commit (default #f))
   (location  channel-location
              (default (current-source-location)) (innate)))
-;; TODO: Add a way to express dependencies among channels.
 
 (define %default-channels
   ;; Default list of channels.
@@ -92,6 +93,12 @@
   (commit    channel-instance-commit)
   (checkout  channel-instance-checkout))
 
+(define-record-type <channel-metadata>
+  (channel-metadata version dependencies)
+  channel-metadata?
+  (version       channel-metadata-version)
+  (dependencies  channel-metadata-dependencies))
+
 (define (channel-reference channel)
   "Return the \"reference\" for CHANNEL, an sexp suitable for
 'latest-repository-commit'."
@@ -99,20 +106,90 @@
     (#f      `(branch . ,(channel-branch channel)))
     (commit  `(commit . ,(channel-commit channel)))))
 
-(define (latest-channel-instances store channels)
+(define (read-channel-metadata instance)
+  "Return a channel-metadata record read from the channel INSTANCE's
+description file, or return #F if the channel instance does not include the
+file."
+  (let* ((source (channel-instance-checkout instance))
+         (meta-file (string-append source "/.guix-channel")))
+    (and (file-exists? meta-file)
+         (and-let* ((raw (call-with-input-file meta-file read))
+                    (version (and=> (assoc-ref raw 'version) first))
+                    (dependencies (or (assoc-ref raw 'dependencies) '())))
+           (channel-metadata
+            version
+            (map (lambda (item)
+                   (let ((get (lambda* (key #:optional default)
+                                (or (and=> (assoc-ref item key) first) default))))
+                     (and-let* ((name (get 'name))
+                                (url (get 'url))
+                                (branch (get 'branch "master")))
+                       (channel
+                        (name name)
+                        (branch branch)
+                        (url url)
+                        (commit (get 'commit))))))
+                 dependencies))))))
+
+(define (channel-instance-dependencies instance)
+  "Return the list of channels that are declared as dependencies for the given
+channel INSTANCE."
+  (match (read-channel-metadata instance)
+    (#f '())
+    (($ <channel-metadata> version dependencies)
+     dependencies)))
+
+(define* (latest-channel-instances store channels #:optional (previous-channels '()))
   "Return a list of channel instances corresponding to the latest checkouts of
-CHANNELS."
-  (map (lambda (channel)
-         (format (current-error-port)
-                 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
-                 (channel-name channel)
-                 (channel-url channel))
-         (let-values (((checkout commit)
-                       (latest-repository-commit store (channel-url channel)
-                                                 #:ref (channel-reference
-                                                        channel))))
-           (channel-instance channel commit checkout)))
-       channels))
+CHANNELS and the channels on which they depend.  PREVIOUS-CHANNELS is a list
+of previously processed channels."
+  ;; Only process channels that are unique, or that are more specific than a
+  ;; previous channel specification.
+  (define (ignore? channel others)
+    (member channel others
+            (lambda (a b)
+              (and (eq? (channel-name a) (channel-name b))
+                   (or (channel-commit b)
+                       (not (or (channel-commit a)
+                                (channel-commit b))))))))
+  ;; Accumulate a list of instances.  A list of processed channels is also
+  ;; accumulated to decide on duplicate channel specifications.
+  (match (fold (lambda (channel acc)
+                 (match acc
+                   ((#:channels previous-channels #:instances instances)
+                    (if (ignore? channel previous-channels)
+                        acc
+                        (begin
+                          (format (current-error-port)
+                                  (G_ "Updating channel '~a' from Git repository at '~a'...~%")
+                                  (channel-name channel)
+                                  (channel-url channel))
+                          (let-values (((checkout commit)
+                                        (latest-repository-commit store (channel-url channel)
+                                                                  #:ref (channel-reference
+                                                                         channel))))
+                            (let ((instance (channel-instance channel commit checkout)))
+                              (let-values (((new-instances new-channels)
+                                            (latest-channel-instances
+                                             store
+                                             (channel-instance-dependencies instance)
+                                             previous-channels)))
+                                `(#:channels
+                                  ,(append (cons channel new-channels)
+                                           previous-channels)
+                                  #:instances
+                                  ,(append (cons instance new-instances)
+                                           instances))))))))))
+               `(#:channels ,previous-channels #:instances ())
+               channels)
+    ((#:channels channels #:instances instances)
+     (let ((instance-name (compose channel-name channel-instance-channel)))
+       ;; Remove all earlier channel specifications if they are followed by a
+       ;; more specific one.
+       (values (delete-duplicates instances
+                                  (lambda (a b)
+                                    (eq? (instance-name a) (instance-name b))))
+               channels)))))
 
 (define %self-build-file
   ;; The file containing code to build Guix.  This serves the same purpose as
@@ -223,8 +300,21 @@ INSTANCES."
           (lambda (instance)
             (if (eq? instance core-instance)
                 (return core)
-                (build-channel-instance instance
-                                        (cons core dependencies))))
+                (match (channel-instance-dependencies instance)
+                  (()
+                   (build-channel-instance instance
+                                           (cons core dependencies)))
+                  (channels
+                   (mlet %store-monad ((dependencies-derivation
+                                        (latest-channel-derivation
+                                         ;; %default-channels is used here to
+                                         ;; ensure that the core channel is
+                                         ;; available for channels declared as
+                                         ;; dependencies.
+                                         (append channels %default-channels))))
+                     (build-channel-instance instance
+                                             (cons dependencies-derivation
+                                                   (cons core dependencies))))))))
           instances)))
 
 (define (whole-package-for-legacy name modules)
-- 
2.19.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-18 20:24   ` Ricardo Wurmus
@ 2018-10-20 20:52     ` Chris Marusich
  2018-10-22 12:04       ` Ludovic Courtès
  2018-10-22 12:14     ` Ludovic Courtès
  1 sibling, 1 reply; 14+ messages in thread
From: Chris Marusich @ 2018-10-20 20:52 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 3837 bytes --]

Ricardo Wurmus <ricardo.wurmus@mdc-berlin.de> writes:

> [...]
>
>> Chris raises interesting issues.  I think it’s OK to first come up with
>> an implementation that has some limitations but works with the simple
>> use cases we have in mind.
>
> I’ve fixed this according to what we’ve discussed: when more than one of
> the user-provided or channel-required channels have the same name we
> ignore the more recent specification unless it is more specific
> (i.e. the new channel specification mentions a commit while the former
> did not).

As long as the "channel resolution mechanism" is deterministic, it's
probably OK.  But if you have two channels A which depends on C, and B
which depends on C', where C' is a different version of C, then we can
arrive in a situation where the author of A tests and provides support
for their package definitions in the absence of channel B, and the
author of B tests and provides support for their package definitions in
the absence of channel A, and a user who wants to use packages from both
A and B is stuck because one of the channel owners (the one whose
dependency we didn't pick) doesn't want to support that use case.

It sounds to me like we are taking all the channels are merging them
into a single profile, and then we access it using a single Guix
inferior.  That's why we feel like we have to chose a specific version
of C to use with both A and B.  In this way, "installing" multiple
channels into this one profile is similar to the propagation of multiple
packages into a single profile.

Consider package propagation.  If I have a piece of software X which
depends on library Z and another piece of software Y which depends on
Z', where Z' is a different version of Z, then we have a similar
problem.  If I install packages X and Y into my profile and the
libraries Z and Z' are propagated, it will cause conflicts, and we will
need to choose a single version of the library.  When we do that, there
is no guarantee that X or Y will function correctly, since the person
who developed X didn't test using Z', and the person who developed Y
didn't test using Z.

Instead, if we install install X and Y without propagating Z and Z', we
have a solution: X and Y can coexist in the same profile.  X refers to Z
in the store (via its rpath or equivalent), and Y refers to Z' in
another location in the store.  When the user runs X, it runs using the
library Z.  When the user runs Y, it runs using library Z'.  The user
has not "voided their warranty", so to speak, by using a version of the
library that the developer doesn't want to support.  If there's a
problem, the user can go to the developer for support more easily.

I think your proposed solution is basically "channel propagation".
Don't get me wrong: It's great that we can choose a specific version of
channel C deterministically!  This means users can share their
channels.scm file and reproduce the exact channel configuration easily.
However, it might be even better if we could figure out how to avoid
"propagating" the channels and introducing conflicts.  Maybe there is a
way to run one Guix inferior per channel, so that one inferior can be
responsible for packages from channel A (using C), and another inferior
can be responsible for packages from channel B (using C')?

By the way, even if we come up with a solution like this, I think it's
safe to say that the core Guix channel must be the same version always.
I can't currently see how it might make sense for two third-party
channels to depend on different versions of the Guix channel, since the
Guix channel provides not only package definitions but also the Guix
code.

I hope that makes sense.  Either way, your work is already an
improvement.  Thank you for it!

-- 
Chris

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-20 20:52     ` Chris Marusich
@ 2018-10-22 12:04       ` Ludovic Courtès
  2018-10-23  7:44         ` Chris Marusich
  0 siblings, 1 reply; 14+ messages in thread
From: Ludovic Courtès @ 2018-10-22 12:04 UTC (permalink / raw)
  To: Chris Marusich; +Cc: guix-devel, Ricardo Wurmus

Hello!

Chris Marusich <cmmarusich@gmail.com> skribis:

> Ricardo Wurmus <ricardo.wurmus@mdc-berlin.de> writes:
>
>> [...]
>>
>>> Chris raises interesting issues.  I think it’s OK to first come up with
>>> an implementation that has some limitations but works with the simple
>>> use cases we have in mind.
>>
>> I’ve fixed this according to what we’ve discussed: when more than one of
>> the user-provided or channel-required channels have the same name we
>> ignore the more recent specification unless it is more specific
>> (i.e. the new channel specification mentions a commit while the former
>> did not).
>
> As long as the "channel resolution mechanism" is deterministic, it's
> probably OK.  But if you have two channels A which depends on C, and B
> which depends on C', where C' is a different version of C, then we can
> arrive in a situation where the author of A tests and provides support
> for their package definitions in the absence of channel B, and the
> author of B tests and provides support for their package definitions in
> the absence of channel A, and a user who wants to use packages from both
> A and B is stuck because one of the channel owners (the one whose
> dependency we didn't pick) doesn't want to support that use case.

Good point.  I agree that it’s similar to the question of propagated
inputs, which we deal with by reporting an error when a collision
arises.

So, similarly, I think the safe way would be to report an error when
channel requirements conflict.

We must define what it means for two <channel>s to conflict:

  • if a channel’s ‘commit’ is #f, then any channel with the same name
    but a different ‘uri’ and/or a different ‘branch’ and/or a non-#f
    commit conflicts;

  • if a channel’s ‘commit’ is not #f, then any channel with the same
    name and otherwise different fields conflicts.

WDYT?

If we have inspiration later, we can liberalize this, for instance by
using several inferiors.  It would be quite a bit of extra work, and
it’s not immediately clear to me how that could work.  I believe what
Ricardo proposes already covers many use cases anyway.

Thanks,
Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-18 20:24   ` Ricardo Wurmus
  2018-10-20 20:52     ` Chris Marusich
@ 2018-10-22 12:14     ` Ludovic Courtès
  1 sibling, 0 replies; 14+ messages in thread
From: Ludovic Courtès @ 2018-10-22 12:14 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

Hello!

Ricardo Wurmus <ricardo.wurmus@mdc-berlin.de> skribis:

>> I’d recommend ‘match’ for the outer sexp, and then something like the
>> ‘alist-let*’ macro from (gnu services herd) in places where you’d like
>> to leave field ordering unspecified.
>
> I keep forgetting about alist-let* (from srfi-2, not herd), even though
> it’s so useful!  “channel-instance-dependencies” now uses the
> <channel-metadata> record via “read-channel-metadata” and uses match.

Heck I didn’t even know about SRFI-2.  :-)

>> Then I think it would make sense to add the ‘dependencies’ field to
>> <channel-instance> directly (and keep <channel-metadata> internal.)
>> Each element of the ‘dependencies’ field would be another
>> <channel-instance>.
>>
>> Actually ‘dependencies’ could be a promise that reads channel meta-data
>> and looks up the channel instances for the given dependencies.
>> Something like that.
>
> This sounds good, but I don’t know how to make it work well, because
> there’s a circular relationship here if we want to keep the abstractions
> pretty.  I can’t simply define the “dependencies” field of
> <channel-instance> to have a default thunked procedure like this:
>
>    (match (read-channel-metadata checkout)
>      (#f '())
>      (($ <channel-metadata> _ dependencies)
>       dependencies))
>
> Because record fields cannot access other record fields such as
> “checkout”.  This makes the code look rather silly as we’re creating an
> instance with an explicit dependencies value only to read it from that
> same record in the next expression.
>
> In light of these complications I’d prefer to have a procedure
> “channel-instance-dependencies” that handles this for us, and do without
> a “dependencies” field on the <channel-instance> record.
>
> What do you think?

Sure, that makes sense to me (sometimes I throw ideas that look great on
paper but simply don’t fly in practice!).

> From e23225640e723988de215d110e377c93c8108245 Mon Sep 17 00:00:00 2001
> From: Ricardo Wurmus <rekado@elephly.net>
> Date: Sat, 13 Oct 2018 08:39:23 +0200
> Subject: [PATCH] guix: Add support for channel dependencies.
>
> * guix/channels.scm (<channel-metadata>): New record.
> (read-channel-metadata, channel-instance-dependencies): New procedures.
> (latest-channel-instances): Include channel dependencies; add optional
> argument PREVIOUS-CHANNELS.
> (channel-instance-derivations): Build derivation for additional channels and
> add it as dependency to the channel instance derivation.
> * doc/guix.texi (Channels): Add subsection "Declaring Channel Dependencies".

[...]

> +  ;; Accumulate a list of instances.  A list of processed channels is also
> +  ;; accumulated to decide on duplicate channel specifications.
> +  (match (fold (lambda (channel acc)
> +                 (match acc
> +                   ((#:channels previous-channels #:instances instances)
> +                    (if (ignore? channel previous-channels)
> +                        acc
> +                        (begin
> +                          (format (current-error-port)
> +                                  (G_ "Updating channel '~a' from Git repository at '~a'...~%")
> +                                  (channel-name channel)
> +                                  (channel-url channel))
> +                          (let-values (((checkout commit)
> +                                        (latest-repository-commit store (channel-url channel)
> +                                                                  #:ref (channel-reference
> +                                                                         channel))))
> +                            (let ((instance (channel-instance channel commit checkout)))
> +                              (let-values (((new-instances new-channels)
> +                                            (latest-channel-instances
> +                                             store
> +                                             (channel-instance-dependencies instance)
> +                                             previous-channels)))
> +                                `(#:channels
> +                                  ,(append (cons channel new-channels)
> +                                           previous-channels)
> +                                  #:instances
> +                                  ,(append (cons instance new-instances)
> +                                           instances))))))))))
> +               `(#:channels ,previous-channels #:instances ())
> +               channels)

This seems to be assuming that CHANNELS is topologically-sorted, is that
right?

Otherwise LGTM.

There’s the conflict error reporting mentioned in another message that I
think we could implement, but that can come later.

Also we should consider adding unit tests for at least some of this; I
plaid guilty for not having provided a test strategy from the start.

Thank you, and apologies for not replying on time for your presentation!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-22 12:04       ` Ludovic Courtès
@ 2018-10-23  7:44         ` Chris Marusich
  2018-10-24 13:14           ` Ludovic Courtès
  0 siblings, 1 reply; 14+ messages in thread
From: Chris Marusich @ 2018-10-23  7:44 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel, Ricardo Wurmus

[-- Attachment #1: Type: text/plain, Size: 3896 bytes --]

ludo@gnu.org (Ludovic Courtès) writes:

> Good point.  I agree that it’s similar to the question of propagated
> inputs, which we deal with by reporting an error when a collision
> arises.
>
> So, similarly, I think the safe way would be to report an error when
> channel requirements conflict.

With profiles, two packages conflict if and only if a file exists at the
same relative path in both packages' outputs.  In other words, both
packages provide "the same" file.

With channels, we build a profile containing the union of the channels.
So, normal profile conflicts will definitely occur in the profile if two
channels provide the same module (e.g., $out/my/awesome/packages.scm),
right?  Because we're putting the two channels into a single profile, I
think the normal conflict resolution mechanism will force us to use only
one module in the resulting profile.  And I think that if two channels
provide the same module, it's definitely a conflict of some kind.

Furthermore, if two channels both provide "the same" package definition
for a tool called my-awesome-program in two different modules, Guile
scheme code can still specify precisely which my-awesome-program should
be used.  In that sense, there is no conflict.  However, if both package
definition use the same name and version, then a command like "guix
package -i my-awesome-program" (and the APIs that convert package
specifications to packages) will have to choose one specific package,
right?  In that sense, there is a conflict.

It seems to me that these kinds of conflicts are what we want to avoid.
Regardless of what the channel is named, regardless of what URI it comes
from, regardless of what commit it came from, if it provides the same
modules or the same packages as another channel, there's a "channel
conflict".

However, detecting that sort of conflict seems pretty complicated.  I
don't have a good idea for how to detect it.  It seems like you would
have to traverse the entire set of modules and packages that a channel
provides, and cross-check it with other channels to make sure there are
no conflicts.  We might have to use multiple inferiors to do that, like
you mentioned.

Also like you said, we can try to implement some heuristics to reject
situations in which a "channel conflict" is likely.  Would it be hard to
change the channel mechanism so that it fails if there are any (normal)
conflicts while generating the profile that contains all the channels?
If we could prevent those (normal) conflicts while generating the
profile, it would prevent a certain class of channel conflicts: namely,
it would be impossible for two channels to provide the same guile
modules.

> We must define what it means for two <channel>s to conflict:
>
>   • if a channel’s ‘commit’ is #f, then any channel with the same name
>     but a different ‘uri’ and/or a different ‘branch’ and/or a non-#f
>     commit conflicts;
>
>   • if a channel’s ‘commit’ is not #f, then any channel with the same
>     name and otherwise different fields conflicts.

This seems like a reasonable heuristic.  What will we do when two
channels differ only in their name?  What about when two channels only
have identical fields?  Maybe in those cases we should just pick one,
ignore the other, and log a warning, since their content will be the
same.

> If we have inspiration later, we can liberalize this, for instance by
> using several inferiors.  It would be quite a bit of extra work, and
> it’s not immediately clear to me how that could work.  I believe what
> Ricardo proposes already covers many use cases anyway.

You're probably right.  I'm just trying to think about how we might
apply the functional model to this problem, rather than implementing
heuristics.  But maybe heuristics are good enough!

-- 
Chris

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Channel dependencies
  2018-10-23  7:44         ` Chris Marusich
@ 2018-10-24 13:14           ` Ludovic Courtès
  0 siblings, 0 replies; 14+ messages in thread
From: Ludovic Courtès @ 2018-10-24 13:14 UTC (permalink / raw)
  To: Chris Marusich; +Cc: guix-devel, Ricardo Wurmus

Hello!

Chris Marusich <cmmarusich@gmail.com> skribis:

> ludo@gnu.org (Ludovic Courtès) writes:
>
>> Good point.  I agree that it’s similar to the question of propagated
>> inputs, which we deal with by reporting an error when a collision
>> arises.
>>
>> So, similarly, I think the safe way would be to report an error when
>> channel requirements conflict.
>
> With profiles, two packages conflict if and only if a file exists at the
> same relative path in both packages' outputs.

What you describe here are “soft collisions”, which the profile builder
reports as warnings (which are invisible with today’s ‘guix package’.)

I was referring to profile collisions where two packages with the same
name end up in the same profile (the ‘&profile-collision-error’
exception.)

This exception would also be raised if ‘guix pull’ ended up adding the
same channels more than once in ~/.config/guix/current.

> Also like you said, we can try to implement some heuristics to reject
> situations in which a "channel conflict" is likely.  Would it be hard to
> change the channel mechanism so that it fails if there are any (normal)
> conflicts while generating the profile that contains all the channels?
> If we could prevent those (normal) conflicts while generating the
> profile, it would prevent a certain class of channel conflicts: namely,
> it would be impossible for two channels to provide the same guile
> modules.

‘union-build’ has a #:resolve-collision parameter.  We could set it when
building ~/.config/guix/current so that an error is raised when the same
file is provided more than once.

(It’s a simple change we can make independently of what Ricardo is
proposing.)

WDYT?

>> We must define what it means for two <channel>s to conflict:
>>
>>   • if a channel’s ‘commit’ is #f, then any channel with the same name
>>     but a different ‘uri’ and/or a different ‘branch’ and/or a non-#f
>>     commit conflicts;
>>
>>   • if a channel’s ‘commit’ is not #f, then any channel with the same
>>     name and otherwise different fields conflicts.
>
> This seems like a reasonable heuristic.  What will we do when two
> channels differ only in their name?  What about when two channels only
> have identical fields?  Maybe in those cases we should just pick one,
> ignore the other, and log a warning, since their content will be the
> same.

Yes, they would effectively be ‘equal?’.

>> If we have inspiration later, we can liberalize this, for instance by
>> using several inferiors.  It would be quite a bit of extra work, and
>> it’s not immediately clear to me how that could work.  I believe what
>> Ricardo proposes already covers many use cases anyway.
>
> You're probably right.  I'm just trying to think about how we might
> apply the functional model to this problem, rather than implementing
> heuristics.  But maybe heuristics are good enough!

Sure, and that’s good!

Thanks,
Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2018-10-24 13:19 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-13  6:54 Channel dependencies Ricardo Wurmus
2018-10-13 11:09 ` Pierre Neidhardt
2018-10-14  2:16 ` Chris Marusich
2018-10-14  9:49   ` Ricardo Wurmus
2018-10-15  9:29     ` Ludovic Courtès
2018-10-15  9:35       ` Ricardo Wurmus
2018-10-15 11:49         ` Ludovic Courtès
2018-10-15  9:41 ` Ludovic Courtès
2018-10-18 20:24   ` Ricardo Wurmus
2018-10-20 20:52     ` Chris Marusich
2018-10-22 12:04       ` Ludovic Courtès
2018-10-23  7:44         ` Chris Marusich
2018-10-24 13:14           ` Ludovic Courtès
2018-10-22 12:14     ` Ludovic Courtès

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).