From fb368f4e760777e399aa58b08b89df1832fda8ba Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 10:49:22 +0200 Subject: packages: Add 'package-development-inputs'. * guix/packages.scm (package-development-inputs): New procedure. * guix/scripts/environment.scm (package-environment-inputs): Use it. * tests/packages.scm ("package-development-inputs") ("package-development-inputs, cross-compilation"): New tests. * doc/guix.texi (package Reference): Document it. --- guix/scripts/environment.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix/scripts') diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 6958bd6238..418f11c37e 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -82,7 +82,7 @@ package." packages for PACKAGE." ;; Remove non-package inputs such as origin records. (filter-map input->manifest-entry - (bag-transitive-inputs (package->bag package)))) + (package-development-inputs package))) (define (show-help) (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...] -- cgit v1.2.3 From 23f99f1a299ed0e19d926a0f719980b3c151c9c0 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 11:14:58 +0200 Subject: profiles: Add 'package->development-manifest'. * guix/profiles.scm (package->development-manifest): New procedure. * guix/scripts/environment.scm (input->manifest-entry) (package-environment-inputs): Remove. * guix/scripts/environment.scm (options/resolve-packages): Use 'package->development-manifest' instead of 'package-environment-inputs'. * tests/profiles.scm ("package->development-manifest"): New test. --- doc/guix.texi | 11 +++++++++++ guix/profiles.scm | 19 +++++++++++++++++++ guix/scripts/environment.scm | 27 +++++---------------------- tests/profiles.scm | 7 +++++++ 4 files changed, 42 insertions(+), 22 deletions(-) (limited to 'guix/scripts') diff --git a/doc/guix.texi b/doc/guix.texi index 31b8cd7069..37d31d5fa9 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -3341,6 +3341,17 @@ objects, like this: '("emacs" "guile@@2.2" "guile@@2.2:debug")) @end lisp +@findex package->development-manifest +You might also want to create a manifest for all the dependencies of a +package, rather than the package itself: + +@lisp +(package->development-manifest (specification->package "emacs")) +@end lisp + +The example above gives you all the software required to develop Emacs, +similar to what @command{guix environment emacs} provides. + @xref{export-manifest, @option{--export-manifest}}, to learn how to obtain a manifest file from an existing profile. diff --git a/guix/profiles.scm b/guix/profiles.scm index 2486f91d09..9f30349c69 100644 --- a/guix/profiles.scm +++ b/guix/profiles.scm @@ -124,6 +124,7 @@ profile-manifest package->manifest-entry + package->development-manifest packages->manifest ca-certificate-bundle %default-profile-hooks @@ -400,6 +401,24 @@ file name." (properties properties)))) entry)) +(define* (package->development-manifest package + #:optional + (system (%current-system)) + #:key target) + "Return a manifest for the \"development inputs\" of PACKAGE for SYSTEM, +optionally when cross-compiling to TARGET. Development inputs include both +explicit and implicit inputs of PACKAGE." + (manifest + (filter-map (match-lambda + ((label (? package? package)) + (package->manifest-entry package)) + ((label (? package? package) output) + (package->manifest-entry package output)) + ;; TODO: Support . + (_ + #f)) + (package-development-inputs package system #:target target)))) + (define (packages->manifest packages) "Return a list of manifest entries, one for each item listed in PACKAGES. Elements of PACKAGES can be either package objects or package/string tuples diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 418f11c37e..54f48a7482 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -66,24 +66,6 @@ do not augment existing environment variables with additional search paths." (newline))) (profile-search-paths profile manifest))) -(define (input->manifest-entry input) - "Return a manifest entry for INPUT, or #f if INPUT does not correspond to a -package." - (match input - ((_ (? package? package)) - (package->manifest-entry package)) - ((_ (? package? package) output) - (package->manifest-entry package output)) - (_ - #f))) - -(define (package-environment-inputs package) - "Return a list of manifest entries corresponding to the transitive input -packages for PACKAGE." - ;; Remove non-package inputs such as origin records. - (filter-map input->manifest-entry - (package-development-inputs package))) - (define (show-help) (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...] Build an environment that includes the dependencies of PACKAGE and execute @@ -297,11 +279,11 @@ for the corresponding packages." ((? package? package) (if (eq? mode 'ad-hoc-package) (list (package->manifest-entry* package)) - (package-environment-inputs package))) + (manifest-entries (package->development-manifest package)))) (((? package? package) (? string? output)) (if (eq? mode 'ad-hoc-package) (list (package->manifest-entry* package output)) - (package-environment-inputs package))) + (manifest-entries (package->development-manifest package)))) ((lst ...) (append-map (cut packages->outputs <> mode) lst)))) @@ -313,8 +295,9 @@ for the corresponding packages." (specification->package+output spec))) (list (package->manifest-entry* package output)))) (('package 'package (? string? spec)) - (package-environment-inputs - (transform (specification->package+output spec)))) + (manifest-entries + (package->development-manifest + (transform (specification->package+output spec))))) (('expression mode str) ;; Add all the outputs of the package STR evaluates to. (packages->outputs (read/eval str) mode)) diff --git a/tests/profiles.scm b/tests/profiles.scm index 06a0387221..cac5b73347 100644 --- a/tests/profiles.scm +++ b/tests/profiles.scm @@ -265,6 +265,13 @@ (manifest-transaction-removal-candidate? guile-2.0.9 t) (null? install) (null? downgrade) (null? upgrade))))) +(test-assert "package->development-manifest" + (let ((manifest (package->development-manifest packages:hello))) + (every (lambda (name) + (manifest-installed? manifest + (manifest-pattern (name name)))) + '("gcc" "binutils" "glibc" "coreutils" "grep" "sed")))) + (test-assertm "profile-derivation" (mlet* %store-monad ((entry -> (package->manifest-entry %bootstrap-guile)) -- cgit v1.2.3 From 80edb7df6586464aa40e84e103f0045452de95db Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 15:19:54 +0200 Subject: Add 'guix shell'. * guix/scripts/shell.scm, tests/guix-shell.sh: New files. * Makefile.am (MODULES): Add 'shell.scm'. (SH_TESTS): Add 'tests/guix-shell.sh'. * guix/scripts/environment.scm (show-environment-options-help): New procedure. (show-help): Use it. (guix-environment*): New procedure. (guix-environment): Use it. * po/guix/POTFILES.in: Add it. * doc/guix.texi (Features): Refer to "guix shell" (Invoking guix package): Likewise. (Development): Likewise. (Invoking guix shell): New node. (Invoking guix environment): Add deprecation warning. (Debugging Build Failures): Use 'guix shell' in examples. (Invoking guix container): Refer to 'guix shell'. (Invoking guix processes, Virtualization Services): Adjust examples to use 'guix shell'. * doc/contributing.texi (Building from Git): Refer to 'guix shell'. * etc/completion/bash/guix: Handle "shell". --- Makefile.am | 2 + doc/contributing.texi | 8 +- doc/guix.texi | 368 ++++++++++++++++++++++++++++++++++++++++--- etc/completion/bash/guix | 6 +- guix/scripts/environment.scm | 52 ++++-- guix/scripts/shell.scm | 135 ++++++++++++++++ po/guix/POTFILES.in | 1 + tests/guix-shell.sh | 54 +++++++ 8 files changed, 579 insertions(+), 47 deletions(-) create mode 100644 guix/scripts/shell.scm create mode 100644 tests/guix-shell.sh (limited to 'guix/scripts') diff --git a/Makefile.am b/Makefile.am index 41ec19eb89..239387c2f4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -316,6 +316,7 @@ MODULES = \ guix/scripts/import/stackage.scm \ guix/scripts/import/texlive.scm \ guix/scripts/environment.scm \ + guix/scripts/shell.scm \ guix/scripts/publish.scm \ guix/scripts/edit.scm \ guix/scripts/size.scm \ @@ -552,6 +553,7 @@ SH_TESTS = \ tests/guix-authenticate.sh \ tests/guix-environment.sh \ tests/guix-environment-container.sh \ + tests/guix-shell.sh \ tests/guix-graph.sh \ tests/guix-describe.sh \ tests/guix-repl.sh \ diff --git a/doc/contributing.texi b/doc/contributing.texi index 76ab913b0d..db0f836157 100644 --- a/doc/contributing.texi +++ b/doc/contributing.texi @@ -73,10 +73,10 @@ all the dependencies and appropriate environment variables are set up to hack on Guix: @example -guix environment guix --pure +guix shell -D guix --pure @end example -@xref{Invoking guix environment}, for more information on that command. +@xref{Invoking guix shell}, for more information on that command. If you are unable to use Guix when building Guix from a checkout, the following are the required packages in addition to those mentioned in the @@ -92,10 +92,10 @@ installation instructions (@pxref{Requirements}). @end itemize On Guix, extra dependencies can be added by instead running @command{guix -environment} with @option{--ad-hoc}: +shell}: @example -guix environment guix --pure --ad-hoc help2man git strace +guix shell -D guix help2man git strace --pure @end example Run @command{./bootstrap} to generate the build system infrastructure diff --git a/doc/guix.texi b/doc/guix.texi index 37d31d5fa9..f5bfb59229 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -119,6 +119,7 @@ Documentation License''. @dircategory Software development @direntry +* guix shell: (guix)Invoking guix shell. Creating software environments. * guix environment: (guix)Invoking guix environment. Building development environments with Guix. * guix build: (guix)Invoking guix build. Building packages. * guix pack: (guix)Invoking guix pack. Creating binary bundles. @@ -262,6 +263,7 @@ Channels Development +* Invoking guix shell:: Spawning one-off software environments. * Invoking guix environment:: Setting up development environments. * Invoking guix pack:: Creating software bundles. * The GCC toolchain:: Working with languages supported by GCC. @@ -3067,10 +3069,10 @@ substitutes: they can force a local build and @emph{challenge} providers (@pxref{Invoking guix challenge}). Control over the build environment is a feature that is also useful for -developers. The @command{guix environment} command allows developers of +developers. The @command{guix shell} command allows developers of a package to quickly set up the right development environment for their package, without having to manually install the dependencies of the -package into their profile (@pxref{Invoking guix environment}). +package into their profile (@pxref{Invoking guix shell}). @cindex replication, of software environments @cindex provenance tracking, of software artifacts @@ -3234,7 +3236,7 @@ As an example, @var{file} might contain a definition like this Developers may find it useful to include such a @file{guix.scm} file in the root of their project source tree that can be used to test development snapshots and create reproducible development environments -(@pxref{Invoking guix environment}). +(@pxref{Invoking guix shell}). The @var{file} may also contain a JSON representation of one or more package definitions. Running @code{guix package -f} on @@ -5559,31 +5561,352 @@ If you are a software developer, Guix provides tools that you should find helpful---independently of the language you're developing in. This is what this chapter is about. -The @command{guix environment} command provides a convenient way to set up -@dfn{development environments} containing all the dependencies and tools -necessary to work on the software package of your choice. The @command{guix +The @command{guix shell} command provides a convenient way to set up +one-off software environments, be it for development purposes or to run +a command without installing it in your profile. The @command{guix pack} command allows you to create @dfn{application bundles} that can be easily distributed to users who do not run Guix. @menu +* Invoking guix shell:: Spawning one-off software environments. * Invoking guix environment:: Setting up development environments. * Invoking guix pack:: Creating software bundles. * The GCC toolchain:: Working with languages supported by GCC. * Invoking guix git authenticate:: Authenticating Git repositories. @end menu -@node Invoking guix environment -@section Invoking @command{guix environment} +@node Invoking guix shell +@section Invoking @command{guix shell} @cindex reproducible build environments @cindex development environments @cindex @command{guix environment} @cindex environment, package build environment -The purpose of @command{guix environment} is to assist hackers in -creating reproducible development environments without polluting their -package profile. The @command{guix environment} tool takes one or more -packages, builds all of their inputs, and creates a shell -environment to use them. +The purpose of @command{guix shell} is to make it easy to create one-off +software environments, without changing one's profile. It is typically +used to create development environments; it is also a convenient way to +run applications without ``polluting'' your profile. + +@quotation Note +The @command{guix shell} command was recently introduced to supersede +@command{guix environment} (@pxref{Invoking guix environment}). If you +are familiar with @command{guix environment}, you will notice that it is +similar but also---we hope!---more convenient. +@end quotation + +The general syntax is: + +@example +guix shell [@var{options}] [@var{package}@dots{}] +@end example + +The following example creates an environment containing Python and NumPy, +building or downloading any missing package, and runs the +@command{python3} command in that environment: + +@example +guix shell python python-numpy -- python3 +@end example + +Development environments can be created as in the example below, which +spawns an interactive shell containing all the dependencies and +environment variables needed to work on Inkscape: + +@example +guix shell --development inkscape +@end example + +Exiting the shell places the user back in the original environment +before @command{guix shell} was invoked. The next garbage collection +(@pxref{Invoking guix gc}) may clean up packages that were installed in +the environment and that are no longer used outside of it. + +By default, the shell session or command runs in an @emph{augmented} +environment, where the new packages are added to search path environment +variables such as @code{PATH}. You can, instead, choose to create an +@emph{isolated} environment containing nothing but the packages you +asked for. Passing the @option{--pure} option clears environment +variable definitions found in the parent environment@footnote{Users +sometimes wrongfully augment environment variables such as @env{PATH} in +their @file{~/.bashrc} file. As a consequence, when @command{guix +environment} launches it, Bash may read @file{~/.bashrc}, thereby +introducing ``impurities'' in these environment variables. It is an +error to define such environment variables in @file{.bashrc}; instead, +they should be defined in @file{.bash_profile}, which is sourced only by +log-in shells. @xref{Bash Startup Files,,, bash, The GNU Bash Reference +Manual}, for details on Bash start-up files.}; passing +@option{--container} goes one step further by spawning a @dfn{container} +isolated from the rest of the system: + +@example +guix shell --container emacs gcc-toolchain +@end example + +The command above spawns an interactive shell in a container when +nothing but @code{emacs}, @code{gcc-toolchain}, and their dependencies +is available. The container lacks network access and shares no files +other than the current working directory with the surrounding +environment. This is useful to prevent access to system-wide resources +such as @file{/usr/bin} on foreign distros. + +This @option{--container} option can also prove useful if you wish to +run a security-sensitive application, such as a web browser, in an +isolated environment. For example, the command below launches +Ungoogled-Chromium in an isolated environment, this time sharing network +access with the host and preserving its @code{DISPLAY} environment +variable, but without even sharing the current directory: + +@example +guix shell --container --network --no-cwd ungoogled-chromium \ + --preserve='^DISPLAY$' -- chromium +@end example + +@vindex GUIX_ENVIRONMENT +@command{guix shell} defines the @env{GUIX_ENVIRONMENT} +variable in the shell it spawns; its value is the file name of the +profile of this environment. This allows users to, say, define a +specific prompt for development environments in their @file{.bashrc} +(@pxref{Bash Startup Files,,, bash, The GNU Bash Reference Manual}): + +@example +if [ -n "$GUIX_ENVIRONMENT" ] +then + export PS1="\u@@\h \w [dev]\$ " +fi +@end example + +@noindent +...@: or to browse the profile: + +@example +$ ls "$GUIX_ENVIRONMENT/bin" +@end example + +The available options are summarized below. + +@table @code +@item --development +@itemx -D +Cause @command{guix shell} to include in the environment the +dependencies of the following package rather than the package itself. +This can be combined with other packages. For instance, the command +below starts an interactive shell containing the build-time dependencies +of GNU@tie{}Guile, plus Autoconf, Automake, and Libtool: + +@example +guix shell -D guile autoconf automake libtool +@end example + +@item --expression=@var{expr} +@itemx -e @var{expr} +Create an environment for the package or list of packages that +@var{expr} evaluates to. + +For example, running: + +@example +guix shell -D -e '(@@ (gnu packages maths) petsc-openmpi)' +@end example + +starts a shell with the environment for this specific variant of the +PETSc package. + +Running: + +@example +guix shell -e '(@@ (gnu) %base-packages)' +@end example + +starts a shell with all the base system packages available. + +The above commands only use the default output of the given packages. +To select other outputs, two element tuples can be specified: + +@example +guix shell -e '(list (@@ (gnu packages bash) bash) "include")' +@end example + +@item --file=@var{file} +@itemx -f @var{file} +Create an environment containing the package or list of packages that +the code within @var{file} evaluates to. + +As an example, @var{file} might contain a definition like this +(@pxref{Defining Packages}): + +@lisp +@verbatiminclude environment-gdb.scm +@end lisp + +With the file above, you can enter a development environment for GDB by +running: + +@example +guix shell -D -f gdb-devel.scm +@end example + +@item --manifest=@var{file} +@itemx -m @var{file} +Create an environment for the packages contained in the manifest object +returned by the Scheme code in @var{file}. This option can be repeated +several times, in which case the manifests are concatenated. + +This is similar to the same-named option in @command{guix package} +(@pxref{profile-manifest, @option{--manifest}}) and uses the same +manifest files. + +@item --pure +Unset existing environment variables when building the new environment, except +those specified with @option{--preserve} (see below). This has the effect of +creating an environment in which search paths only contain package inputs. + +@item --preserve=@var{regexp} +@itemx -E @var{regexp} +When used alongside @option{--pure}, preserve the environment variables +matching @var{regexp}---in other words, put them on a ``white list'' of +environment variables that must be preserved. This option can be repeated +several times. + +@example +guix shell --pure --preserve=^SLURM openmpi @dots{} \ + -- mpirun @dots{} +@end example + +This example runs @command{mpirun} in a context where the only environment +variables defined are @env{PATH}, environment variables whose name starts +with @samp{SLURM}, as well as the usual ``precious'' variables (@env{HOME}, +@env{USER}, etc.). + +@item --search-paths +Display the environment variable definitions that make up the +environment. + +@item --system=@var{system} +@itemx -s @var{system} +Attempt to build for @var{system}---e.g., @code{i686-linux}. + +@item --container +@itemx -C +@cindex container +Run @var{command} within an isolated container. The current working +directory outside the container is mapped inside the container. +Additionally, unless overridden with @option{--user}, a dummy home +directory is created that matches the current user's home directory, and +@file{/etc/passwd} is configured accordingly. + +The spawned process runs as the current user outside the container. Inside +the container, it has the same UID and GID as the current user, unless +@option{--user} is passed (see below). + +@item --network +@itemx -N +For containers, share the network namespace with the host system. +Containers created without this flag only have access to the loopback +device. + +@item --link-profile +@itemx -P +For containers, link the environment profile to @file{~/.guix-profile} +within the container and set @code{GUIX_ENVIRONMENT} to that. +This is equivalent to making @file{~/.guix-profile} a symlink to the +actual profile within the container. +Linking will fail and abort the environment if the directory already +exists, which will certainly be the case if @command{guix shell} +was invoked in the user's home directory. + +Certain packages are configured to look in @file{~/.guix-profile} for +configuration files and data;@footnote{For example, the +@code{fontconfig} package inspects @file{~/.guix-profile/share/fonts} +for additional fonts.} @option{--link-profile} allows these programs to +behave as expected within the environment. + +@item --user=@var{user} +@itemx -u @var{user} +For containers, use the username @var{user} in place of the current +user. The generated @file{/etc/passwd} entry within the container will +contain the name @var{user}, the home directory will be +@file{/home/@var{user}}, and no user GECOS data will be copied. Furthermore, +the UID and GID inside the container are 1000. @var{user} +need not exist on the system. + +Additionally, any shared or exposed path (see @option{--share} and +@option{--expose} respectively) whose target is within the current user's +home directory will be remapped relative to @file{/home/USER}; this +includes the automatic mapping of the current working directory. + +@example +# will expose paths as /home/foo/wd, /home/foo/test, and /home/foo/target +cd $HOME/wd +guix shell --container --user=foo \ + --expose=$HOME/test \ + --expose=/tmp/target=$HOME/target +@end example + +While this will limit the leaking of user identity through home paths +and each of the user fields, this is only one useful component of a +broader privacy/anonymity solution---not one in and of itself. + +@item --no-cwd +For containers, the default behavior is to share the current working +directory with the isolated container and immediately change to that +directory within the container. If this is undesirable, +@option{--no-cwd} will cause the current working directory to @emph{not} +be automatically shared and will change to the user's home directory +within the container instead. See also @option{--user}. + +@item --expose=@var{source}[=@var{target}] +@itemx --share=@var{source}[=@var{target}] +For containers, @option{--expose} (resp. @option{--share}) exposes the +file system @var{source} from the host system as the read-only +(resp. writable) file system @var{target} within the container. If +@var{target} is not specified, @var{source} is used as the target mount +point in the container. + +The example below spawns a Guile REPL in a container in which the user's +home directory is accessible read-only via the @file{/exchange} +directory: + +@example +guix shell --container --expose=$HOME=/exchange guile -- guile +@end example + +@item --root=@var{file} +@itemx -r @var{file} +@cindex persistent environment +@cindex garbage collector root, for environments +Make @var{file} a symlink to the profile for this environment, and +register it as a garbage collector root. + +This is useful if you want to protect your environment from garbage +collection, to make it ``persistent''. + +When this option is omitted, the environment is protected from garbage +collection only for the duration of the @command{guix shell} +session. This means that next time you recreate the same environment, +you could have to rebuild or re-download packages. @xref{Invoking guix +gc}, for more on GC roots. +@end table + +@command{guix shell} also supports all of the common build options that +@command{guix build} supports (@pxref{Common Build Options}) as well as +package transformation options (@pxref{Package Transformation Options}). + +@node Invoking guix environment +@section Invoking @command{guix environment} + +The purpose of @command{guix environment} is to assist in creating +development environments. + +@quotation Deprecation warning +The @command{guix environment} command is deprecated in favor of +@command{guix shell}, which performs similar functions but is more +convenient to use. @xref{Invoking guix shell}. + +Being deprecated, @command{guix environment} is slated for eventual +removal, but the Guix project is committed to keeping it until May 1st, +2023. Please get in touch with us at @email{guix-devel@@gnu.org} if you +would like to discuss it. +@end quotation The general syntax is: @@ -11099,14 +11422,14 @@ a container similar to the one the build daemon creates: $ guix build -K foo @dots{} $ cd /tmp/guix-build-foo.drv-0 -$ guix environment --no-grafts -C foo --ad-hoc strace gdb +$ guix shell --no-grafts -C foo strace gdb [env]# source ./environment-variables [env]# cd foo-1.2 @end example -Here, @command{guix environment -C} creates a container and spawns a new -shell in it (@pxref{Invoking guix environment}). The @command{--ad-hoc -strace gdb} part adds the @command{strace} and @command{gdb} commands to +Here, @command{guix shell -C} creates a container and spawns a new +shell in it (@pxref{Invoking guix shell}). The @command{strace gdb} +part adds the @command{strace} and @command{gdb} commands to the container, which you may find handy while debugging. The @option{--no-grafts} option makes sure we get the exact same environment, with ungrafted packages (@pxref{Security Updates}, for more @@ -11120,7 +11443,7 @@ remove @file{/bin/sh}: @end example (Don't worry, this is harmless: this is all happening in the throw-away -container created by @command{guix environment}.) +container created by @command{guix shell}.) The @command{strace} command is probably not in the search path, but we can run: @@ -13316,8 +13639,8 @@ is subject to radical change in the future. The purpose of @command{guix container} is to manipulate processes running within an isolated environment, commonly known as a -``container'', typically created by the @command{guix environment} -(@pxref{Invoking guix environment}) and @command{guix system container} +``container'', typically created by the @command{guix shell} +(@pxref{Invoking guix shell}) and @command{guix system container} (@pxref{Invoking guix system}) commands. The general syntax is: @@ -13503,7 +13826,7 @@ listed.}. Here's an example of the information it returns: $ sudo guix processes SessionPID: 19002 ClientPID: 19090 -ClientCommand: guix environment --ad-hoc python +ClientCommand: guix shell python SessionPID: 19402 ClientPID: 19367 @@ -29699,8 +30022,7 @@ When the service is running, you can view its console by connecting to it with a VNC client, for example with: @example -guix environment --ad-hoc tigervnc-client -- \ - vncviewer localhost:5900 +guix shell tigervnc-client -- vncviewer localhost:5900 @end example The default configuration (see @code{hurd-vm-configuration} below) diff --git a/etc/completion/bash/guix b/etc/completion/bash/guix index 87d9911e53..ec6adfdb22 100644 --- a/etc/completion/bash/guix +++ b/etc/completion/bash/guix @@ -1,5 +1,5 @@ # GNU Guix --- Functional package management for GNU -# Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès +# Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès # Copyright © 2021 Tobias Geerinck-Rice # # This file is part of GNU Guix. @@ -215,7 +215,7 @@ _guix_complete () if [[ "$word" = "--" ]] then case "$command" in - environment) + environment|shell) break ;; time-machine) @@ -265,7 +265,7 @@ _guix_complete () else _guix_complete_available_package_or_store_file "$word_at_point" fi - elif [[ "$command" = "environment" ]] + elif [[ "$command" = "environment" || "$command" = "shell" ]] then if _guix_is_dash_L || _guix_is_dash_m || _guix_is_dash_p || _guix_is_dash_l then diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 54f48a7482..77956fc018 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -50,7 +50,11 @@ #:use-module (srfi srfi-37) #:use-module (srfi srfi-98) #:export (assert-container-features - guix-environment)) + guix-environment + guix-environment* + show-environment-options-help + (%options . %environment-options) + (%default-options . %environment-default-options))) (define %default-shell (or (getenv "SHELL") "/bin/sh")) @@ -66,23 +70,16 @@ do not augment existing environment variables with additional search paths." (newline))) (profile-search-paths profile manifest))) -(define (show-help) - (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...] -Build an environment that includes the dependencies of PACKAGE and execute -COMMAND or an interactive shell in that environment.\n")) +(define (show-environment-options-help) + "Print help about options shared between 'guix environment' and 'guix +shell'." (display (G_ " -e, --expression=EXPR create environment for the package that EXPR evaluates to")) (display (G_ " - -l, --load=FILE create environment for the package that the code within - FILE evaluates to")) - (display (G_ " -m, --manifest=FILE create environment with the manifest from FILE")) (display (G_ " -p, --profile=PATH create environment from profile at PATH")) - (display (G_ " - --ad-hoc include all specified packages in the environment instead - of only their inputs")) (display (G_ " --pure unset existing environment variables")) (display (G_ " @@ -118,7 +115,24 @@ COMMAND or an interactive shell in that environment.\n")) (display (G_ " -v, --verbosity=LEVEL use the given verbosity LEVEL")) (display (G_ " - --bootstrap use bootstrap binaries to build the environment")) + --bootstrap use bootstrap binaries to build the environment"))) + +(define (show-help) + (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...] +Build an environment that includes the dependencies of PACKAGE and execute +COMMAND or an interactive shell in that environment.\n")) + (warning (G_ "This command is deprecated in favor of 'guix shell'.\n")) + (newline) + + ;; These two options are left out in 'guix shell'. + (display (G_ " + -l, --load=FILE create environment for the package that the code within + FILE evaluates to")) + (display (G_ " + --ad-hoc include all specified packages in the environment instead + of only their inputs")) + + (show-environment-options-help) (newline) (show-build-options-help) (newline) @@ -649,11 +663,15 @@ message if any test fails." (define-command (guix-environment . args) (category development) - (synopsis "spawn one-off software environments") + (synopsis "spawn one-off software environments (deprecated)") + + (guix-environment* (parse-args args))) +(define (guix-environment* opts) + "Run the 'guix environment' command on OPTS, an alist resulting for +command-line option processing with 'parse-command-line'." (with-error-handling - (let* ((opts (parse-args args)) - (pure? (assoc-ref opts 'pure)) + (let* ((pure? (assoc-ref opts 'pure)) (container? (assoc-ref opts 'container?)) (link-prof? (assoc-ref opts 'link-profile?)) (network? (assoc-ref opts 'network?)) @@ -724,8 +742,8 @@ message if any test fails." (prof-drv (manifest->derivation manifest system bootstrap?)) (profile -> (if profile - (readlink* profile) - (derivation->output-path prof-drv))) + (readlink* profile) + (derivation->output-path prof-drv))) (gc-root -> (assoc-ref opts 'gc-root))) ;; First build the inputs. This is necessary even for diff --git a/guix/scripts/shell.scm b/guix/scripts/shell.scm new file mode 100644 index 0000000000..190dd8837d --- /dev/null +++ b/guix/scripts/shell.scm @@ -0,0 +1,135 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Ludovic Courtès +;;; +;;; 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 . + +(define-module (guix scripts shell) + #:use-module (guix ui) + #:use-module (guix scripts environment) + #:autoload (guix scripts build) (show-build-options-help) + #:autoload (guix transformations) (show-transformation-options-help) + #:use-module (guix scripts) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-37) + #:use-module (srfi srfi-71) + #:use-module (ice-9 match) + #:export (guix-shell)) + +(define (show-help) + (display (G_ "Usage: guix shell [OPTION] PACKAGES... [-- COMMAND...] +Build an environment that includes PACKAGES and execute COMMAND or an +interactive shell in that environment.\n")) + (newline) + + ;; These two options differ from 'guix environment'. + (display (G_ " + -D, --development include the development inputs of the next package")) + (display (G_ " + -f, --file=FILE create environment for the package that the code within + FILE evaluates to")) + + (show-environment-options-help) + (newline) + (show-build-options-help) + (newline) + (show-transformation-options-help) + (newline) + (display (G_ " + -h, --help display this help and exit")) + (display (G_ " + -V, --version display version information and exit")) + (newline) + (show-bug-report-information)) + +(define (tag-package-arg opts arg) + "Return a two-element list with the form (TAG ARG) that tags ARG with either +'ad-hoc' in OPTS has the 'ad-hoc?' key set to #t, or 'inputs' otherwise." + (if (assoc-ref opts 'ad-hoc?) + `(ad-hoc-package ,arg) + `(package ,arg))) + +(define (ensure-ad-hoc alist) + (if (assq-ref alist 'ad-hoc?) + alist + `((ad-hoc? . #t) ,@alist))) + +(define (wrapped-option opt) + "Wrap OPT, a SRFI-37 option, such that its processor always adds the +'ad-hoc?' flag to the resulting alist." + (option (option-names opt) + (option-required-arg? opt) + (option-optional-arg? opt) + (compose ensure-ad-hoc (option-processor opt)))) + +(define %options + ;; Specification of the command-line options. + (let ((to-remove '("ad-hoc" "inherit" "load" "help" "version"))) + (append + (list (option '(#\h "help") #f #f + (lambda args + (show-help) + (exit 0))) + (option '(#\V "version") #f #f + (lambda args + (show-version-and-exit "guix shell"))) + + (option '(#\D "development") #f #f + (lambda (opt name arg result) + ;; Temporarily remove the 'ad-hoc?' flag from result. + ;; The next option will put it back thanks to + ;; 'wrapped-option'. + (alist-delete 'ad-hoc? result))) + + ;; For consistency with 'guix package', support '-f' rather than + ;; '-l' like 'guix environment' does. + (option '(#\f "file") #t #f + (lambda (opt name arg result) + (alist-cons 'load (tag-package-arg result arg) + result)))) + (filter-map (lambda (opt) + (and (not (any (lambda (name) + (member name to-remove)) + (option-names opt))) + (wrapped-option opt))) + %environment-options)))) + +(define %default-options + `((ad-hoc? . #t) ;always true + ,@%environment-default-options)) + +(define (parse-args args) + "Parse the list of command line arguments ARGS." + (define (handle-argument arg result) + (alist-cons 'package (tag-package-arg result arg) + (ensure-ad-hoc result))) + + ;; The '--' token is used to separate the command to run from the rest of + ;; the operands. + (let ((args command (break (cut string=? "--" <>) args))) + (let ((opts (parse-command-line args %options (list %default-options) + #:argument-handler handle-argument))) + (match command + (() opts) + (("--") opts) + (("--" command ...) (alist-cons 'exec command opts)))))) + + +(define-command (guix-shell . args) + (category development) + (synopsis "spawn one-off software environments") + + (guix-environment* (parse-args args))) diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in index 5e77b3c230..ee0adbcf3c 100644 --- a/po/guix/POTFILES.in +++ b/po/guix/POTFILES.in @@ -140,5 +140,6 @@ guix/scripts/offload.scm guix/scripts/perform-download.scm guix/scripts/refresh.scm guix/scripts/repl.scm +guix/scripts/shell.scm guix/scripts/system/reconfigure.scm nix/nix-daemon/guix-daemon.cc diff --git a/tests/guix-shell.sh b/tests/guix-shell.sh new file mode 100644 index 0000000000..f08637f7ff --- /dev/null +++ b/tests/guix-shell.sh @@ -0,0 +1,54 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2021 Ludovic Courtès +# +# 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 . + +# +# Test the 'guix shell' alias. +# + +guix shell --version + +tmpdir="t-guix-shell-$$" +trap 'rm -r "$tmpdir"' EXIT +mkdir "$tmpdir" + +guix shell --bootstrap --pure guile-bootstrap -- guile --version + +# '--ad-hoc' is a thing of the past. +! guix shell --ad-hoc guile-bootstrap + +if guile -c '(getaddrinfo "www.gnu.org" "80" AI_NUMERICSERV)' 2> /dev/null +then + # Compute the build environment for the initial GNU Make. + guix shell --bootstrap --no-substitutes --search-paths --pure \ + -D -e '(@ (guix tests) gnu-make-for-tests)' > "$tmpdir/a" + + # Make sure bootstrap binaries are in the profile. + profile=`grep "^export PATH" "$tmpdir/a" | sed -r 's|^.*="(.*)/bin"|\1|'` + + # Make sure the bootstrap binaries are all listed where they belong. + grep -E "^export PATH=\"$profile/bin\"" "$tmpdir/a" + grep -E "^export CPATH=\"$profile/include\"" "$tmpdir/a" + grep -E "^export LIBRARY_PATH=\"$profile/lib\"" "$tmpdir/a" + for dep in bootstrap-binaries-0 gcc-bootstrap-0 glibc-bootstrap-0 + do + guix gc --references "$profile" | grep "$dep" + done + + # 'make-boot0' itself must not be listed. + ! guix gc --references "$profile" | grep make-boot0 +fi -- cgit v1.2.3 From 746584e0ca200e7bf51b139ceb36c19ea81d6ef1 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 17:18:43 +0200 Subject: shell: By default load the local 'manifest.scm' or 'guix.scm' file. * guix/scripts/shell.scm (parse-args): Add call to 'auto-detect-manifest'. (authorized-directory-file, authorized-shell-directory?) (find-file-in-parent-directories, auto-detect-manifest): New procedures. * tests/guix-shell.sh: Add test. * doc/guix.texi (Invoking guix shell): Document it. --- doc/guix.texi | 14 ++++++ guix/scripts/shell.scm | 119 ++++++++++++++++++++++++++++++++++++++++++++++--- tests/guix-shell.sh | 50 ++++++++++++++++++++- 3 files changed, 176 insertions(+), 7 deletions(-) (limited to 'guix/scripts') diff --git a/doc/guix.texi b/doc/guix.texi index f5bfb59229..5809bbacd4 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5621,6 +5621,20 @@ before @command{guix shell} was invoked. The next garbage collection (@pxref{Invoking guix gc}) may clean up packages that were installed in the environment and that are no longer used outside of it. +As an added convenience, when running from a directory that contains a +@file{manifest.scm} or a @file{guix.scm} file (in this order), possibly +in a parent directory, @command{guix shell} automatically loads the +file---provided the directory is listed in +@file{~/.config/guix/shell-authorized-directories}, and only for +interactive use: + +@example +guix shell +@end example + +This provides an easy way to define, share, and enter development +environments. + By default, the shell session or command runs in an @emph{augmented} environment, where the new packages are added to search path environment variables such as @code{PATH}. You can, instead, choose to create an diff --git a/guix/scripts/shell.scm b/guix/scripts/shell.scm index 190dd8837d..bee3192a9d 100644 --- a/guix/scripts/shell.scm +++ b/guix/scripts/shell.scm @@ -18,15 +18,20 @@ (define-module (guix scripts shell) #:use-module (guix ui) + #:use-module ((guix diagnostics) #:select (location)) #:use-module (guix scripts environment) #:autoload (guix scripts build) (show-build-options-help) #:autoload (guix transformations) (show-transformation-options-help) #:use-module (guix scripts) + #:use-module (guix packages) + #:use-module (guix profiles) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:use-module (srfi srfi-37) #:use-module (srfi srfi-71) #:use-module (ice-9 match) + #:autoload (ice-9 rdelim) (read-line) + #:autoload (guix utils) (config-directory) #:export (guix-shell)) (define (show-help) @@ -41,6 +46,8 @@ interactive shell in that environment.\n")) (display (G_ " -f, --file=FILE create environment for the package that the code within FILE evaluates to")) + (display (G_ " + -q inhibit loading of 'guix.scm' and 'manifest.scm'")) (show-environment-options-help) (newline) @@ -99,7 +106,10 @@ interactive shell in that environment.\n")) (option '(#\f "file") #t #f (lambda (opt name arg result) (alist-cons 'load (tag-package-arg result arg) - result)))) + result))) + (option '(#\q) #f #f + (lambda (opt name arg result) + (alist-cons 'explicit-loading? #t result)))) (filter-map (lambda (opt) (and (not (any (lambda (name) (member name to-remove)) @@ -122,10 +132,109 @@ interactive shell in that environment.\n")) (let ((args command (break (cut string=? "--" <>) args))) (let ((opts (parse-command-line args %options (list %default-options) #:argument-handler handle-argument))) - (match command - (() opts) - (("--") opts) - (("--" command ...) (alist-cons 'exec command opts)))))) + (auto-detect-manifest + (match command + (() opts) + (("--") opts) + (("--" command ...) (alist-cons 'exec command opts))))))) + +(define (find-file-in-parent-directories candidates) + "Find one of CANDIDATES in the current directory or one of its ancestors." + (define start (getcwd)) + (define device (stat:dev (stat start))) + + (let loop ((directory start)) + (let ((stat (stat directory))) + (and (= (stat:uid stat) (getuid)) + (= (stat:dev stat) device) + (or (any (lambda (candidate) + (let ((candidate (string-append directory "/" candidate))) + (and (file-exists? candidate) candidate))) + candidates) + (and (not (string=? directory "/")) + (loop (dirname directory)))))))) ;lexical ".." resolution + +(define (authorized-directory-file) + "Return the name of the file listing directories for which 'guix shell' may +automatically load 'guix.scm' or 'manifest.scm' files." + (string-append (config-directory) "/shell-authorized-directories")) + +(define (authorized-shell-directory? directory) + "Return true if DIRECTORY is among the authorized directories for automatic +loading. The list of authorized directories is read from +'authorized-directory-file'; each line must be either: an absolute file name, +a hash-prefixed comment, or a blank line." + (catch 'system-error + (lambda () + (call-with-input-file (authorized-directory-file) + (lambda (port) + (let loop () + (match (read-line port) + ((? eof-object?) #f) + ((= string-trim line) + (cond ((string-prefix? "#" line) ;comment + (loop)) + ((string-prefix? "/" line) ;absolute file name + (or (string=? line directory) + (loop))) + ((string-null? (string-trim-right line)) ;blank line + (loop)) + (else ;bogus line + (let ((loc (location (port-filename port) + (port-line port) + (port-column port)))) + (warning loc (G_ "ignoring invalid file name: '~a'~%") + line)))))))))) + (const #f))) + +(define (auto-detect-manifest opts) + "If OPTS do not specify packages or a manifest, load a \"guix.scm\" or +\"manifest.scm\" file from the current directory or one of its ancestors. +Return the modified OPTS." + (define (options-contain-payload? opts) + (match opts + (() #f) + ((('package . _) . _) #t) + ((('load . _) . _) #t) + ((('manifest . _) . _) #t) + ((('expression . _) . _) #t) + ((_ . rest) (options-contain-payload? rest)))) + + (define interactive? + (not (assoc-ref opts 'exec))) + + (define disallow-implicit-load? + (assoc-ref opts 'explicit-loading?)) + + (if (or (not interactive?) + disallow-implicit-load? + (options-contain-payload? opts)) + opts + (match (find-file-in-parent-directories '("manifest.scm" "guix.scm")) + (#f + (warning (G_ "no packages specified; creating an empty environment~%")) + opts) + (file + (if (authorized-shell-directory? (dirname file)) + (begin + (info (G_ "loading environment from '~a'...~%") file) + (match (basename file) + ("guix.scm" (alist-cons 'load `(package ,file) opts)) + ("manifest.scm" (alist-cons 'manifest file opts)))) + (begin + (warning (G_ "not loading '~a' because not authorized to do so~%") + file) + (display-hint (format #f (G_ "To allow automatic loading of +@file{~a} when running @command{guix shell}, you must explicitly authorize its +directory, like so: + +@example +echo ~a >> ~a +@end example\n") + file + (dirname file) + (authorized-directory-file))) + opts)))))) (define-command (guix-shell . args) diff --git a/tests/guix-shell.sh b/tests/guix-shell.sh index f08637f7ff..95725cba2d 100644 --- a/tests/guix-shell.sh +++ b/tests/guix-shell.sh @@ -22,15 +22,55 @@ guix shell --version +configdir="t-guix-shell-config-$$" tmpdir="t-guix-shell-$$" -trap 'rm -r "$tmpdir"' EXIT -mkdir "$tmpdir" +trap 'rm -r "$tmpdir" "$configdir"' EXIT +mkdir "$tmpdir" "$configdir" "$configdir/guix" + +XDG_CONFIG_HOME="$(realpath $configdir)" +export XDG_CONFIG_HOME guix shell --bootstrap --pure guile-bootstrap -- guile --version # '--ad-hoc' is a thing of the past. ! guix shell --ad-hoc guile-bootstrap +# Ignoring unauthorized files. +cat > "$tmpdir/guix.scm" < "$configdir/guix/shell-authorized-directories" + +# Ignoring 'manifest.scm' and 'guix.scm' in non-interactive use. +(cd "$tmpdir"; guix shell --bootstrap -- true) +mv "$tmpdir/guix.scm" "$tmpdir/manifest.scm" +(cd "$tmpdir"; guix shell --bootstrap -- true) +rm "$tmpdir/manifest.scm" + +# Honoring the local 'manifest.scm' file. +cat > "$tmpdir/manifest.scm" <manifest '("guile-bootstrap")) +EOF +cat > "$tmpdir/fake-shell.sh" < "$tmpdir/manifest.scm" +(cd "$tmpdir"; SHELL="$(realpath fake-shell.sh)" guix shell --bootstrap -q) +rm "$tmpdir/manifest.scm" + if guile -c '(getaddrinfo "www.gnu.org" "80" AI_NUMERICSERV)' 2> /dev/null then # Compute the build environment for the initial GNU Make. @@ -51,4 +91,10 @@ then # 'make-boot0' itself must not be listed. ! guix gc --references "$profile" | grep make-boot0 + + # Honoring the local 'guix.scm' file. + echo '(@ (guix tests) gnu-make-for-tests)' > "$tmpdir/guix.scm" + (cd "$tmpdir"; guix shell --bootstrap --search-paths --pure > "b") + cmp "$tmpdir/a" "$tmpdir/b" + rm "$tmpdir/guix.scm" fi -- cgit v1.2.3 From 648a6eb03fddb6c67f0ec685ac74d2c7a82179cd Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 18:59:54 +0200 Subject: environment: Skip derivation computation when '--profile' is used. * guix/scripts/environment.scm (guix-environment*): Bypass calls to 'package-derivation' and to 'manifest->derivation' when PROFILE is true. --- guix/scripts/environment.scm | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 77956fc018..32f376fdd2 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -729,18 +729,21 @@ command-line option processing with 'parse-command-line'." ;; Use the bootstrap Guile when requested. (parameterize ((%graft? (assoc-ref opts 'graft?)) (%guile-for-build - (package-derivation - store - (if bootstrap? - %bootstrap-guile - (default-guile))))) + (and (or container? (not profile)) + (package-derivation + store + (if bootstrap? + %bootstrap-guile + (default-guile)))))) (run-with-store store ;; Containers need a Bourne shell at /bin/sh. (mlet* %store-monad ((bash (environment-bash container? bootstrap? system)) - (prof-drv (manifest->derivation - manifest system bootstrap?)) + (prof-drv (if profile + (return #f) + (manifest->derivation + manifest system bootstrap?))) (profile -> (if profile (readlink* profile) (derivation->output-path prof-drv))) @@ -750,9 +753,9 @@ command-line option processing with 'parse-command-line'." ;; --search-paths. Additionally, we might need to build bash for ;; a container. (mbegin %store-monad - (built-derivations (if (derivation? bash) - (list prof-drv bash) - (list prof-drv))) + (built-derivations (append + (if prof-drv (list prof-drv) '()) + (if (derivation? bash) (list bash) '()))) (mwhen gc-root (register-gc-root profile gc-root)) -- cgit v1.2.3 From 99499a2037b886925bc73dd81340bbf9c02c95c7 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 22:09:51 +0200 Subject: environment: Do not connect to the daemon when '--profile' is used. This further speeds up the 'guix environment -p PROFILE' case. * guix/scripts/environment.scm (guix-environment*)[store-needed?]: New variable. [with-store/maybe]: New macro. Use it instead of 'with-store', and remove 'with-build-handler' form. --- guix/scripts/environment.scm | 183 +++++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 83 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 32f376fdd2..e23d52df39 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -691,6 +691,26 @@ command-line option processing with 'parse-command-line'." (mappings (pick-all opts 'file-system-mapping)) (white-list (pick-all opts 'inherit-regexp))) + (define store-needed? + ;; Whether connecting to the daemon is needed. + (or container? (not profile))) + + (define-syntax-rule (with-store/maybe store exp ...) + ;; Evaluate EXP... with STORE bound to a connection, unless + ;; STORE-NEEDED? is false, in which case STORE is bound to #f. + (let ((proc (lambda (store) exp ...))) + (if store-needed? + (with-store s + (set-build-options-from-command-line s opts) + (with-build-handler (build-notifier #:use-substitutes? + (assoc-ref opts 'substitutes?) + #:verbosity + (assoc-ref opts 'verbosity) + #:dry-run? + (assoc-ref opts 'dry-run?)) + (proc s))) + (proc #f)))) + (when container? (assert-container-features)) (when (and (not container?) link-prof?) @@ -701,88 +721,85 @@ command-line option processing with 'parse-command-line'." (leave (G_ "--no-cwd cannot be used without --container~%"))) - (with-store store - (with-build-handler (build-notifier #:use-substitutes? - (assoc-ref opts 'substitutes?) - #:verbosity - (assoc-ref opts 'verbosity) - #:dry-run? - (assoc-ref opts 'dry-run?)) - (with-status-verbosity (assoc-ref opts 'verbosity) - (define manifest-from-opts - (options/resolve-packages store opts)) - - (define manifest - (if profile - (profile-manifest profile) - manifest-from-opts)) - - (when (and profile - (> (length (manifest-entries manifest-from-opts)) 0)) - (leave (G_ "'--profile' cannot be used with package options~%"))) - - (when (null? (manifest-entries manifest)) - (warning (G_ "no packages specified; creating an empty environment~%"))) - - (set-build-options-from-command-line store opts) - - ;; Use the bootstrap Guile when requested. - (parameterize ((%graft? (assoc-ref opts 'graft?)) - (%guile-for-build - (and (or container? (not profile)) - (package-derivation - store - (if bootstrap? - %bootstrap-guile - (default-guile)))))) - (run-with-store store - ;; Containers need a Bourne shell at /bin/sh. - (mlet* %store-monad ((bash (environment-bash container? - bootstrap? - system)) - (prof-drv (if profile - (return #f) - (manifest->derivation - manifest system bootstrap?))) - (profile -> (if profile - (readlink* profile) - (derivation->output-path prof-drv))) - (gc-root -> (assoc-ref opts 'gc-root))) - - ;; First build the inputs. This is necessary even for - ;; --search-paths. Additionally, we might need to build bash for - ;; a container. - (mbegin %store-monad + (with-store/maybe store + (with-status-verbosity (assoc-ref opts 'verbosity) + (define manifest-from-opts + (options/resolve-packages store opts)) + + (define manifest + (if profile + (profile-manifest profile) + manifest-from-opts)) + + (when (and profile + (> (length (manifest-entries manifest-from-opts)) 0)) + (leave (G_ "'--profile' cannot be used with package options~%"))) + + (when (null? (manifest-entries manifest)) + (warning (G_ "no packages specified; creating an empty environment~%"))) + + ;; Use the bootstrap Guile when requested. + (parameterize ((%graft? (assoc-ref opts 'graft?)) + (%guile-for-build + (and store-needed? + (package-derivation + store + (if bootstrap? + %bootstrap-guile + (default-guile)))))) + (run-with-store store + ;; Containers need a Bourne shell at /bin/sh. + (mlet* %store-monad ((bash (environment-bash container? + bootstrap? + system)) + (prof-drv (if profile + (return #f) + (manifest->derivation + manifest system bootstrap?))) + (profile -> (if profile + (readlink* profile) + (derivation->output-path prof-drv))) + (gc-root -> (assoc-ref opts 'gc-root))) + + ;; First build the inputs. This is necessary even for + ;; --search-paths. Additionally, we might need to build bash for + ;; a container. + (mbegin %store-monad + (mwhen store-needed? (built-derivations (append (if prof-drv (list prof-drv) '()) - (if (derivation? bash) (list bash) '()))) - (mwhen gc-root - (register-gc-root profile gc-root)) - - (cond - ((assoc-ref opts 'search-paths) - (show-search-paths profile manifest #:pure? pure?) - (return #t)) - (container? - (let ((bash-binary - (if bootstrap? - (derivation->output-path bash) - (string-append (derivation->output-path bash) - "/bin/sh")))) - (launch-environment/container #:command command - #:bash bash-binary - #:user user - #:user-mappings mappings - #:profile profile - #:manifest manifest - #:white-list white-list - #:link-profile? link-prof? - #:network? network? - #:map-cwd? (not no-cwd?)))) - - (else - (return - (exit/status - (launch-environment/fork command profile manifest - #:white-list white-list - #:pure? pure?))))))))))))))) + (if (derivation? bash) (list bash) '())))) + (mwhen gc-root + (register-gc-root profile gc-root)) + + (cond + ((assoc-ref opts 'search-paths) + (show-search-paths profile manifest #:pure? pure?) + (return #t)) + (container? + (let ((bash-binary + (if bootstrap? + (derivation->output-path bash) + (string-append (derivation->output-path bash) + "/bin/sh")))) + (launch-environment/container #:command command + #:bash bash-binary + #:user user + #:user-mappings mappings + #:profile profile + #:manifest manifest + #:white-list white-list + #:link-profile? link-prof? + #:network? network? + #:map-cwd? (not no-cwd?)))) + + (else + (return + (exit/status + (launch-environment/fork command profile manifest + #:white-list white-list + #:pure? pure?)))))))))))))) + +;;; Local Variables: +;;; (put 'with-store/maybe 'scheme-indent-function 1) +;;; End: -- cgit v1.2.3 From 3c96158438e3a813fb98f0eba78ee5c3ae93f065 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 22:22:18 +0200 Subject: environment: Autoload some modules. This further speeds up the 'guix environment -p PROFILE' case. * guix/scripts/environment.scm: Autoload a bunch of modules. --- guix/scripts/environment.scm | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index e23d52df39..05a43659da 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -34,15 +34,18 @@ #:use-module (guix scripts) #:use-module (guix scripts build) #:use-module (guix transformations) - #:use-module (gnu build linux-container) - #:use-module (gnu build accounts) - #:use-module ((guix build syscalls) #:select (set-network-interface-up)) - #:use-module (gnu system linux-container) + #:autoload (gnu build linux-container) (call-with-container %namespaces + user-namespace-supported? + unprivileged-user-namespace-supported? + setgroups-supported?) + #:autoload (gnu build accounts) (password-entry group-entry + password-entry-name password-entry-directory + write-passwd write-group) + #:autoload (guix build syscalls) (set-network-interface-up) #:use-module (gnu system file-systems) - #:use-module (gnu packages) - #:use-module (gnu packages bash) - #:use-module ((gnu packages bootstrap) - #:select (bootstrap-executable %bootstrap-guile)) + #:autoload (gnu packages) (specification->package+output) + #:autoload (gnu packages bash) (bash) + #:autoload (gnu packages bootstrap) (bootstrap-executable %bootstrap-guile) #:use-module (ice-9 match) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) -- cgit v1.2.3 From 9730692d9f06f8c412b45ef108006921298983c4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 1 Oct 2021 22:47:33 +0200 Subject: shell: Maintain a profile cache. shell: Maintain a profile cache. With this change, running "guix shell" (no arguments) is equivalent to: guix environment -r ~/.cache/guix/profiles/some-root -l guix.scm This is the cache miss. On cache hit, it's equivalent to: guix environment -p ~/.cache/guix/profiles/some-root ... which can run in 0.1s. * guix/scripts/shell.scm (options-with-caching): New procedure. (parse-args): Use it. (%profile-cache-directory): New variable. (profile-cache-key, profile-cached-gc-root): New procedures. (show-help, %options): Add '--rebuild-cache'. (guix-shell)[cache-entries, entry-expiration]: New procedures. Add call to 'maybe-remove-expired-cache-entries'. * doc/guix.texi (Invoking guix shell): Document '--rebuild-cache'. --- doc/guix.texi | 11 +++++ guix/scripts/shell.scm | 127 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 130 insertions(+), 8 deletions(-) (limited to 'guix/scripts') diff --git a/doc/guix.texi b/doc/guix.texi index 5809bbacd4..093e354509 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5769,6 +5769,17 @@ This is similar to the same-named option in @command{guix package} (@pxref{profile-manifest, @option{--manifest}}) and uses the same manifest files. +@item --rebuild-cache +When using @option{--manifest}, @option{--file}, or when invoked without +arguments, @command{guix shell} caches the environment so that +subsequent uses are instantaneous. The cache is invalidated anytime the +file is modified. + +The @option{--rebuild-cache} forces the cached environment to be +refreshed even if the file has not changed. This is useful if the +@command{guix.scm} or @command{manifest.scm} has external dependencies, +or if its behavior depends, say, on environment variables. + @item --pure Unset existing environment variables when building the new environment, except those specified with @option{--preserve} (see below). This has the effect of diff --git a/guix/scripts/shell.scm b/guix/scripts/shell.scm index bee3192a9d..05b6f98656 100644 --- a/guix/scripts/shell.scm +++ b/guix/scripts/shell.scm @@ -31,7 +31,15 @@ #:use-module (srfi srfi-71) #:use-module (ice-9 match) #:autoload (ice-9 rdelim) (read-line) - #:autoload (guix utils) (config-directory) + #:autoload (guix base32) (bytevector->base32-string) + #:autoload (rnrs bytevectors) (string->utf8) + #:autoload (guix utils) (config-directory cache-directory) + #:autoload (guix describe) (current-channels) + #:autoload (guix channels) (channel-commit) + #:autoload (gcrypt hash) (sha256) + #:use-module ((guix build utils) #:select (mkdir-p)) + #:use-module (guix cache) + #:use-module ((ice-9 ftw) #:select (scandir)) #:export (guix-shell)) (define (show-help) @@ -48,6 +56,8 @@ interactive shell in that environment.\n")) FILE evaluates to")) (display (G_ " -q inhibit loading of 'guix.scm' and 'manifest.scm'")) + (display (G_ " + --rebuild-cache rebuild cached environment, if any")) (show-environment-options-help) (newline) @@ -109,7 +119,10 @@ interactive shell in that environment.\n")) result))) (option '(#\q) #f #f (lambda (opt name arg result) - (alist-cons 'explicit-loading? #t result)))) + (alist-cons 'explicit-loading? #t result))) + (option '("rebuild-cache") #f #f + (lambda (opt name arg result) + (alist-cons 'rebuild-cache? #t result)))) (filter-map (lambda (opt) (and (not (any (lambda (name) (member name to-remove)) @@ -132,11 +145,12 @@ interactive shell in that environment.\n")) (let ((args command (break (cut string=? "--" <>) args))) (let ((opts (parse-command-line args %options (list %default-options) #:argument-handler handle-argument))) - (auto-detect-manifest - (match command - (() opts) - (("--") opts) - (("--" command ...) (alist-cons 'exec command opts))))))) + (options-with-caching + (auto-detect-manifest + (match command + (() opts) + (("--") opts) + (("--" command ...) (alist-cons 'exec command opts)))))))) (define (find-file-in-parent-directories candidates) "Find one of CANDIDATES in the current directory or one of its ancestors." @@ -187,6 +201,53 @@ a hash-prefixed comment, or a blank line." line)))))))))) (const #f))) +(define (options-with-caching opts) + "If OPTS contains exactly one 'load' or one 'manifest' key, automatically +add a 'profile' key (when a profile for that file is already in cache) or a +'gc-root' key (to add the profile to cache)." + (define (single-file-for-caching opts) + (let loop ((opts opts) + (file #f)) + (match opts + (() file) + ((('package . _) . _) #f) + ((('load . ('package candidate)) . rest) + (and (not file) (loop rest candidate))) + ((('manifest . candidate) . rest) + (and (not file) (loop rest candidate))) + ((('expression . _) . _) #f) + ((_ . rest) (loop rest file))))) + + ;; Check whether there's a single 'load' or 'manifest' option. When that is + ;; the case, arrange to automatically cache the resulting profile. + (match (single-file-for-caching opts) + (#f opts) + (file + (let* ((root (profile-cached-gc-root file)) + (stat (and root (false-if-exception (lstat root))))) + (if (and (not (assoc-ref opts 'rebuild-cache?)) + stat + (<= (stat:mtime ((@ (guile) stat) file)) + (stat:mtime stat))) + (let ((now (current-time))) + ;; Update the atime on ROOT to reflect usage. + (utime root + now (stat:mtime stat) 0 (stat:mtimensec stat) + AT_SYMLINK_NOFOLLOW) + (alist-cons 'profile root + (remove (match-lambda + (('load . _) #t) + (('manifest . _) #t) + (_ #f)) + opts))) ;load right away + (if (and root (not (assq-ref opts 'gc-root))) + (begin + (if stat + (delete-file root) + (mkdir-p (dirname root))) + (alist-cons 'gc-root root opts)) + opts)))))) + (define (auto-detect-manifest opts) "If OPTS do not specify packages or a manifest, load a \"guix.scm\" or \"manifest.scm\" file from the current directory or one of its ancestors. @@ -236,9 +297,59 @@ echo ~a >> ~a (authorized-directory-file))) opts)))))) + +;;; +;;; Profile cache. +;;; + +(define %profile-cache-directory + ;; Directory where profiles created by 'guix shell' alone (without extra + ;; options) are cached. + (make-parameter (string-append (cache-directory #:ensure? #f) + "/profiles"))) + +(define (profile-cache-key file) + "Return the cache key for the profile corresponding to FILE, a 'guix.scm' or +'manifest.scm' file, or #f if we lack channel information." + (match (current-channels) + (() #f) + (((= channel-commit commits) ...) + (let ((stat (stat file))) + (bytevector->base32-string + ;; Since FILE is not canonicalized, only include the device/inode + ;; numbers. XXX: In some rare cases involving Btrfs and NFS, this can + ;; be insufficient: . + (sha256 (string->utf8 + (string-append (string-join commits) ":" + (number->string (stat:dev stat)) ":" + (number->string (stat:ino stat)))))))))) + +(define (profile-cached-gc-root file) + "Return the cached GC root for FILE, a 'guix.scm' or 'manifest.scm' file, or +#f if we lack information to cache it." + (match (profile-cache-key file) + (#f #f) + (key (string-append (%profile-cache-directory) "/" key)))) + (define-command (guix-shell . args) (category development) (synopsis "spawn one-off software environments") - (guix-environment* (parse-args args))) + (define (cache-entries directory) + (filter-map (match-lambda + ((or "." "..") #f) + (file (string-append directory "/" file))) + (or (scandir directory) '()))) + + (define* (entry-expiration file) + ;; Return the time at which FILE, a cached profile, is considered expired. + (match (false-if-exception (lstat file)) + (#f 0) ;FILE may have been deleted in the meantime + (st (+ (stat:atime st) (* 60 60 24 7))))) + + (let ((result (guix-environment* (parse-args args)))) + (maybe-remove-expired-cache-entries (%profile-cache-directory) + cache-entries + #:entry-expiration entry-expiration) + result)) -- cgit v1.2.3 From 9e46942c1c8e719af97026d89d64b25da759eff8 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 19 Oct 2021 11:17:24 +0200 Subject: environment: Add '--check'. * guix/scripts/environment.scm (show-environment-options-help) (%options): Add '--check'. * guix/scripts/environment.scm (child-shell-environment) (validate-child-shell-environment): New procedures. (guix-environment*): Call 'validate-child-shell-environment' when 'check?' key is in OPTS. * doc/guix.texi (Invoking guix shell): Shorten footnote about Bash startup files. Document '--check' and mention startup files. (Invoking guix environment): Document '--check'. --- doc/guix.texi | 39 ++++++++--- guix/scripts/environment.scm | 162 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 12 deletions(-) (limited to 'guix/scripts') diff --git a/doc/guix.texi b/doc/guix.texi index 093e354509..22215214e0 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5640,17 +5640,11 @@ environment, where the new packages are added to search path environment variables such as @code{PATH}. You can, instead, choose to create an @emph{isolated} environment containing nothing but the packages you asked for. Passing the @option{--pure} option clears environment -variable definitions found in the parent environment@footnote{Users -sometimes wrongfully augment environment variables such as @env{PATH} in -their @file{~/.bashrc} file. As a consequence, when @command{guix -environment} launches it, Bash may read @file{~/.bashrc}, thereby -introducing ``impurities'' in these environment variables. It is an -error to define such environment variables in @file{.bashrc}; instead, -they should be defined in @file{.bash_profile}, which is sourced only by -log-in shells. @xref{Bash Startup Files,,, bash, The GNU Bash Reference -Manual}, for details on Bash start-up files.}; passing -@option{--container} goes one step further by spawning a @dfn{container} -isolated from the rest of the system: +variable definitions found in the parent environment@footnote{Be sure to +use the @option{--check} option the first time you use @command{guix +shell} interactively to make sure the shell does not undo the effect of +@option{--pure}.}; passing @option{--container} goes one step further by +spawning a @dfn{container} isolated from the rest of the system: @example guix shell --container emacs gcc-toolchain @@ -5699,6 +5693,24 @@ $ ls "$GUIX_ENVIRONMENT/bin" The available options are summarized below. @table @code +@item --check +Set up the environment and check whether the shell would clobber +environment variables. It's a good idea to use this option the first +time you run @command{guix shell} for an interactive session to make +sure your setup is correct. + +For example, if the shell modifies the @env{PATH} environment variable, +report it since you would get a different environment than what you +asked for. + +Such problems usually indicate that the shell startup files are +unexpectedly modifying those environment variables. For example, if you +are using Bash, make sure that environment variables are set or modified +in @file{~/.bash_profile} and @emph{not} in @file{~/.bashrc}---the +former is sourced only by log-in shells. @xref{Bash Startup Files,,, +bash, The GNU Bash Reference Manual}, for details on Bash start-up +files. + @item --development @itemx -D Cause @command{guix shell} to include in the environment the @@ -6065,6 +6077,11 @@ guix environment --preserve='^DISPLAY$' --container --network \ The available options are summarized below. @table @code +@item --check +Set up the environment and check whether the shell would clobber +environment variables. @xref{Invoking guix shell, @option{--check}}, +for more info. + @item --root=@var{file} @itemx -r @var{file} @cindex persistent environment diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 05a43659da..7b97a8e39a 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -41,12 +41,14 @@ #:autoload (gnu build accounts) (password-entry group-entry password-entry-name password-entry-directory write-passwd write-group) - #:autoload (guix build syscalls) (set-network-interface-up) + #:autoload (guix build syscalls) (set-network-interface-up openpty login-tty) #:use-module (gnu system file-systems) #:autoload (gnu packages) (specification->package+output) #:autoload (gnu packages bash) (bash) #:autoload (gnu packages bootstrap) (bootstrap-executable %bootstrap-guile) #:use-module (ice-9 match) + #:autoload (ice-9 rdelim) (read-line) + #:use-module (ice-9 vlist) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) @@ -83,6 +85,8 @@ shell'." -m, --manifest=FILE create environment with the manifest from FILE")) (display (G_ " -p, --profile=PATH create environment from profile at PATH")) + (display (G_ " + --check check if the shell clobbers environment variables")) (display (G_ " --pure unset existing environment variables")) (display (G_ " @@ -178,6 +182,9 @@ COMMAND or an interactive shell in that environment.\n")) (option '(#\V "version") #f #f (lambda args (show-version-and-exit "guix environment"))) + (option '("check") #f #f + (lambda (opt name arg result) + (alist-cons 'check? #t result))) (option '("pure") #f #f (lambda (opt name arg result) (alist-cons 'pure #t result))) @@ -396,6 +403,155 @@ regexps in WHITE-LIST." ((program . args) (apply execlp program program args)))) +(define (child-shell-environment shell profile manifest) + "Create a child process, load PROFILE and MANIFEST, and then run SHELL in +interactive mode in it. Return a name/value vhash for all the variables shown +by running 'set' in the shell." + (define-values (controller inferior) + (openpty)) + + (define script + ;; Script to obtain the list of environment variable values. On a POSIX + ;; shell we can rely on 'set', but on fish we have to use 'env' (fish's + ;; 'set' truncates values and prints them in a different format.) + "env || /usr/bin/env || set; echo GUIX-CHECK-DONE; read x; exit\n") + + (define lines + (match (primitive-fork) + (0 + (catch #t + (lambda () + (load-profile profile manifest #:pure? #t) + (setenv "GUIX_ENVIRONMENT" profile) + (close-fdes controller) + (login-tty inferior) + (execl shell shell)) + (lambda _ + (primitive-exit 127)))) + (pid + (close-fdes inferior) + (let* ((port (fdopen controller "r+l")) + (result (begin + (display script port) + (let loop ((lines '())) + (match (read-line port) + ((? eof-object?) (reverse lines)) + ("GUIX-CHECK-DONE\r" + (display "done\n" port) + (reverse lines)) + (line + ;; Drop the '\r' from LINE. + (loop (cons (string-drop-right line 1) + lines)))))))) + (close-port port) + (waitpid pid) + result)))) + + (fold (lambda (line table) + ;; Note: 'set' in fish outputs "NAME VALUE" instead of "NAME=VALUE" + ;; but it also truncates values anyway, so don't try to support it. + (let ((index (string-index line #\=))) + (if index + (vhash-cons (string-take line index) + (string-drop line (+ 1 index)) + table) + table))) + vlist-null + lines)) + +(define* (validate-child-shell-environment profile manifest + #:optional (shell %default-shell)) + "Run SHELL in interactive mode in an environment for PROFILE and MANIFEST +and report clobbered environment variables." + (define warned? #f) + (define-syntax-rule (warn exp ...) + (begin + (set! warned? #t) + (warning exp ...))) + + (info (G_ "checking the environment variables visible from shell '~a'...~%") + shell) + (let ((actual (child-shell-environment shell profile manifest))) + (when (vlist-null? actual) + (leave (G_ "failed to determine environment of shell '~a'~%") + shell)) + (for-each (match-lambda + ((spec . expected) + (let ((name (search-path-specification-variable spec))) + (match (vhash-assoc name actual) + (#f + (warn (G_ "variable '~a' is missing from shell \ +environment~%") + name)) + ((_ . actual) + (cond ((string=? expected actual) + #t) + ((string-prefix? expected actual) + (warn (G_ "variable '~a' has unexpected \ +suffix '~a'~%") + name + (string-drop actual + (string-length expected)))) + (else + (warn (G_ "variable '~a' is clobbered: '~a'~%") + name actual)))))))) + (profile-search-paths profile manifest)) + + ;; Special case. + (match (vhash-assoc "GUIX_ENVIRONMENT" actual) + (#f + (warn (G_ "'GUIX_ENVIRONMENT' is missing from the shell \ +environment~%"))) + ((_ . value) + (unless (string=? value profile) + (warn (G_ "'GUIX_ENVIRONMENT' is set to '~a' instead of '~a'~%") + value profile)))) + + ;; Check the prompt unless we have more important warnings. + (unless warned? + (match (vhash-assoc "PS1" actual) + (#f #f) + (str + (when (and (getenv "PS1") (string=? str (getenv "PS1"))) + (warning (G_ "'PS1' is the same in sub-shell~%")) + (display-hint (G_ "Consider setting a different prompt for +environment shells to make them distinguishable. + +If you are using Bash, you can do that by adding these lines to +@file{~/.bashrc}: + +@example +if [ -n \"$GUIX_ENVIRONMENT\" ] +then + export PS1=\"\\u@@\\h \\w [env]\\$ \" +fi +@end example +")))))) + + (if warned? + (begin + (display-hint (G_ "One or more environment variables have a +different value in the shell than the one we set. This means that you may +find yourself running code in an environment different from the one you asked +Guix to prepare. + +This usually indicates that your shell startup files are unexpectedly +modifying those environment variables. For example, if you are using Bash, +make sure that environment variables are set or modified in +@file{~/.bash_profile} and @emph{not} in @file{~/.bashrc}. For more +information on Bash startup files, run: + +@example +info \"(bash) Bash Startup Files\" +@end example + +Alternatively, you can avoid the problem by passing the @option{--container} +or @option{-C} option. That will give you a fully isolated environment +running in a \"container\", immune to the issue described above.")) + (exit 1)) + (info (G_ "All is good! The shell gets correct environment \ +variables.~%"))))) + (define* (launch-environment/fork command profile manifest #:key pure? (white-list '())) "Run COMMAND in a new process with an environment containing PROFILE, with @@ -775,6 +931,10 @@ command-line option processing with 'parse-command-line'." (mwhen gc-root (register-gc-root profile gc-root)) + (mwhen (assoc-ref opts 'check?) + (return + (validate-child-shell-environment profile manifest))) + (cond ((assoc-ref opts 'search-paths) (show-search-paths profile manifest #:pure? pure?) -- cgit v1.2.3 From 409f538d651a7ba26a41f714915e6b7d59e0a82f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 19 Oct 2021 11:50:14 +0200 Subject: shell: Suggest running '--check' once for interactive use. * guix/scripts/shell.scm (hint-directory, hint-file, record-hint) (hint-given?): New procedures. (guix-shell): Record and probe the 'shell-check' hint. --- guix/scripts/shell.scm | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'guix/scripts') diff --git a/guix/scripts/shell.scm b/guix/scripts/shell.scm index 05b6f98656..c7eba429d5 100644 --- a/guix/scripts/shell.scm +++ b/guix/scripts/shell.scm @@ -331,6 +331,29 @@ echo ~a >> ~a (#f #f) (key (string-append (%profile-cache-directory) "/" key)))) + +;;; +;;; One-time hints. +;;; + +(define (hint-directory) + "Return the directory name where previously given hints are recorded." + (string-append (cache-directory #:ensure? #f) "/hints")) + +(define (hint-file hint) + "Return the name of the file that marks HINT as already printed." + (string-append (hint-directory) "/" (symbol->string hint))) + +(define (record-hint hint) + "Mark HINT as already given." + (let ((file (hint-file hint))) + (mkdir-p (dirname file)) + (close-fdes (open-fdes file (logior O_CREAT O_WRONLY))))) + +(define (hint-given? hint) + "Return true if HINT was already given." + (file-exists? (hint-file hint))) + (define-command (guix-shell . args) (category development) @@ -348,7 +371,22 @@ echo ~a >> ~a (#f 0) ;FILE may have been deleted in the meantime (st (+ (stat:atime st) (* 60 60 24 7))))) - (let ((result (guix-environment* (parse-args args)))) + (define opts + (parse-args args)) + + (define interactive? + (not (assoc-ref opts 'exec))) + + (if (assoc-ref opts 'check?) + (record-hint 'shell-check) + (when (and interactive? + (not (hint-given? 'shell-check)) + (not (assoc-ref opts 'container?)) + (not (assoc-ref opts 'search-paths))) + (display-hint (G_ "Consider passing the @option{--check} option once +to make sure your shell does not clobber environment variables."))) ) + + (let ((result (guix-environment* opts))) (maybe-remove-expired-cache-entries (%profile-cache-directory) cache-entries #:entry-expiration entry-expiration) -- cgit v1.2.3 From bcc4aef0dbaaf3a66e11e0af30c88e60f8f9d4fb Mon Sep 17 00:00:00 2001 From: Zhu Zihao Date: Tue, 26 Oct 2021 12:10:26 +0800 Subject: environment: Fix broken file-local variable. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/scripts/environment.scm (Local Variables): Add missing eval. Signed-off-by: Ludovic Courtès --- guix/scripts/environment.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix/scripts') diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 7b97a8e39a..cca0ad991b 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -964,5 +964,5 @@ command-line option processing with 'parse-command-line'." #:pure? pure?)))))))))))))) ;;; Local Variables: -;;; (put 'with-store/maybe 'scheme-indent-function 1) +;;; eval: (put 'with-store/maybe 'scheme-indent-function 1) ;;; End: -- cgit v1.2.3 From 98173af5222ab5e879e44ba9521dec5416ba8c60 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 30 Oct 2021 16:35:06 +0200 Subject: shell: Error out when an unauthorized guix.scm/manifest.scm is found. The previous behavior was confusing: a warning would be printed and 'guix shell' would go on starting an empty environment. Reported by Tobias Geerinckx-Rice . * guix/scripts/shell.scm (auto-detect-manifest): Change "not loading" case from warning to error. * tests/guix-shell.sh: Adjust accordingly. --- guix/scripts/shell.scm | 7 ++++--- tests/guix-shell.sh | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/shell.scm b/guix/scripts/shell.scm index c7eba429d5..5749485a44 100644 --- a/guix/scripts/shell.scm +++ b/guix/scripts/shell.scm @@ -283,8 +283,9 @@ Return the modified OPTS." ("guix.scm" (alist-cons 'load `(package ,file) opts)) ("manifest.scm" (alist-cons 'manifest file opts)))) (begin - (warning (G_ "not loading '~a' because not authorized to do so~%") - file) + (report-error + (G_ "not loading '~a' because not authorized to do so~%") + file) (display-hint (format #f (G_ "To allow automatic loading of @file{~a} when running @command{guix shell}, you must explicitly authorize its directory, like so: @@ -295,7 +296,7 @@ echo ~a >> ~a file (dirname file) (authorized-directory-file))) - opts)))))) + (exit 1))))))) ;;; diff --git a/tests/guix-shell.sh b/tests/guix-shell.sh index 95725cba2d..3bdf625189 100644 --- a/tests/guix-shell.sh +++ b/tests/guix-shell.sh @@ -39,7 +39,9 @@ guix shell --bootstrap --pure guile-bootstrap -- guile --version cat > "$tmpdir/guix.scm" < "stderr") +grep "not authorized" "$tmpdir/stderr" +rm "$tmpdir/stderr" # Authorize the directory. echo "$(realpath "$tmpdir")" > "$configdir/guix/shell-authorized-directories" -- cgit v1.2.3 From d5eb05f01ea59da8435e9df4f15835edbb31a30e Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:27 +0200 Subject: guix home: import: Make the user to specify a destination directory. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copy the appropriate the relevant configuration files to the destination directory, and call ‘local-file’ on them. Without this, ‘guix home import’ will generate a service declaration like this (service home-bash-service-type (home-bash-configuration (bashrc (list (slurp-file-gexp (local-file "/home/yoctocell/.bashrc")))))) but when running ‘guix home reconfigure’, the ~/.bashrc file would be moved, so when running ‘guix home reconfigure’ for the second time, it would read the ~/.bashrc which is itself a symlink to a file the store. * guix/scripts/home/import.scm (generate-bash-module+configuration): Take ‘destination-directory’ parameter (modules+configurations): Copy the user’s configuration file to ‘%destination-directory’. * guix/scripts/home.scm (process-command): Adjust accordingly; create ‘destination’ if it doesn’t exist. Signed-off-by: Ludovic Courtès --- guix/scripts/home.scm | 24 ++++++++----- guix/scripts/home/import.scm | 86 +++++++++++++++++++++++++------------------- 2 files changed, 65 insertions(+), 45 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm index 55e7b436c1..3f48b98ed4 100644 --- a/guix/scripts/home.scm +++ b/guix/scripts/home.scm @@ -40,6 +40,7 @@ #:autoload (guix scripts pull) (channel-commit-hyperlink) #:use-module (guix scripts home import) #:use-module ((guix status) #:select (with-status-verbosity)) + #:use-module ((guix build utils) #:select (mkdir-p)) #:use-module (guix gexp) #:use-module (guix monads) #:use-module (srfi srfi-1) @@ -260,15 +261,20 @@ argument list and OPTS is the option alist." (apply search args)) ((import) (let* ((profiles (delete-duplicates - (match (filter-map (match-lambda - (('profile . p) p) - (_ #f)) - opts) - (() (list %current-profile)) - (lst (reverse lst))))) - (manifest (concatenate-manifests - (map profile-manifest profiles)))) - (import-manifest manifest (current-output-port)))) + (match (filter-map (match-lambda + (('profile . p) p) + (_ #f)) + opts) + (() (list %current-profile)) + (lst (reverse lst))))) + (manifest (concatenate-manifests + (map profile-manifest profiles))) + (destination (match args + ((destination) destination) + (_ (leave (G_ "wrong number of arguments~%")))))) + (unless (file-exists? destination) + (mkdir-p destination)) + (import-manifest manifest destination (current-output-port)))) ((describe) (match (generation-number %guix-home) (0 diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index 611f580e85..c7c60e95e8 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -36,49 +36,61 @@ ;;; ;;; Code: +(define (generate-bash-configuration+modules destination-directory) + (define (destination-append path) + (string-append destination-directory "/" path)) -(define (generate-bash-module+configuration) - (let ((rc (string-append (getenv "HOME") "/.bashrc")) - (profile (string-append (getenv "HOME") "/.bash_profile")) - (logout (string-append (getenv "HOME") "/.bash_logout"))) - `((gnu home services bash) + (let ((rc (destination-append ".bashrc")) + (profile (destination-append ".bash_profile")) + (logout (destination-append ".bash_logout"))) + `((gnu home-services bash) (service home-bash-service-type - (home-bash-configuration - ,@(if (file-exists? rc) - `((bashrc - (list (local-file ,rc)))) - '()) - ,@(if (file-exists? profile) - `((bash-profile - (list (local-file ,profile)))) - '()) - ,@(if (file-exists? logout) - `((bash-logout - (list (local-file ,logout)))) - '())))))) - + (home-bash-configuration + ,@(if (file-exists? rc) + `((bashrc + (list (slurp-file-gexp + (local-file ,rc))))) + '()) + ,@(if (file-exists? profile) + `((bash-profile + (list (slurp-file-gexp + (local-file ,profile))))) + '()) + ,@(if (file-exists? logout) + `((bash-logout + (list (slurp-file-gexp + (local-file ,logout))))) + '())))))) (define %files-configurations-alist `((".bashrc" . ,generate-bash-module+configuration) (".bash_profile" . ,generate-bash-module+configuration) (".bash_logout" . ,generate-bash-module+configuration))) -(define (modules+configurations) - (let ((configurations (delete-duplicates - (filter-map (match-lambda - ((file . proc) - (if (file-exists? - (string-append (getenv "HOME") "/" file)) - proc - #f))) - %files-configurations-alist) - (lambda (x y) - (equal? (procedure-name x) (procedure-name y)))))) - (map (lambda (proc) (proc)) configurations))) +(define (configurations+modules destination-directory) + "Return a list of procedures which when called, generate code for a home +service declaration." + (define configurations + (delete-duplicates + (filter-map (match-lambda + ((file . proc) + (let ((absolute-path (string-append (getenv "HOME") + "/" file))) + (and (file-exists? absolute-path) + (begin + (copy-file absolute-path + (string-append + destination-directory "/" file)) + proc))))) + %files+configurations-alist) + (lambda (x y) + (equal? (procedure-name x) (procedure-name y))))) + + (map (lambda (proc) (proc destination-directory)) configurations)) ;; Based on `manifest->code' from (guix profiles) ;; MAYBE: Upstream it? -(define* (manifest->code manifest +(define* (manifest->code manifest destination-directory #:key (entry-package-version (const "")) (home-environment? #f)) @@ -129,7 +141,8 @@ available." ":" output)))) (manifest-entries manifest)))) (if home-environment? - (let ((modules+configurations (modules+configurations))) + (let ((configurations+modules + (configurations+modules destination-directory))) `(begin (use-modules (gnu home) (gnu packages) @@ -171,7 +184,8 @@ available." (options->transformation ',options)))) transformation-procedures))) (if home-environment? - (let ((modules+configurations (modules+configurations))) + (let ((configurations+modules + (configurations+modules destination-directory))) `(begin (use-modules (guix transformations) (gnu home) @@ -204,7 +218,7 @@ containing PACKAGES, or SPECS (package specifications), and SERVICES." (services (list ,@services)))) (define* (import-manifest - manifest + manifest destination-directory #:optional (port (current-output-port))) "Write to PORT a corresponding to MANIFEST." (define (version-spec entry) @@ -227,7 +241,7 @@ containing PACKAGES, or SPECS (package specifications), and SERVICES." (version-unique-prefix (manifest-entry-version entry) versions))))))) - (match (manifest->code manifest + (match (manifest->code manifest destination-directory #:entry-package-version version-spec #:home-environment? #t) (('begin exp ...) -- cgit v1.2.3 From e4d09d25ae6e4319c9eb1c78601af7007128a21e Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:32 +0200 Subject: guix home: import: Allow multiple modules to be imported for each service. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, only one module could be imported for each service, e.g., only (gnu home-services shell) could be imported when generating the Bash service declaration. However, for some services, multiple modules might need to be imported in order for it to work. * guix/scripts/home/import.scm (generate-bash-module+configuration): Rename to ... (generate-bash-configuration+modules): ... this. (%files-configurations-alist): Rename to ... (%files+configurations-alist): ... this. (modules+configurations): Rename to ... (configurations+modules): ... this. (manifest->code): Adjust accordingly. Signed-off-by: Ludovic Courtès --- guix/scripts/home/import.scm | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index c7c60e95e8..533abdbb8d 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -43,8 +43,7 @@ (let ((rc (destination-append ".bashrc")) (profile (destination-append ".bash_profile")) (logout (destination-append ".bash_logout"))) - `((gnu home-services bash) - (service home-bash-service-type + `((service home-bash-service-type (home-bash-configuration ,@(if (file-exists? rc) `((bashrc @@ -60,12 +59,15 @@ `((bash-logout (list (slurp-file-gexp (local-file ,logout))))) - '())))))) + '()))) + (gnu home-services bash)))) -(define %files-configurations-alist - `((".bashrc" . ,generate-bash-module+configuration) - (".bash_profile" . ,generate-bash-module+configuration) - (".bash_logout" . ,generate-bash-module+configuration))) + + +(define %files+configurations-alist + `((".bashrc" . ,generate-bash-configuration+modules) + (".bash_profile" . ,generate-bash-configuration+modules) + (".bash_logout" . ,generate-bash-configuration+modules))) (define (configurations+modules destination-directory) "Return a list of procedures which when called, generate code for a home @@ -144,12 +146,13 @@ available." (let ((configurations+modules (configurations+modules destination-directory))) `(begin - (use-modules (gnu home) - (gnu packages) - ,@(map first modules+configurations)) - ,(home-environment-template - #:specs specs - #:services (map second modules+configurations)))) + (use-modules (gnu home) + (gnu packages) + (gnu services) + ,@(concatenate (map cdr configurations+modules))) + ,(home-environment-template + #:specs specs + #:services (map first configurations+modules)))) `(begin (use-modules (gnu packages)) @@ -190,13 +193,14 @@ available." (use-modules (guix transformations) (gnu home) (gnu packages) - ,@(map first modules+configurations)) + (gnu services) + ,@(concatenate (map cdr configurations+modules))) ,@transformations ,(home-environment-template #:packages packages - #:services (map second modules+configurations)))) + #:services (map first configurations+modules)))) `(begin (use-modules (guix transformations) (gnu packages)) -- cgit v1.2.3 From 3be1a982f30dee261b90fd2779fc4d1b04c50961 Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:34 +0200 Subject: guix home: import: Fix module name for Bash service. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/scripts/home/import.scm (generate-bash-configuration+modules): Change (gnu home-services bash) to (gnu home-services shells); add (guix gexp). Signed-off-by: Ludovic Courtès --- guix/scripts/home/import.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index 533abdbb8d..f20088aa88 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -60,7 +60,8 @@ (list (slurp-file-gexp (local-file ,logout))))) '()))) - (gnu home-services bash)))) + (guix gexp) + (gnu home services shells)))) -- cgit v1.2.3 From e12e8aad35e734fe2c4a0a54e9c6bc91eedf921d Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:35 +0200 Subject: guix home: import: Don’t use 'slurp-file-gexp'. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘slurp-file-gexp’ is not a bound procedure. * guix/scripts/home/import.scm (generate-bash-configuration+modules): Don’t use ‘slurp-file-gexp’. Signed-off-by: Ludovic Courtès --- guix/scripts/home/import.scm | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index f20088aa88..0e7c454f45 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -47,24 +47,19 @@ (home-bash-configuration ,@(if (file-exists? rc) `((bashrc - (list (slurp-file-gexp - (local-file ,rc))))) + (list (local-file ,rc)))) '()) ,@(if (file-exists? profile) `((bash-profile - (list (slurp-file-gexp - (local-file ,profile))))) + (list (local-file ,profile)))) '()) ,@(if (file-exists? logout) `((bash-logout - (list (slurp-file-gexp - (local-file ,logout))))) + (list (local-file ,logout)))) '()))) (guix gexp) (gnu home services shells)))) - - (define %files+configurations-alist `((".bashrc" . ,generate-bash-configuration+modules) (".bash_profile" . ,generate-bash-configuration+modules) -- cgit v1.2.3 From 256bd712648f404451f820eaedab3985df819556 Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:37 +0200 Subject: guix home: import: Delete duplicate modules when importing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two different services might require the same module(s), so delete duplicates when generating the ‘use-modules’ form. * import.scm (manifest->code): Delete duplicate modules. Signed-off-by: Ludovic Courtès --- guix/scripts/home/import.scm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index 0e7c454f45..f0ae233b75 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -145,7 +145,8 @@ available." (use-modules (gnu home) (gnu packages) (gnu services) - ,@(concatenate (map cdr configurations+modules))) + ,@((compose delete-duplicates concatenate) + (map cdr configurations+modules))) ,(home-environment-template #:specs specs #:services (map first configurations+modules)))) @@ -190,7 +191,8 @@ available." (gnu home) (gnu packages) (gnu services) - ,@(concatenate (map cdr configurations+modules))) + ,@((compose delete-duplicates concatenate) + (map cdr configurations+modules))) ,@transformations -- cgit v1.2.3 From 40acbaf0789d47464062622bef463921556c2235 Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:41 +0200 Subject: Add tests for ‘guix home import’. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tests/home-import.scm: New file. * Makefile.am (SCM_TESTS): Add it. Signed-off-by: Ludovic Courtès --- Makefile.am | 1 + guix/scripts/home/import.scm | 7 +- tests/home-import.scm | 179 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 tests/home-import.scm (limited to 'guix/scripts') diff --git a/Makefile.am b/Makefile.am index 239387c2f4..d608b08899 100644 --- a/Makefile.am +++ b/Makefile.am @@ -475,6 +475,7 @@ SCM_TESTS = \ tests/graph.scm \ tests/gremlin.scm \ tests/hackage.scm \ + tests/home-import.scm \ tests/import-git.scm \ tests/import-utils.scm \ tests/inferior.scm \ diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index f0ae233b75..6e3ed065d5 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -27,7 +27,10 @@ #:use-module (ice-9 pretty-print) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) - #:export (import-manifest)) + #:export (import-manifest + + ;; For tests. + manifest->code)) ;;; Commentary: ;;; @@ -36,6 +39,8 @@ ;;; ;;; Code: + + (define (generate-bash-configuration+modules destination-directory) (define (destination-append path) (string-append destination-directory "/" path)) diff --git a/tests/home-import.scm b/tests/home-import.scm new file mode 100644 index 0000000000..691e8196d6 --- /dev/null +++ b/tests/home-import.scm @@ -0,0 +1,179 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Xinglu Chen +;;; +;;; 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 . + +(define-module (test-home-import) + #:use-module (guix scripts home import) + #:use-module (guix utils) + #:use-module (guix build utils) + #:use-module (guix packages) + #:use-module (ice-9 match) + #:use-module ((guix profiles) #:hide (manifest->code)) + #:use-module ((guix build syscalls) #:select (mkdtemp!)) + #:use-module (gnu packages) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-64)) + +;; Test the (guix scripts home import) tools. + +(test-begin "home-import") + +;; Example manifest entries. + +(define guile-2.0.9 + (manifest-entry + (name "guile") + (version "2.0.9") + (item "/gnu/store/..."))) + +(define glibc + (manifest-entry + (name "glibc") + (version "2.19") + (item "/gnu/store/..."))) + +(define gcc + (manifest-entry + (name "gcc") + (version "10.3.0") + (item "/gnu/store/..."))) + +;; Helpers for checking and generating home environments. + +(define %destination-directory "/tmp/guix-config") +(mkdir-p %destination-directory) + +(define %temporary-home-directory (mkdtemp! "/tmp/guix-home-import.XXXXXX")) + +(define-syntax-rule (define-home-environment-matcher name pattern) + (define (name obj) + (match obj + (pattern #t) + (x (pk 'fail x #f))))) + +(define (create-temporary-home files-alist) + "Create a temporary home directory in '%temporary-home-directory'. +FILES-ALIST is an association list of files and the content of the +corresponding file." + (define (create-file file content) + (let ((absolute-path (string-append %temporary-home-directory "/" file))) + (unless (file-exists? absolute-path) + (mkdir-p (dirname absolute-path))) + (call-with-output-file absolute-path + (cut display content <>)))) + + (for-each (match-lambda + ((file . content) (create-file file content))) + files-alist)) + +;; Copied from (guix profiles) +(define (version-spec entry) + (let ((name (manifest-entry-name entry))) + (match (map package-version (find-packages-by-name name)) + ((_) + ;; A single version of NAME is available, so do not specify the + ;; version number, even if the available version doesn't match ENTRY. + "") + (versions + ;; If ENTRY uses the latest version, don't specify any version. + ;; Otherwise return the shortest unique version prefix. Note that + ;; this is based on the currently available packages, which could + ;; differ from the packages available in the revision that was used + ;; to build MANIFEST. + (let ((current (manifest-entry-version entry))) + (if (every (cut version>? current <>) + (delete current versions)) + "" + (version-unique-prefix (manifest-entry-version entry) + versions))))))) + +(define (eval-test-with-home-environment files-alist manifest matcher) + (create-temporary-home files-alist) + (setenv "HOME" %temporary-home-directory) + (mkdir-p %temporary-home-directory) + (let* ((home-environment (manifest->code manifest %destination-directory + #:entry-package-version version-spec + #:home-environment? #t)) + (result (matcher home-environment))) + (delete-file-recursively %temporary-home-directory) + result)) + +(define-home-environment-matcher match-home-environment-no-services + ('begin + ('use-modules + ('gnu 'home) + ('gnu 'packages) + ('gnu 'services)) + ('home-environment + ('packages + ('map 'specification->package + ('list "guile@2.0.9" "gcc" "glibc@2.19"))) + ('services + ('list))))) + +(define-home-environment-matcher match-home-environment-no-services-nor-packages + ('begin + ('use-modules + ('gnu 'home) + ('gnu 'packages) + ('gnu 'services)) + ('home-environment + ('packages + ('map 'specification->package + ('list))) + ('services + ('list))))) + +(define-home-environment-matcher match-home-environment-bash-service + ('begin + ('use-modules + ('gnu 'home) + ('gnu 'packages) + ('gnu 'services) + ('guix 'gexp) + ('gnu 'home 'services 'shells)) + ('home-environment + ('packages + ('map 'specification->package + ('list))) + ('services + ('list ('service + 'home-bash-service-type + ('home-bash-configuration + ('bashrc + ('list ('local-file "/tmp/guix-config/.bashrc")))))))))) + +(test-assert "manifest->code: No services" + (eval-test-with-home-environment + '() + (make-manifest (list guile-2.0.9 gcc glibc)) + match-home-environment-no-services)) + +(test-assert "manifest->code: No packages nor services" + (eval-test-with-home-environment + '() + (make-manifest '()) + match-home-environment-no-services-nor-packages)) + +(test-assert "manifest->code: Bash service" + (eval-test-with-home-environment + '((".bashrc" . "echo 'hello guix'")) + (make-manifest '()) + match-home-environment-bash-service)) + +(test-end "home-import") -- cgit v1.2.3 From ea19381bd9405bdb711c36b534e463fc7c8ca949 Mon Sep 17 00:00:00 2001 From: Xinglu Chen Date: Sat, 30 Oct 2021 12:42:44 +0200 Subject: guix home: import: Call ‘local-file’ with ‘name’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set the name of the file to just the basename of the file passed to ‘local-file’. * guix/scripts/home/import.scm (basename+remove-dots): New procedure. (generate-bash-configuration+modules): Use it. * tests/home-import.scm (match-home-environment-bash-service): Adjust accordingly. Signed-off-by: Ludovic Courtès --- guix/scripts/home/import.scm | 20 ++++++++++++++++---- tests/home-import.scm | 3 ++- 2 files changed, 18 insertions(+), 5 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index 6e3ed065d5..a0022458f6 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -39,7 +39,16 @@ ;;; ;;; Code: - +(define (basename+remove-dots file-name) + "Remove the dot from the dotfile FILE-NAME; replace the other dots in +FILE-NAME with \"-\", and return the basename of it." + (string-map (match-lambda + (#\. #\-) + (c c)) + (let ((base (basename file-name))) + (if (string-prefix? "." base) + (string-drop base 1) + base)))) (define (generate-bash-configuration+modules destination-directory) (define (destination-append path) @@ -52,15 +61,18 @@ (home-bash-configuration ,@(if (file-exists? rc) `((bashrc - (list (local-file ,rc)))) + (list (local-file ,rc + ,(basename+remove-dots rc))))) '()) ,@(if (file-exists? profile) `((bash-profile - (list (local-file ,profile)))) + (list (local-file ,profile + ,(basename+remove-dots profile))))) '()) ,@(if (file-exists? logout) `((bash-logout - (list (local-file ,logout)))) + (list (local-file ,logout + ,(basename+remove-dots logout))))) '()))) (guix gexp) (gnu home services shells)))) diff --git a/tests/home-import.scm b/tests/home-import.scm index 691e8196d6..40d9547e8b 100644 --- a/tests/home-import.scm +++ b/tests/home-import.scm @@ -156,7 +156,8 @@ corresponding file." 'home-bash-service-type ('home-bash-configuration ('bashrc - ('list ('local-file "/tmp/guix-config/.bashrc")))))))))) + ('list ('local-file "/tmp/guix-config/.bashrc" + "bashrc")))))))))) (test-assert "manifest->code: No services" (eval-test-with-home-environment -- cgit v1.2.3 From 341fba217f5a9490c0e3a077e017adf7bc68acf5 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 30 Oct 2021 21:39:18 +0200 Subject: home: import: Compare procedures with 'eq?'. 'procedure-name' is a debugging aid and cannot be reliably used to check for procedure equality. * guix/scripts/home/import.scm (configurations+modules): Remove use of 'procedure-name'. --- guix/scripts/home/import.scm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index a0022458f6..d00739da22 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -98,9 +98,8 @@ service declaration." destination-directory "/" file)) proc))))) %files+configurations-alist) - (lambda (x y) - (equal? (procedure-name x) (procedure-name y))))) - + eq?)) + (map (lambda (proc) (proc destination-directory)) configurations)) ;; Based on `manifest->code' from (guix profiles) -- cgit v1.2.3 From f3933ae40d4192fa3aeff95ac768bab86ade766f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 30 Oct 2021 21:43:19 +0200 Subject: home: import: Clarify "destination directory". Suggested by Liliana Marie Prikler . * guix/scripts/home/import.scm (configurations+modules): Rename 'destination-directory' to 'configuration-directory'. Improve docstring. --- guix/scripts/home/import.scm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index d00739da22..a51f7f504b 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -82,9 +82,11 @@ FILE-NAME with \"-\", and return the basename of it." (".bash_profile" . ,generate-bash-configuration+modules) (".bash_logout" . ,generate-bash-configuration+modules))) -(define (configurations+modules destination-directory) +(define (configurations+modules configuration-directory) "Return a list of procedures which when called, generate code for a home -service declaration." +service declaration. Copy configuration files to CONFIGURATION-DIRECTORY; the +generated service declarations will refer to those files that have been saved +in CONFIGURATION-DIRECTORY." (define configurations (delete-duplicates (filter-map (match-lambda @@ -95,12 +97,12 @@ service declaration." (begin (copy-file absolute-path (string-append - destination-directory "/" file)) + configuration-directory "/" file)) proc))))) %files+configurations-alist) eq?)) - (map (lambda (proc) (proc destination-directory)) configurations)) + (map (lambda (proc) (proc configuration-directory)) configurations)) ;; Based on `manifest->code' from (guix profiles) ;; MAYBE: Upstream it? -- cgit v1.2.3 From 96728c54df365cc48f14a514b63616ff7a6d052b Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 30 Oct 2021 23:30:50 +0200 Subject: home: import: Factorize triplicated 'version-spec' procedure. * guix/scripts/package.scm (manifest-entry-version-prefix): New procedure, moved from... (export-manifest)[version-spec]: ... here. Adjust caller. * tests/home-import.scm (version-spec): Remove. (eval-test-with-home-environment): Use 'manifest-entry-version-prefix' instead. * guix/scripts/home/import.scm (import-manifest): Likewise. --- guix/scripts/home/import.scm | 23 ++-------------------- guix/scripts/package.scm | 47 ++++++++++++++++++++++++-------------------- tests/home-import.scm | 26 ++++-------------------- 3 files changed, 32 insertions(+), 64 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index a51f7f504b..8f6b3b58aa 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -22,6 +22,7 @@ #:use-module (guix ui) #:use-module (guix utils) #:use-module (guix packages) + #:autoload (guix scripts package) (manifest-entry-version-prefix) #:use-module (gnu packages) #:use-module (ice-9 match) #:use-module (ice-9 pretty-print) @@ -241,28 +242,8 @@ containing PACKAGES, or SPECS (package specifications), and SERVICES." manifest destination-directory #:optional (port (current-output-port))) "Write to PORT a corresponding to MANIFEST." - (define (version-spec entry) - (let ((name (manifest-entry-name entry))) - (match (map package-version (find-packages-by-name name)) - ((_) - ;; A single version of NAME is available, so do not specify the - ;; version number, even if the available version doesn't match ENTRY. - "") - (versions - ;; If ENTRY uses the latest version, don't specify any version. - ;; Otherwise return the shortest unique version prefix. Note that - ;; this is based on the currently available packages, which could - ;; differ from the packages available in the revision that was used - ;; to build MANIFEST. - (let ((current (manifest-entry-version entry))) - (if (every (cut version>? current <>) - (delete current versions)) - "" - (version-unique-prefix (manifest-entry-version entry) - versions))))))) - (match (manifest->code manifest destination-directory - #:entry-package-version version-spec + #:entry-package-version manifest-entry-version-prefix #:home-environment? #t) (('begin exp ...) (format port (G_ "\ diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm index a34ecdcb54..4b9c5f210d 100644 --- a/guix/scripts/package.scm +++ b/guix/scripts/package.scm @@ -68,6 +68,7 @@ guix-package search-path-environment-variables + manifest-entry-version-prefix transaction-upgrade-entry ;mostly for testing @@ -327,31 +328,35 @@ Alternately, see @command{guix package --search-paths -p ~s}.") ;;; Export a manifest. ;;; +(define (manifest-entry-version-prefix entry) + "Search among all the versions of ENTRY's package that are available, and +return the shortest unambiguous version prefix for this package. If only one +version of ENTRY's package is available, return the empty string." + (let ((name (manifest-entry-name entry))) + (match (map package-version (find-packages-by-name name)) + ((_) + ;; A single version of NAME is available, so do not specify the + ;; version number, even if the available version doesn't match ENTRY. + "") + (versions + ;; If ENTRY uses the latest version, don't specify any version. + ;; Otherwise return the shortest unique version prefix. Note that + ;; this is based on the currently available packages, which could + ;; differ from the packages available in the revision that was used + ;; to build MANIFEST. + (let ((current (manifest-entry-version entry))) + (if (every (cut version>? current <>) + (delete current versions)) + "" + (version-unique-prefix (manifest-entry-version entry) + versions))))))) + (define* (export-manifest manifest #:optional (port (current-output-port))) "Write to PORT a manifest corresponding to MANIFEST." - (define (version-spec entry) - (let ((name (manifest-entry-name entry))) - (match (map package-version (find-packages-by-name name)) - ((_) - ;; A single version of NAME is available, so do not specify the - ;; version number, even if the available version doesn't match ENTRY. - "") - (versions - ;; If ENTRY uses the latest version, don't specify any version. - ;; Otherwise return the shortest unique version prefix. Note that - ;; this is based on the currently available packages, which could - ;; differ from the packages available in the revision that was used - ;; to build MANIFEST. - (let ((current (manifest-entry-version entry))) - (if (every (cut version>? current <>) - (delete current versions)) - "" - (version-unique-prefix (manifest-entry-version entry) - versions))))))) - (match (manifest->code manifest - #:entry-package-version version-spec) + #:entry-package-version + manifest-entry-version-prefix) (('begin exp ...) (format port (G_ "\ ;; This \"manifest\" file can be passed to 'guix package -m' to reproduce diff --git a/tests/home-import.scm b/tests/home-import.scm index 40d9547e8b..dc413d8516 100644 --- a/tests/home-import.scm +++ b/tests/home-import.scm @@ -24,6 +24,8 @@ #:use-module (ice-9 match) #:use-module ((guix profiles) #:hide (manifest->code)) #:use-module ((guix build syscalls) #:select (mkdtemp!)) + #:use-module ((guix scripts package) + #:select (manifest-entry-version-prefix)) #:use-module (gnu packages) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) @@ -81,33 +83,13 @@ corresponding file." ((file . content) (create-file file content))) files-alist)) -;; Copied from (guix profiles) -(define (version-spec entry) - (let ((name (manifest-entry-name entry))) - (match (map package-version (find-packages-by-name name)) - ((_) - ;; A single version of NAME is available, so do not specify the - ;; version number, even if the available version doesn't match ENTRY. - "") - (versions - ;; If ENTRY uses the latest version, don't specify any version. - ;; Otherwise return the shortest unique version prefix. Note that - ;; this is based on the currently available packages, which could - ;; differ from the packages available in the revision that was used - ;; to build MANIFEST. - (let ((current (manifest-entry-version entry))) - (if (every (cut version>? current <>) - (delete current versions)) - "" - (version-unique-prefix (manifest-entry-version entry) - versions))))))) - (define (eval-test-with-home-environment files-alist manifest matcher) (create-temporary-home files-alist) (setenv "HOME" %temporary-home-directory) (mkdir-p %temporary-home-directory) (let* ((home-environment (manifest->code manifest %destination-directory - #:entry-package-version version-spec + #:entry-package-version + manifest-entry-version-prefix #:home-environment? #t)) (result (matcher home-environment))) (delete-file-recursively %temporary-home-directory) -- cgit v1.2.3 From 6f4ca78761471602e3af37ee1a33de446114039f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 31 Oct 2021 00:02:27 +0200 Subject: home: import: Avoid duplication of 'manifest->code'. * guix/scripts/home/import.scm (manifest->code): Remove. (manifest+configuration-files->code): New procedure. (import-manifest): Use 'manifest+configuration-files->code' instead of 'manifest->code'. * tests/home-import.scm (eval-test-with-home-environment): Likewise. (match-home-environment-transformations): New procedure. ("manifest->code: No services, package transformations"): New test. --- guix/scripts/home/import.scm | 176 ++++++++++--------------------------------- tests/home-import.scm | 33 +++++++- 2 files changed, 69 insertions(+), 140 deletions(-) (limited to 'guix/scripts') diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index 8f6b3b58aa..7a7712dd96 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2021 Xinglu Chen ;;; Copyright © 2021 Andrew Tropin +;;; Copyright © 2021 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -31,7 +32,7 @@ #:export (import-manifest ;; For tests. - manifest->code)) + manifest+configuration-files->code)) ;;; Commentary: ;;; @@ -105,146 +106,49 @@ in CONFIGURATION-DIRECTORY." (map (lambda (proc) (proc configuration-directory)) configurations)) -;; Based on `manifest->code' from (guix profiles) -;; MAYBE: Upstream it? -(define* (manifest->code manifest destination-directory - #:key - (entry-package-version (const "")) - (home-environment? #f)) - "Return an sexp representing code to build an approximate version of -MANIFEST; the code is wrapped in a top-level 'begin' form. If -HOME-ENVIRONMENT? is #t, return an definition. -Call ENTRY-PACKAGE-VERSION to determine the version number to use in -the spec for a given entry; it can be set to 'manifest-entry-version' -for fully-specified version numbers, or to some other procedure to -disambiguate versions for packages for which several versions are -available." - (define (entry-transformations entry) - ;; Return the transformations that apply to ENTRY. - (assoc-ref (manifest-entry-properties entry) 'transformations)) - - (define transformation-procedures - ;; List of transformation options/procedure name pairs. - (let loop ((entries (manifest-entries manifest)) - (counter 1) - (result '())) - (match entries - (() result) - ((entry . tail) - (match (entry-transformations entry) - (#f - (loop tail counter result)) - (options - (if (assoc-ref result options) - (loop tail counter result) - (loop tail (+ 1 counter) - (alist-cons options - (string->symbol - (format #f "transform~a" counter)) - result))))))))) - - (define (qualified-name entry) - ;; Return the name of ENTRY possibly with "@" followed by a version. - (match (entry-package-version entry) - ("" (manifest-entry-name entry)) - (version (string-append (manifest-entry-name entry) - "@" version)))) - - (if (null? transformation-procedures) - (let ((specs (map (lambda (entry) - (match (manifest-entry-output entry) - ("out" (qualified-name entry)) - (output (string-append (qualified-name entry) - ":" output)))) - (manifest-entries manifest)))) - (if home-environment? - (let ((configurations+modules - (configurations+modules destination-directory))) - `(begin - (use-modules (gnu home) - (gnu packages) - (gnu services) - ,@((compose delete-duplicates concatenate) - (map cdr configurations+modules))) - ,(home-environment-template - #:specs specs - #:services (map first configurations+modules)))) - `(begin - (use-modules (gnu packages)) - - (specifications->manifest - (list ,@specs))))) - (let* ((transform (lambda (options exp) - (if (not options) - exp - (let ((proc (assoc-ref transformation-procedures - options))) - `(,proc ,exp))))) - (packages (map (lambda (entry) - (define options - (entry-transformations entry)) - - (define name - (qualified-name entry)) - - (match (manifest-entry-output entry) - ("out" - (transform options - `(specification->package ,name))) - (output - `(list ,(transform - options - `(specification->package ,name)) - ,output)))) - (manifest-entries manifest))) - (transformations (map (match-lambda - ((options . name) - `(define ,name - (options->transformation ',options)))) - transformation-procedures))) - (if home-environment? - (let ((configurations+modules - (configurations+modules destination-directory))) - `(begin - (use-modules (guix transformations) - (gnu home) - (gnu packages) - (gnu services) - ,@((compose delete-duplicates concatenate) - (map cdr configurations+modules))) - - ,@transformations - - ,(home-environment-template - #:packages packages - #:services (map first configurations+modules)))) - `(begin - (use-modules (guix transformations) - (gnu packages)) - - ,@transformations - - (packages->manifest - (list ,@packages))))))) - -(define* (home-environment-template #:key (packages #f) (specs #f) services) - "Return an S-exp containing a declaration -containing PACKAGES, or SPECS (package specifications), and SERVICES." - `(home-environment - (packages - ,@(if packages - `((list ,@packages)) - `((map specification->package - (list ,@specs))))) - (services (list ,@services)))) +(define (manifest+configuration-files->code manifest + configuration-directory) + "Read MANIFEST and the user's configuration files listed in +%FILES+CONFIGURATIONS-ALIST, and return a 'home-environment' sexp. Copy the +user's files to CONFIGURATION-DIRECTORY; the generated sexp refers to them." + (match (manifest->code manifest + #:entry-package-version + manifest-entry-version-prefix) + (('begin ('use-modules profile-modules ...) + definitions ... ('packages->manifest packages)) + (match (configurations+modules configuration-directory) + (((services . modules) ...) + `(begin + (use-modules (gnu home) + (gnu packages) + (gnu services) + ,@(delete-duplicates + (append profile-modules (concatenate modules)))) + + ,@definitions + + (home-environment + (packages ,packages) + (services (list ,@services))))))) + (('begin ('specifications->manifest packages)) + (match (configurations+modules configuration-directory) + (((services . modules) ...) + `(begin + (use-modules (gnu home) + (gnu packages) + (gnu services) + ,@(delete-duplicates (concatenate modules))) + + (home-environment + (packages (map specification->package ,packages)) + (services (list ,@services))))))))) (define* (import-manifest manifest destination-directory #:optional (port (current-output-port))) "Write to PORT a corresponding to MANIFEST." - (match (manifest->code manifest destination-directory - #:entry-package-version manifest-entry-version-prefix - #:home-environment? #t) + (match (manifest+configuration-files->code manifest + destination-directory) (('begin exp ...) (format port (G_ "\ ;; This \"home-environment\" file can be passed to 'guix home reconfigure' diff --git a/tests/home-import.scm b/tests/home-import.scm index dc413d8516..abd3cec43d 100644 --- a/tests/home-import.scm +++ b/tests/home-import.scm @@ -87,10 +87,8 @@ corresponding file." (create-temporary-home files-alist) (setenv "HOME" %temporary-home-directory) (mkdir-p %temporary-home-directory) - (let* ((home-environment (manifest->code manifest %destination-directory - #:entry-package-version - manifest-entry-version-prefix - #:home-environment? #t)) + (let* ((home-environment (manifest+configuration-files->code + manifest %destination-directory)) (result (matcher home-environment))) (delete-file-recursively %temporary-home-directory) result)) @@ -108,6 +106,22 @@ corresponding file." ('services ('list))))) +(define-home-environment-matcher match-home-environment-transformations + ('begin + ('use-modules + ('gnu 'home) + ('gnu 'packages) + ('gnu 'services) + ('guix 'transformations)) + + ('define transform ('options->transformation _)) + ('home-environment + ('packages + ('list (transform ('specification->package "guile@2.0.9")) + ('specification->package "gcc") + ('specification->package "glibc@2.19"))) + ('services ('list))))) + (define-home-environment-matcher match-home-environment-no-services-nor-packages ('begin ('use-modules @@ -141,12 +155,23 @@ corresponding file." ('list ('local-file "/tmp/guix-config/.bashrc" "bashrc")))))))))) + (test-assert "manifest->code: No services" (eval-test-with-home-environment '() (make-manifest (list guile-2.0.9 gcc glibc)) match-home-environment-no-services)) +(test-assert "manifest->code: No services, package transformations" + (eval-test-with-home-environment + '() + (make-manifest (list (manifest-entry + (inherit guile-2.0.9) + (properties `((transformations + . ((foo . "bar")))))) + gcc glibc)) + match-home-environment-transformations)) + (test-assert "manifest->code: No packages nor services" (eval-test-with-home-environment '() -- cgit v1.2.3