diff mbox series

[bug#37978] guix: new command "guix time-machine"

Message ID m1bltzd4z7.fsf@ordinateur-de-catherine--konrad.home
State Accepted
Headers show
Series [bug#37978] guix: new command "guix time-machine" | expand

Commit Message

Konrad Hinsen Oct. 25, 2019, 3:42 p.m. UTC
* guix/scripts/time-machine.scm: New file.
* guix/scripts/pull.scm: Export function channel-list.
* guix/inferior.scm: New function cached-guix-filetree-for-channels.
* doc/guix.texi: Document "git time-machine"
---
 doc/guix.texi                 |  47 +++++++++++++++-
 guix/inferior.scm             |  38 +++++++++----
 guix/scripts/pull.scm         |   1 +
 guix/scripts/time-machine.scm | 101 ++++++++++++++++++++++++++++++++++
 4 files changed, 174 insertions(+), 13 deletions(-)
 create mode 100644 guix/scripts/time-machine.scm

Comments

Ludovic Courtès Nov. 6, 2019, 1:53 p.m. UTC | #1
Hello Konrad!

Konrad Hinsen <konrad.hinsen@fastmail.net> skribis:

> * guix/scripts/time-machine.scm: New file.
> * guix/scripts/pull.scm: Export function channel-list.
> * guix/inferior.scm: New function cached-guix-filetree-for-channels.
> * doc/guix.texi: Document "git time-machine"

Awesome.  :-)

Please also add time-machine.scm to Makefile.am.  In the commit log,
you’ll get bonus points if you mention the modified entities in
parentheses (see ‘git log’ for examples.)  :-)

> @@ -247,6 +247,7 @@ Utilities
>  * Invoking guix container::     Process isolation.
>  * Invoking guix weather::       Assessing substitute availability.
>  * Invoking guix processes::     Listing client processes.
> +* Invoking guix time-machine::  Running an older version of Guix.

How about moving this section a bit higher, because it’s more widely
useful than ‘guix processes’ for instance?  Actually, it could go under
“Package Management” right before “Inferiors”, WDYT?

>  The @command{guix describe --format=channels} command can even generate this
> -list of channels directly (@pxref{Invoking guix describe}).
> +list of channels directly (@pxref{Invoking guix describe}). The resulting
> +file can be used with the -C options of @command{guix pull}
> +(@pxref{Invoking guix pull}) or @command{guix time-machine}
> +(@pxref{Invoking guix time-machine}).

Nitpick: Please write two spaces after an end-of-sentence period.

> +The general syntax is:
> +
> +@example
> +guix time-machine @var{channels} -- @var{command} @var {arg}@dots{}
> +@end example

I think it should be “guix time-machine @var{options}@dots{} -- …”,
right?

IIUC, if you run:

  guix time-machine -- build hello

you build “hello” with the latest master, right?

Perhaps it would be good to add an example like this one actually, WDYT?

> +where @var{command} and @var{arg}@dots{} are passed unmodified to the
> +@command{guix} command in its old version.  The @var{channels} that define
> +this version can be specified using the following options:

Perhaps add “like for @command{guix pull}”.

> -(define* (inferior-for-channels channels
> -                                #:key
> -                                (cache-directory (%inferior-cache-directory))
> -                                (ttl (* 3600 24 30)))
> -  "Return an inferior for CHANNELS, a list of channels.  Use the cache at
> -CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds.  This
> -procedure opens a new connection to the build daemon.
> -
> -This is a convenience procedure that people may use in manifests passed to
> -'guix package -m', for instance."
> +(define* (cached-guix-filetree-for-channels channels
> +                                            #:key
> +                                            (cache-directory (%inferior-cache-directory))
> +                                            (ttl (* 3600 24 30)))
> +  "Return a directory containing a guix filetree defined by CHANNELS, a list of channels.
> +The directory is a subdirectory of CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds.
> +This procedure opens a new connection to the build daemon."

How about (1) calling it ‘cached-channel-instance’ (or similar), and (2)
not opening a connection to the daemon?

Regarding (2), it means that procedure would be a monadic procedure and
it’s up to the user to do with-store + run-with-store or whatever.  The
general convention is to not open new connections on behalf of the user
(‘inferior-for-channels’ is one of the only exceptions to the rule
because it’s a convenience function for use in manifests.)

Perhaps this change should be a separate patch.

> +(define (guix-time-machine . args)
> +  (with-error-handling
> +    (let* ((opts         (parse-args args))
> +           (channels     (channel-list opts))
> +           (command-line (assoc-ref opts 'exec))
> +           (directory    (cached-guix-filetree-for-channels channels))
> +           (executable   (string-append directory "/bin/guix")))
> +      (apply system* (cons executable command-line)))))

I think this should be:

  (apply execl executable command-line)

so that we don’t create an extra process and actually get the exit code
for that sub-process.

Thanks,
Ludo’.
Deslauriers, Douglas via Guix-patches" via Nov. 6, 2019, 2:27 p.m. UTC | #2
Konrad,

Thank you!  This is neat!

Konrad Hinsen 写道:
> * doc/guix.texi: Document "git time-machine"
                             ^^^
Kind regards,

T G-R
Ludovic Courtès Nov. 6, 2019, 2:30 p.m. UTC | #3
Another thing that comes to mind: it’d be nice to add an entry in
‘etc/news.scm’ to let users know about the new command!

Ludo’.
Konrad Hinsen Nov. 7, 2019, 1:11 p.m. UTC | #4
Tobias Geerinckx-Rice <me@tobias.gr> writes:

> Konrad Hinsen 写道:
>> * doc/guix.texi: Document "git time-machine"
>                              ^^^

Ouch. That's me. I always confuse "git" and "guix" on the command line.
It's worst for "pull", which works with both.

Is command-line dyslexia a thing already ? ;-)

Thanks for spotting the typo,
  Konrad.
Konrad Hinsen Nov. 7, 2019, 7:40 p.m. UTC | #5
Ludovic Courtès <ludo@gnu.org> writes:

> Another thing that comes to mind: it’d be nice to add an entry in
> ‘etc/news.scm’ to let users know about the new command!

If I understand the format correctly, I can't write this entry before
the command is merged into master, because I need the commit that
implements it.

Konrad.
Ludovic Courtès Nov. 7, 2019, 9:10 p.m. UTC | #6
Konrad Hinsen <konrad.hinsen@fastmail.net> skribis:

> Ludovic Courtès <ludo@gnu.org> writes:
>
>> Another thing that comes to mind: it’d be nice to add an entry in
>> ‘etc/news.scm’ to let users know about the new command!
>
> If I understand the format correctly, I can't write this entry before
> the command is merged into master, because I need the commit that
> implements it.

Exactly, so whoever pushes it must adjust the commit ID in the news
entry right before pushing.  You can just write (commit "XXX") in the
meantime.

Ludo’.
Konrad Hinsen Nov. 8, 2019, 7:14 a.m. UTC | #7
Hi Ludo,

Thanks for your detailed comments, most of which I agree with, and I
will make the required changes. There is one issue I see:

> How about (1) calling it ‘cached-channel-instance’ (or similar), and (2)
> not opening a connection to the daemon?
>
> Regarding (2), it means that procedure would be a monadic procedure and
> it’s up to the user to do with-store + run-with-store or whatever.  The

That would imply that the user always opens a daemon, even if it is not
necessary because the required instance is already in the cache. Is that
a price worth paying for a more conventional interface, in your opinion?

Cheers,
  Konrad.
Konrad Hinsen Nov. 8, 2019, 2:16 p.m. UTC | #8
Konrad Hinsen <konrad.hinsen@fastmail.net> writes:

> Thanks for your detailed comments, most of which I agree with, and I
> will make the required changes.

I just submitted two new patches. Looking forward to everyone's feedbck!

Cheers,
  Konrad.
Ludovic Courtès Nov. 8, 2019, 8:09 p.m. UTC | #9
Hello,

Konrad Hinsen <konrad.hinsen@fastmail.net> skribis:

>> How about (1) calling it ‘cached-channel-instance’ (or similar), and (2)
>> not opening a connection to the daemon?
>>
>> Regarding (2), it means that procedure would be a monadic procedure and
>> it’s up to the user to do with-store + run-with-store or whatever.  The
>
> That would imply that the user always opens a daemon, even if it is not
> necessary because the required instance is already in the cache. Is that
> a price worth paying for a more conventional interface, in your opinion?

Ah, I hadn’t thought about it.

Hmm opening a connection to the daemon doesn’t cost much, but OTOH it’s
best if we can avoid it here since ‘guix time-machine’ is just a
trampoline.

Dunno, I’ll comment on the new patches and we’ll see.  :-)

Ludo’.
Ludovic Courtès Nov. 15, 2019, 10:35 p.m. UTC | #10
Hi Konrad,

Konrad Hinsen <konrad.hinsen@fastmail.net> skribis:

> Konrad Hinsen <konrad.hinsen@fastmail.net> writes:
>
>> Thanks for your detailed comments, most of which I agree with, and I
>> will make the required changes.
>
> I just submitted two new patches. Looking forward to everyone's feedbck!

I’ve just applied them, thank you!

I followed up with one commit to gracefully handle Git errors (e.g.,
when one type ‘--commit=foobar’) and another one to handle build options
like the other commands.  Let me know if anything looks wrong.

Anyway, I can do things like this:

--8<---------------cut here---------------start------------->8---
$ ./pre-inst-env guix time-machine --commit=65956ad3526ba09e1f7a40722c96c6ef7c0936fe -- package -A |wc -l
Updating channel 'guix' from Git repository at 'https://git.savannah.gnu.org/git/guix.git'...
guile: warning: failed to install locale
warning: failed to install locale: Invalid argument
7666
--8<---------------cut here---------------end--------------->8---

… where the commit in question is from June 2018.  Pretty fun!

It would be neat if the “Updating…” message wouldn’t show up when in
fact everything is already available.  Also, it’d be nice if we could
write (and in ‘guix pull’ too) ‘--date=2019-07-07’ or
‘--date=last-month’.  Future work!  :-)

Thanks again for this nifty command!

Ludo’.
Konrad Hinsen Nov. 16, 2019, 9:06 a.m. UTC | #11
Hi Ludo,

> I followed up with one commit to gracefully handle Git errors (e.g.,
> when one type ‘--commit=foobar’) and another one to handle build options
> like the other commands.  Let me know if anything looks wrong.

Quite on the contrary, everything looks fine - thanks!

I hadn't realized that "guix pull" takes build options, but obviously
that makes sense.

> It would be neat if the “Updating…” message wouldn’t show up when in
> fact everything is already available.  Also, it’d be nice if we could
> write (and in ‘guix pull’ too) ‘--date=2019-07-07’ or
> ‘--date=last-month’.  Future work!  :-)

Right. Also not pulling anything if the requested channels are identical
to those of the current guix version. There's always more to to!

Cheers,
  Konrad.
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 7cc33c6e22..a147f16088 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -247,6 +247,7 @@  Utilities
 * Invoking guix container::     Process isolation.
 * Invoking guix weather::       Assessing substitute availability.
 * Invoking guix processes::     Listing client processes.
+* Invoking guix time-machine::  Running an older version of Guix.
 
 Invoking @command{guix build}
 
@@ -4142,7 +4143,10 @@  say, on another machine, by providing a channel specification in
 @end lisp
 
 The @command{guix describe --format=channels} command can even generate this
-list of channels directly (@pxref{Invoking guix describe}).
+list of channels directly (@pxref{Invoking guix describe}). The resulting
+file can be used with the -C options of @command{guix pull}
+(@pxref{Invoking guix pull}) or @command{guix time-machine}
+(@pxref{Invoking guix time-machine}).
 
 At this point the two machines run the @emph{exact same Guix}, with access to
 the @emph{exact same packages}.  The output of @command{guix build gimp} on
@@ -7894,6 +7898,7 @@  the Scheme programming interface of Guix in a convenient way.
 * Invoking guix container::     Process isolation.
 * Invoking guix weather::       Assessing substitute availability.
 * Invoking guix processes::     Listing client processes.
+* Invoking guix time-machine::  Running an older version of Guix.
 @end menu
 
 @node Invoking guix build
@@ -10563,6 +10568,46 @@  ClientPID: 19419
 ClientCommand: cuirass --cache-directory /var/cache/cuirass @dots{}
 @end example
 
+@node Invoking guix time-machine
+@section Invoking @command{guix time-machine}
+
+@cindex @command{guix time-machine}
+@cindex pinning, channels
+@cindex replicating Guix
+@cindex reproducibility, of Guix
+
+The @command{guix time-machine} command provides access to older
+versions of Guix, for example to install older versions of packages,
+or to reproduce a computation in an identical environment. The version
+of Guix to be used is defined by a commit or by a channel
+description file created by @command{guix describe}
+(@pxref{Invoking guix describe}).
+
+The general syntax is:
+
+@example
+guix time-machine @var{channels} -- @var{command} @var {arg}@dots{}
+@end example
+
+where @var{command} and @var{arg}@dots{} are passed unmodified to the
+@command{guix} command in its old version.  The @var{channels} that define
+this version can be specified using the following options:
+
+@table @code
+@item --url=@var{url}
+@itemx --commit=@var{commit}
+@itemx --branch=@var{branch}
+Use the @code{guix} channel from the specified @var{url}, at the
+given @var{commit} (a valid Git commit ID represented as a hexadecimal
+string), or @var{branch}.
+
+@item --channels=@var{file}
+@itemx -C @var{file}
+Read the list of channels from @var{file}.  @var{file} must contain
+Scheme code that evaluates to a list of channel objects.
+@xref{Channels} for more information.
+@end table
+
 
 @node System Configuration
 @chapter System Configuration
diff --git a/guix/inferior.scm b/guix/inferior.scm
index b8e2f21f42..cb80bb43d5 100644
--- a/guix/inferior.scm
+++ b/guix/inferior.scm
@@ -89,6 +89,7 @@ 
             gexp->derivation-in-inferior
 
             %inferior-cache-directory
+            cached-guix-filetree-for-channels
             inferior-for-channels))
 
 ;;; Commentary:
@@ -635,16 +636,13 @@  failing when GUIX is too old and lacks the 'guix repl' command."
   (make-parameter (string-append (cache-directory #:ensure? #f)
                                  "/inferiors")))
 
-(define* (inferior-for-channels channels
-                                #:key
-                                (cache-directory (%inferior-cache-directory))
-                                (ttl (* 3600 24 30)))
-  "Return an inferior for CHANNELS, a list of channels.  Use the cache at
-CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds.  This
-procedure opens a new connection to the build daemon.
-
-This is a convenience procedure that people may use in manifests passed to
-'guix package -m', for instance."
+(define* (cached-guix-filetree-for-channels channels
+                                            #:key
+                                            (cache-directory (%inferior-cache-directory))
+                                            (ttl (* 3600 24 30)))
+  "Return a directory containing a guix filetree defined by CHANNELS, a list of channels.
+The directory is a subdirectory of CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds.
+This procedure opens a new connection to the build daemon."
   (with-store store
     (let ()
       (define instances
@@ -680,7 +678,7 @@  This is a convenience procedure that people may use in manifests passed to
                                           (file-expiration-time ttl))
 
       (if (file-exists? cached)
-          (open-inferior cached)
+          cached
           (run-with-store store
             (mlet %store-monad ((profile
                                  (channel-instances->derivation instances)))
@@ -689,4 +687,20 @@  This is a convenience procedure that people may use in manifests passed to
                 (built-derivations (list profile))
                 (symlink* (derivation->output-path profile) cached)
                 (add-indirect-root* cached)
-                (return (open-inferior cached)))))))))
+                (return cached))))))))
+
+(define* (inferior-for-channels channels
+                                #:key
+                                (cache-directory (%inferior-cache-directory))
+                                (ttl (* 3600 24 30)))
+  "Return an inferior for CHANNELS, a list of channels.  Use the cache at
+CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds.  This
+procedure opens a new connection to the build daemon.
+
+This is a convenience procedure that people may use in manifests passed to
+'guix package -m', for instance."
+  (define cached
+    (cached-guix-filetree-for-channels channels
+                                       #:cache-directory cache-directory
+                                       #:ttl ttl))
+  (open-inferior cached))
diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm
index 80d070652b..a508e817b2 100644
--- a/guix/scripts/pull.scm
+++ b/guix/scripts/pull.scm
@@ -56,6 +56,7 @@ 
   #:use-module (ice-9 vlist)
   #:use-module (ice-9 format)
   #:export (display-profile-content
+            channel-list
             guix-pull))
 
 
diff --git a/guix/scripts/time-machine.scm b/guix/scripts/time-machine.scm
new file mode 100644
index 0000000000..8e954d51e1
--- /dev/null
+++ b/guix/scripts/time-machine.scm
@@ -0,0 +1,101 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2019 Konrad Hinsen <konrad.hinsen@fastmail.net>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts time-machine)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:use-module (guix inferior)
+  #:use-module (guix channels)
+  #:use-module ((guix scripts pull) #:select (channel-list))
+  #:use-module (ice-9 match)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:export (guix-time-machine))
+
+
+;;;
+;;; Command-line options.
+;;;
+
+(define (show-help)
+  (display (G_ "Usage: guix time-machine [OPTION] -- COMMAND ARGS...
+Execute COMMAND ARGS... in an older version of Guix.\n"))
+  (display (G_ "
+  -C, --channels=FILE    deploy the channels defined in FILE"))
+  (display (G_ "
+      --url=URL          use the Git repository at URL"))
+  (display (G_ "
+      --commit=COMMIT    use the specified COMMIT"))
+  (display (G_ "
+      --branch=BRANCH    use the tip of the specified BRANCH"))
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\C "channels") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'channel-file arg result)))
+         (option '("url") #t #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'repository-url arg
+                               (alist-delete 'repository-url result))))
+         (option '("commit") #t #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'ref `(commit . ,arg) result)))
+         (option '("branch") #t #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'ref `(branch . ,arg) result)))
+        (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix time-machine")))))
+
+(define (parse-args args)
+  "Parse the list of command line arguments ARGS."
+  ;; The '--' token is used to separate the command to run from the rest of
+  ;; the operands.
+  (let-values (((args command) (break (cut string=? "--" <>) args)))
+    (let ((opts (parse-command-line args %options '(()) #:build-options? #f)))
+      (match command
+        (() opts)
+        (("--") opts)
+        (("--" command ...) (alist-cons 'exec command opts))))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-time-machine . args)
+  (with-error-handling
+    (let* ((opts         (parse-args args))
+           (channels     (channel-list opts))
+           (command-line (assoc-ref opts 'exec))
+           (directory    (cached-guix-filetree-for-channels channels))
+           (executable   (string-append directory "/bin/guix")))
+      (apply system* (cons executable command-line)))))