diff mbox series

[bug#58812,4/5] guix: shell: Add '--symlink' option.

Message ID 20221027035100.28852-4-maxim.cournoyer@gmail.com
State New
Headers show
Series Add --symlink option to 'guix shell'. | expand

Checks

Context Check Description
cbaines/comparison success View comparision
cbaines/git-branch success View Git branch
cbaines/applying patch success
cbaines/issue success View issue

Commit Message

Maxim Cournoyer Oct. 27, 2022, 3:50 a.m. UTC
* guix/scripts/pack.scm (%options): Extract symlink parsing logic to...
(symlink-spec-option-parser): ... here.
(self-contained-tarball/builder): Extract symlink->directives logic to...
* gnu/build/install.scm (make-symlink->directives): ... here.  Add a comment
mentioning why a relative file name is used for the link target.
* guix/scripts/environment.scm (show-environment-options-help): Document new
--symlink option.
(%default-options): Add default value for symlinks.
(%options): Register new symlink option.
(launch-environment/container): Add #:symlinks argument and extend doc.
Create symlinks using evaluate-populate-directive and
make-symlink->directives.
(guix-environment*): Pass symlinks arguments to launch-environment/container.
* doc/guix.texi (Invoking guix shell): Document it.
* tests/guix-shell.sh: Test it.
---
 doc/guix.texi                |  9 +++++-
 gnu/build/install.scm        | 18 ++++++++++++
 guix/scripts/environment.scm | 38 +++++++++++++++++-------
 guix/scripts/pack.scm        | 57 +++++++++++++++---------------------
 tests/guix-shell.sh          | 17 +++++++++++
 5 files changed, 94 insertions(+), 45 deletions(-)

Comments

Ludovic Courtès Nov. 9, 2022, 8:58 p.m. UTC | #1
Hi,

That looks like a useful improvement!  Some comments below.

Maxim Cournoyer <maxim.cournoyer@gmail.com> skribis:

> +@item --symlink=@var{spec}
> +@itemx -S @var{spec}
> +For containers, create the symbolic links specified by @var{spec}, as
> +documented in @ref{pack-symlink-option}.

We should refrain from using @ref in sentences (info "(texinfo) @ref").
Instead, I’d write:

  documented for @command{guix pack} (@pxref{pack-symlink-option}).

>  (define-module (gnu build install)
> +  #:use-module ((guix build union) #:select (relative-file-name))
>    #:use-module (guix build syscalls)
>    #:use-module (guix build utils)
>    #:use-module (guix build store-copy)
> @@ -26,6 +27,7 @@ (define-module (gnu build install)
>    #:use-module (ice-9 match)
>    #:export (install-boot-config
>              evaluate-populate-directive
> +            make-symlink->directives
>              populate-root-file-system
>              install-database-and-gc-roots
>              populate-single-profile-directory
> @@ -124,6 +126,22 @@ (define target* (if (string-suffix? "/" target)
>                  directive)
>          (apply throw args)))))
>  
> +(define (make-symlink->directives directory)
> +  "Return a procedure that turn symlinks specs into directives that target
> +DIRECTORY."
> +  (match-lambda
> +    ((source '-> target)
> +     (let ((target (string-append directory "/" target))
> +           (parent (dirname source)))
> +       ;; Never add a 'directory' directive for "/" so as to preserve its
> +       ;; ownership and avoid adding the same entries multiple times.
> +       `(,@(if (string=? parent "/")
> +               '()
> +               `((directory ,parent)))
> +         ;; Note: a relative file name is used for compatibility with
> +         ;; relocatable packs.
> +         (,source -> ,(relative-file-name parent target)))))))

I think it’s a case where I would refrain from factorizing because this
procedure, as shown by the comments and the use of ‘relative-file-name’,
is specifically tailored for the needs to ‘guix pack -f tarball’.

I’d prefer to have a similar but independently maintained variant of
this procedure in (guix scripts environment) to avoid difficulties down
the road.

> +++ b/guix/scripts/environment.scm
> @@ -33,8 +33,10 @@ (define-module (guix scripts environment)
>    #:use-module ((guix gexp) #:select (lower-object))
>    #:use-module (guix scripts)
>    #:use-module (guix scripts build)
> +  #:use-module ((guix scripts pack) #:select (symlink-spec-option-parser))

You can turn this into #:autoload so we don’t pay the price when not
using ‘--symlink’.

> +++ b/tests/guix-shell.sh
> @@ -20,6 +20,8 @@
>  # Test the 'guix shell' alias.
>  #
>  
> +. tests/utils.sh
> +
>  guix shell --version
>  
>  configdir="t-guix-shell-config-$$"
> @@ -32,6 +34,21 @@ export XDG_CONFIG_HOME
>  
>  guix shell --bootstrap --pure guile-bootstrap -- guile --version
>  
> +# '--symlink' can only be used with --container.
> +! guix shell --bootstrap guile-bootstrap -S /dummy=bin/guile
> +
> +if has_container_support; then
> +    # '--symlink' works.
> +    echo "TESTING SYMLINK IN CONTAINER"
> +    guix shell --bootstrap guile-bootstrap --container \
> +         --symlink=/usr/bin/guile=bin/guile -- \
> +         /usr/bin/guile --version

This should go to ‘tests/guix-environment-container.sh’, which has all
the container-related tests.

Thanks,
Ludo’.
Maxim Cournoyer Nov. 10, 2022, 3:10 a.m. UTC | #2
Hi Ludo!

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

> Hi,
>
> That looks like a useful improvement!  Some comments below.

Thanks!

> Maxim Cournoyer <maxim.cournoyer@gmail.com> skribis:
>
>> +@item --symlink=@var{spec}
>> +@itemx -S @var{spec}
>> +For containers, create the symbolic links specified by @var{spec}, as
>> +documented in @ref{pack-symlink-option}.
>
> We should refrain from using @ref in sentences (info "(texinfo) @ref").
> Instead, I’d write:
>
>   documented for @command{guix pack} (@pxref{pack-symlink-option}).

I've heard that from you before, but is there a reason against?  I like
to know the rationale for doing things a certain way, lest I forget :-).
From info '(texinfo) @ref':

--8<---------------cut here---------------start------------->8---
6.6 '@ref'
==========

'@ref' is nearly the same as '@xref' except that it does not generate a
'See' in the printed output, just the reference itself.  This makes it
useful as the last part of a sentence.

For example,

     For more information, @pxref{This}, and @ref{That}.

produces in Info:

     For more information, *note This::, and *note That::.
--8<---------------cut here---------------end--------------->8---

>>  (define-module (gnu build install)
>> +  #:use-module ((guix build union) #:select (relative-file-name))
>>    #:use-module (guix build syscalls)
>>    #:use-module (guix build utils)
>>    #:use-module (guix build store-copy)
>> @@ -26,6 +27,7 @@ (define-module (gnu build install)
>>    #:use-module (ice-9 match)
>>    #:export (install-boot-config
>>              evaluate-populate-directive
>> +            make-symlink->directives
>>              populate-root-file-system
>>              install-database-and-gc-roots
>>              populate-single-profile-directory
>> @@ -124,6 +126,22 @@ (define target* (if (string-suffix? "/" target)
>>                  directive)
>>          (apply throw args)))))
>>
>> +(define (make-symlink->directives directory)
>> +  "Return a procedure that turn symlinks specs into directives that target
>> +DIRECTORY."
>> +  (match-lambda
>> +    ((source '-> target)
>> +     (let ((target (string-append directory "/" target))
>> +           (parent (dirname source)))
>> +       ;; Never add a 'directory' directive for "/" so as to preserve its
>> +       ;; ownership and avoid adding the same entries multiple times.
>> +       `(,@(if (string=? parent "/")
>> +               '()
>> +               `((directory ,parent)))
>> +         ;; Note: a relative file name is used for compatibility with
>> +         ;; relocatable packs.
>> +         (,source -> ,(relative-file-name parent target)))))))
>
> I think it’s a case where I would refrain from factorizing because this
> procedure, as shown by the comments and the use of ‘relative-file-name’,
> is specifically tailored for the needs to ‘guix pack -f tarball’.
>
> I’d prefer to have a similar but independently maintained variant of
> this procedure in (guix scripts environment) to avoid difficulties down
> the road.

I considered to duplicate it, but I opted to reuse it in the end because
I care that the behavior is exactly the same between the two actions
(guix shell --symlink vs guix pack --symlink).  If the way we handle
this is to be changed in the future, I'd want both to be changed at
once, so they remain consistent.  Does this make sense?

>> +++ b/guix/scripts/environment.scm
>> @@ -33,8 +33,10 @@ (define-module (guix scripts environment)
>>    #:use-module ((guix gexp) #:select (lower-object))
>>    #:use-module (guix scripts)
>>    #:use-module (guix scripts build)
>> +  #:use-module ((guix scripts pack) #:select (symlink-spec-option-parser))
>
> You can turn this into #:autoload so we don’t pay the price when not
> using ‘--symlink’.

Done!  Could Guile simply always use lazy loading (autoload by default)?
Otherwise, when is it OK to use autoload and when is it not?

>> +++ b/tests/guix-shell.sh
>> @@ -20,6 +20,8 @@
>>  # Test the 'guix shell' alias.
>>  #
>>
>> +. tests/utils.sh
>> +
>>  guix shell --version
>>
>>  configdir="t-guix-shell-config-$$"
>> @@ -32,6 +34,21 @@ export XDG_CONFIG_HOME
>>
>>  guix shell --bootstrap --pure guile-bootstrap -- guile --version
>>
>> +# '--symlink' can only be used with --container.
>> +! guix shell --bootstrap guile-bootstrap -S /dummy=bin/guile
>> +
>> +if has_container_support; then
>> +    # '--symlink' works.
>> +    echo "TESTING SYMLINK IN CONTAINER"
>> +    guix shell --bootstrap guile-bootstrap --container \
>> +         --symlink=/usr/bin/guile=bin/guile -- \
>> +         /usr/bin/guile --version
>
> This should go to ‘tests/guix-environment-container.sh’, which has all
> the container-related tests.

Done, for the "has_container_support" conditional tests.

Thanks for taking a peek!

Maxim
Ludovic Courtès Nov. 10, 2022, 2:17 p.m. UTC | #3
Hi Maxim!

Maxim Cournoyer <maxim.cournoyer@gmail.com> skribis:

>> We should refrain from using @ref in sentences (info "(texinfo) @ref").
>> Instead, I’d write:
>>
>>   documented for @command{guix pack} (@pxref{pack-symlink-option}).
>
> I've heard that from you before, but is there a reason against?  I like
> to know the rationale for doing things a certain way, lest I forget :-).
> From info '(texinfo) @ref':

It’s right below the bit you quoted:

     The '@ref' command can tempt writers to express themselves in a
  manner that is suitable for a printed manual but looks awkward in the
  Info format.  Bear in mind that your audience could be using both the
  printed and the Info format.  For example: […]

>>> +(define (make-symlink->directives directory)
>>> +  "Return a procedure that turn symlinks specs into directives that target
>>> +DIRECTORY."
>>> +  (match-lambda
>>> +    ((source '-> target)
>>> +     (let ((target (string-append directory "/" target))
>>> +           (parent (dirname source)))
>>> +       ;; Never add a 'directory' directive for "/" so as to preserve its
>>> +       ;; ownership and avoid adding the same entries multiple times.
>>> +       `(,@(if (string=? parent "/")
>>> +               '()
>>> +               `((directory ,parent)))
>>> +         ;; Note: a relative file name is used for compatibility with
>>> +         ;; relocatable packs.
>>> +         (,source -> ,(relative-file-name parent target)))))))
>>
>> I think it’s a case where I would refrain from factorizing because this
>> procedure, as shown by the comments and the use of ‘relative-file-name’,
>> is specifically tailored for the needs to ‘guix pack -f tarball’.
>>
>> I’d prefer to have a similar but independently maintained variant of
>> this procedure in (guix scripts environment) to avoid difficulties down
>> the road.
>
> I considered to duplicate it, but I opted to reuse it in the end because
> I care that the behavior is exactly the same between the two actions
> (guix shell --symlink vs guix pack --symlink).  If the way we handle
> this is to be changed in the future, I'd want both to be changed at
> once, so they remain consistent.  Does this make sense?

They don’t have to be consistent.  Use of ‘relative-file-name’ here for
example is dictated by the needs of relocatable packs.  It doesn’t have
to be this way here.

I think it’s best to keep separate copies here (they likely won’t be
exactly the same).

>>> +++ b/guix/scripts/environment.scm
>>> @@ -33,8 +33,10 @@ (define-module (guix scripts environment)
>>>    #:use-module ((guix gexp) #:select (lower-object))
>>>    #:use-module (guix scripts)
>>>    #:use-module (guix scripts build)
>>> +  #:use-module ((guix scripts pack) #:select (symlink-spec-option-parser))
>>
>> You can turn this into #:autoload so we don’t pay the price when not
>> using ‘--symlink’.
>
> Done!  Could Guile simply always use lazy loading (autoload by default)?

#:select could be synonymous with #:autoload, if that’s what you mean,
but in general Guile cannot know whether autoloading is semantically
equivalent to eagerly loading: there might be side-effects happening
when the top-level of the module runs.

> Otherwise, when is it OK to use autoload and when is it not?

#:autoload exists as a way to amortize initialization costs and make
sure only necessary functionality gets loaded, thereby reducing CPU and
memory usage.

Only the module user can tell whether #:autoload is appropriate.  In
general you’d use it for optional functionality that has a
non-negligible memory footprint or that would noticeably degrade startup
time.

Ludo’.
Maxim Cournoyer Nov. 10, 2022, 2:49 p.m. UTC | #4
Hi Ludo!

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

> Hi Maxim!
>
> Maxim Cournoyer <maxim.cournoyer@gmail.com> skribis:
>
>>> We should refrain from using @ref in sentences (info "(texinfo) @ref").
>>> Instead, I’d write:
>>>
>>>   documented for @command{guix pack} (@pxref{pack-symlink-option}).
>>
>> I've heard that from you before, but is there a reason against?  I like
>> to know the rationale for doing things a certain way, lest I forget :-).
>> From info '(texinfo) @ref':
>
> It’s right below the bit you quoted:
>
>      The '@ref' command can tempt writers to express themselves in a
>   manner that is suitable for a printed manual but looks awkward in the
>   Info format.  Bear in mind that your audience could be using both the
>   printed and the Info format.  For example: […]

Yes, and I don't get it :-)

--8<---------------cut here---------------start------------->8---
   The '@ref' command can tempt writers to express themselves in a
manner that is suitable for a printed manual but looks awkward in the
Info format.  Bear in mind that your audience could be using both the
printed and the Info format.  For example:

     Sea surges are described in @ref{Hurricanes}.

looks ok in the printed output:

     Sea surges are described in Section 6.7 [Hurricanes], page 72.

but is awkward to read in Info, "note" being a verb:

     Sea surges are described in *note Hurricanes::.
--8<---------------cut here---------------end--------------->8---

I don't see a "note" in the final sentence that should make it awkward?
It's lacking a "see " prefix though, which could help to make things a
bit clearer, I guess.

It looks the same in info as in the pxref example given above:

--8<---------------cut here---------------start------------->8---
For example,

     For more information, @pxref{This}, and @ref{That}.

produces in Info:

     For more information, *note This::, and *note That::.
--8<---------------cut here---------------end--------------->8---

I'm also unsure where the "see" comes before That:: above.  Is it a
mistake in the manual?

>>>> +(define (make-symlink->directives directory)
>>>> +  "Return a procedure that turn symlinks specs into directives that target
>>>> +DIRECTORY."
>>>> +  (match-lambda
>>>> +    ((source '-> target)
>>>> +     (let ((target (string-append directory "/" target))
>>>> +           (parent (dirname source)))
>>>> +       ;; Never add a 'directory' directive for "/" so as to preserve its
>>>> +       ;; ownership and avoid adding the same entries multiple times.
>>>> +       `(,@(if (string=? parent "/")
>>>> +               '()
>>>> +               `((directory ,parent)))
>>>> +         ;; Note: a relative file name is used for compatibility with
>>>> +         ;; relocatable packs.
>>>> +         (,source -> ,(relative-file-name parent target)))))))
>>>
>>> I think it’s a case where I would refrain from factorizing because this
>>> procedure, as shown by the comments and the use of ‘relative-file-name’,
>>> is specifically tailored for the needs to ‘guix pack -f tarball’.
>>>
>>> I’d prefer to have a similar but independently maintained variant of
>>> this procedure in (guix scripts environment) to avoid difficulties down
>>> the road.
>>
>> I considered to duplicate it, but I opted to reuse it in the end because
>> I care that the behavior is exactly the same between the two actions
>> (guix shell --symlink vs guix pack --symlink).  If the way we handle
>> this is to be changed in the future, I'd want both to be changed at
>> once, so they remain consistent.  Does this make sense?
>
> They don’t have to be consistent.  Use of ‘relative-file-name’ here for
> example is dictated by the needs of relocatable packs.  It doesn’t have
> to be this way here.
>
> I think it’s best to keep separate copies here (they likely won’t be
> exactly the same).

OK, I see you point about relative-file-name not being needed.  I'll make
the change.

>>>> +++ b/guix/scripts/environment.scm
>>>> @@ -33,8 +33,10 @@ (define-module (guix scripts environment)
>>>>    #:use-module ((guix gexp) #:select (lower-object))
>>>>    #:use-module (guix scripts)
>>>>    #:use-module (guix scripts build)
>>>> +  #:use-module ((guix scripts pack) #:select (symlink-spec-option-parser))
>>>
>>> You can turn this into #:autoload so we don’t pay the price when not
>>> using ‘--symlink’.
>>
>> Done!  Could Guile simply always use lazy loading (autoload by default)?
>
> #:select could be synonymous with #:autoload, if that’s what you mean,
> but in general Guile cannot know whether autoloading is semantically
> equivalent to eagerly loading: there might be side-effects happening
> when the top-level of the module runs.

Perhaps there could be a strict execution mode where it is assumed that
side effects are not used when modules run?  That seems a seldom used
feature anyway, and could enable making lazy loading of modules the
default.

>> Otherwise, when is it OK to use autoload and when is it not?
>
> #:autoload exists as a way to amortize initialization costs and make
> sure only necessary functionality gets loaded, thereby reducing CPU and
> memory usage.
>
> Only the module user can tell whether #:autoload is appropriate.  In
> general you’d use it for optional functionality that has a
> non-negligible memory footprint or that would noticeably degrade startup
> time.
>
> Ludo’.

Thank you for the explanations and review!  I'll send a v3 shortly.

--
Maxim
Maxim Cournoyer Nov. 10, 2022, 3:16 p.m. UTC | #5
Hello,

Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:

> Hi Ludo!
>
> Ludovic Courtès <ludo@gnu.org> writes:
>
>> Hi Maxim!
>>
>> Maxim Cournoyer <maxim.cournoyer@gmail.com> skribis:
>>
>>>> We should refrain from using @ref in sentences (info "(texinfo) @ref").
>>>> Instead, I’d write:
>>>>
>>>>   documented for @command{guix pack} (@pxref{pack-symlink-option}).
>>>
>>> I've heard that from you before, but is there a reason against?  I like
>>> to know the rationale for doing things a certain way, lest I forget :-).
>>> From info '(texinfo) @ref':
>>
>> It’s right below the bit you quoted:
>>
>>      The '@ref' command can tempt writers to express themselves in a
>>   manner that is suitable for a printed manual but looks awkward in the
>>   Info format.  Bear in mind that your audience could be using both the
>>   printed and the Info format.  For example: […]
>
> Yes, and I don't get it :-)

To be more concrete, this is what it looks currently:

--8<---------------cut here---------------start------------->8---
‘--symlink=SPEC’
‘-S SPEC’
     For containers, create the symbolic links specified by SPEC, as
     documented in *note pack-symlink-option::.
--8<---------------cut here---------------end--------------->8---

This is what it'd look if I use (see: @pxref ...) instead:
--8<---------------cut here---------------start------------->8---
‘--symlink=SPEC’
‘-S SPEC’
     For containers, create the symbolic links specified by SPEC (see:
     *note pack-symlink-option::).
--8<---------------cut here---------------end--------------->8---

Contrary to what the Texinfo manual says, pxref seems to be the one
introducing the awkward "*note" verb in the resulting info.
Ludovic Courtès Nov. 14, 2022, 9:18 a.m. UTC | #6
Hi,

Maxim Cournoyer <maxim.cournoyer@gmail.com> skribis:

> Contrary to what the Texinfo manual says, pxref seems to be the one
> introducing the awkward "*note" verb in the resulting info.

If you read it in Emacs, it looks a bit different; I think info.el adds
removes “note” and adds “see” in some cases (e.g., see
‘Info-hide-note-references’).

Ludo’.
Simon Tournier Nov. 16, 2022, 7:03 p.m. UTC | #7
Hi Maxim,

On Wed, 09 Nov 2022 at 21:58, Ludovic Courtès <ludo@gnu.org> wrote:

>> +@item --symlink=@var{spec}
>> +@itemx -S @var{spec}
>> +For containers, create the symbolic links specified by @var{spec}, as
>> +documented in @ref{pack-symlink-option}.
>
> We should refrain from using @ref in sentences (info "(texinfo) @ref").
> Instead, I’d write:
>
>   documented for @command{guix pack} (@pxref{pack-symlink-option}).

Well, for what it is worth, I have marked this email [1] by Eli
Zaretskii from Emacs.  Somehow, the message provides some rules of thumb
to write Texinfo. :-) Quoting about cross-reference:

     5. Cross-references:

        As a separate sentence: @xref{Node name}, for the details.
        In the middle of a sentence ... see @ref{Node name}, for more.
        In parentheses: Some text (@pxref{Some node}) more text.

1: https://lists.gnu.org/archive/html/emacs-devel/2017-11/msg00525.html


Cheers,
simon
Maxim Cournoyer Nov. 16, 2022, 7:34 p.m. UTC | #8
Hi Simon,

zimoun <zimon.toutoune@gmail.com> writes:

> Hi Maxim,
>
> On Wed, 09 Nov 2022 at 21:58, Ludovic Courtès <ludo@gnu.org> wrote:
>
>>> +@item --symlink=@var{spec}
>>> +@itemx -S @var{spec}
>>> +For containers, create the symbolic links specified by @var{spec}, as
>>> +documented in @ref{pack-symlink-option}.
>>
>> We should refrain from using @ref in sentences (info "(texinfo) @ref").
>> Instead, I’d write:
>>
>>   documented for @command{guix pack} (@pxref{pack-symlink-option}).
>
> Well, for what it is worth, I have marked this email [1] by Eli
> Zaretskii from Emacs.  Somehow, the message provides some rules of thumb
> to write Texinfo. :-) Quoting about cross-reference:
>
>      5. Cross-references:
>
>         As a separate sentence: @xref{Node name}, for the details.
>         In the middle of a sentence ... see @ref{Node name}, for more.
>         In parentheses: Some text (@pxref{Some node}) more text.
>
> 1: https://lists.gnu.org/archive/html/emacs-devel/2017-11/msg00525.html

I like it, it takes the occult out of the equation :-).
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 2f7ab61aec..4bd3c18223 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -49,7 +49,7 @@  Copyright @copyright{} 2017 humanitiesNerd@*
 Copyright @copyright{} 2017, 2021 Christine Lemmer-Webber@*
 Copyright @copyright{} 2017, 2018, 2019, 2020, 2021, 2022 Marius Bakke@*
 Copyright @copyright{} 2017, 2019, 2020, 2022 Hartmut Goebel@*
-Copyright @copyright{} 2017, 2019, 2020, 2021 Maxim Cournoyer@*
+Copyright @copyright{} 2017, 2019, 2020, 2021, 2022 Maxim Cournoyer@*
 Copyright @copyright{} 2017–2022 Tobias Geerinckx-Rice@*
 Copyright @copyright{} 2017 George Clemmer@*
 Copyright @copyright{} 2017 Andy Wingo@*
@@ -6230,6 +6230,12 @@  directory:
 guix shell --container --expose=$HOME=/exchange guile -- guile
 @end example
 
+@cindex symbolic links, guix shell
+@item --symlink=@var{spec}
+@itemx -S @var{spec}
+For containers, create the symbolic links specified by @var{spec}, as
+documented in @ref{pack-symlink-option}.
+
 @cindex file system hierarchy standard (FHS)
 @cindex FHS (file system hierarchy standard)
 @item --emulate-fhs
@@ -7022,6 +7028,7 @@  Compress the resulting tarball using @var{tool}---one of @code{gzip},
 @code{zstd}, @code{bzip2}, @code{xz}, @code{lzip}, or @code{none} for no
 compression.
 
+@anchor{pack-symlink-option}
 @item --symlink=@var{spec}
 @itemx -S @var{spec}
 Add the symlinks specified by @var{spec} to the pack.  This option can
diff --git a/gnu/build/install.scm b/gnu/build/install.scm
index 15cc29b2c8..8cf772f3ea 100644
--- a/gnu/build/install.scm
+++ b/gnu/build/install.scm
@@ -19,6 +19,7 @@ 
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu build install)
+  #:use-module ((guix build union) #:select (relative-file-name))
   #:use-module (guix build syscalls)
   #:use-module (guix build utils)
   #:use-module (guix build store-copy)
@@ -26,6 +27,7 @@  (define-module (gnu build install)
   #:use-module (ice-9 match)
   #:export (install-boot-config
             evaluate-populate-directive
+            make-symlink->directives
             populate-root-file-system
             install-database-and-gc-roots
             populate-single-profile-directory
@@ -124,6 +126,22 @@  (define target* (if (string-suffix? "/" target)
                 directive)
         (apply throw args)))))
 
+(define (make-symlink->directives directory)
+  "Return a procedure that turn symlinks specs into directives that target
+DIRECTORY."
+  (match-lambda
+    ((source '-> target)
+     (let ((target (string-append directory "/" target))
+           (parent (dirname source)))
+       ;; Never add a 'directory' directive for "/" so as to preserve its
+       ;; ownership and avoid adding the same entries multiple times.
+       `(,@(if (string=? parent "/")
+               '()
+               `((directory ,parent)))
+         ;; Note: a relative file name is used for compatibility with
+         ;; relocatable packs.
+         (,source -> ,(relative-file-name parent target)))))))
+
 (define (directives store)
   "Return a list of directives to populate the root file system that will host
 STORE."
diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm
index de9bc8f98d..bd95329c5c 100644
--- a/guix/scripts/environment.scm
+++ b/guix/scripts/environment.scm
@@ -33,8 +33,10 @@  (define-module (guix scripts environment)
   #:use-module ((guix gexp) #:select (lower-object))
   #:use-module (guix scripts)
   #:use-module (guix scripts build)
+  #:use-module ((guix scripts pack) #:select (symlink-spec-option-parser))
   #:use-module (guix transformations)
   #:autoload   (ice-9 ftw) (scandir)
+  #:use-module (gnu build install)
   #:autoload   (gnu build linux-container) (call-with-container %namespaces
                                             user-namespace-supported?
                                             unprivileged-user-namespace-supported?
@@ -120,6 +122,9 @@  (define (show-environment-options-help)
       --expose=SPEC      for containers, expose read-only host file system
                          according to SPEC"))
   (display (G_ "
+  -S, --symlink=SPEC     for containers, add symlinks to the profile according
+                         to SPEC, e.g. \"/usr/bin/env=bin/env\"."))
+  (display (G_ "
   -v, --verbosity=LEVEL  use the given verbosity LEVEL"))
   (display (G_ "
       --bootstrap        use bootstrap binaries to build the environment")))
@@ -157,6 +162,7 @@  (define (show-help)
 (define %default-options
   `((system . ,(%current-system))
     (substitutes? . #t)
+    (symlinks . ())
     (offload? . #t)
     (graft? . #t)
     (print-build-trace? . #t)
@@ -256,6 +262,7 @@  (define %options
                    (alist-cons 'file-system-mapping
                                (specification->file-system-mapping arg #f)
                                result)))
+         (option '(#\S "symlink") #t #f symlink-spec-option-parser)
          (option '(#\r "root") #t #f
                  (lambda (opt name arg result)
                    (alist-cons 'gc-root arg result)))
@@ -672,7 +679,7 @@  (define* (launch-environment/fork command profile manifest
 (define* (launch-environment/container #:key command bash user user-mappings
                                        profile manifest link-profile? network?
                                        map-cwd? emulate-fhs? (setup-hook #f)
-                                       (white-list '()))
+                                       (symlinks '()) (white-list '()))
   "Run COMMAND within a container that features the software in PROFILE.
 Environment variables are set according to the search paths of MANIFEST.  The
 global shell is BASH, a file name for a GNU Bash binary in the store.  When
@@ -690,6 +697,9 @@  (define* (launch-environment/container #:key command bash user user-mappings
 LINK-PROFILE? creates a symbolic link from ~/.guix-profile to the
 environment profile.
 
+SYMLINKS must be a list of (SOURCE -> TARGET) tuples denoting symlinks to be
+added to the container.
+
 Preserve environment variables whose name matches the one of the regexps in
 WHILE-LIST."
   (define (optional-mapping->fs mapping)
@@ -797,6 +807,10 @@  (define fhs-mappings
             (mkdir-p home-dir)
             (setenv "HOME" home-dir)
 
+            ;; Create symlinks.
+            (for-each (cut evaluate-populate-directive <> ".")
+                      (append-map (make-symlink->directives profile) symlinks))
+
             ;; Call an additional setup procedure, if provided.
             (when setup-hook
               (setup-hook profile))
@@ -970,6 +984,7 @@  (define (guix-environment* opts)
     (let* ((pure?        (assoc-ref opts 'pure))
            (container?   (assoc-ref opts 'container?))
            (link-prof?   (assoc-ref opts 'link-profile?))
+           (symlinks     (assoc-ref opts 'symlinks))
            (network?     (assoc-ref opts 'network?))
            (no-cwd?      (assoc-ref opts 'no-cwd?))
            (emulate-fhs? (assoc-ref opts 'emulate-fhs?))
@@ -1010,15 +1025,17 @@  (define-syntax-rule (with-store/maybe store exp ...)
 
       (when container? (assert-container-features))
 
-      (when (and (not container?) link-prof?)
-        (leave (G_ "'--link-profile' cannot be used without '--container'~%")))
-      (when (and (not container?) user)
-        (leave (G_ "'--user' cannot be used without '--container'~%")))
-      (when (and (not container?) no-cwd?)
-        (leave (G_ "--no-cwd cannot be used without '--container'~%")))
-      (when (and (not container?) emulate-fhs?)
-        (leave (G_ "'--emulate-fhs' cannot be used without '--container~%'")))
-
+      (when (not container?)
+        (when link-prof?
+          (leave (G_ "'--link-profile' cannot be used without '--container'~%")))
+        (when user
+          (leave (G_ "'--user' cannot be used without '--container'~%")))
+        (when no-cwd?
+          (leave (G_ "--no-cwd cannot be used without '--container'~%")))
+        (when emulate-fhs?
+          (leave (G_ "'--emulate-fhs' cannot be used without '--container~%'")))
+        (when (pair? symlinks)
+          (leave (G_ "'--symlink' cannot be used without '--container~%'"))))
 
       (with-store/maybe store
         (with-status-verbosity (assoc-ref opts 'verbosity)
@@ -1099,6 +1116,7 @@  (define manifest
                                                     #:network? network?
                                                     #:map-cwd? (not no-cwd?)
                                                     #:emulate-fhs? emulate-fhs?
+                                                    #:symlinks symlinks
                                                     #:setup-hook
                                                     (and emulate-fhs?
                                                          setup-fhs))))
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm
index 06849e4761..e3bddc4274 100644
--- a/guix/scripts/pack.scm
+++ b/guix/scripts/pack.scm
@@ -61,7 +61,9 @@  (define-module (guix scripts pack)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-37)
   #:use-module (ice-9 match)
-  #:export (self-contained-tarball
+  #:export (symlink-spec-option-parser
+
+            self-contained-tarball
             debian-archive
             docker-image
             squashfs-image
@@ -160,6 +162,21 @@  (define str (string-join names "-"))
           ((_) str)
           ((names ... _) (loop names))))))
 
+(define (symlink-spec-option-parser opt name arg result)
+  "A SRFI-37 option parser for the --symlink option."
+  ;; Note: Using 'string-split' allows us to handle empty
+  ;; TARGET (as in "/opt/guile=", meaning that /opt/guile is
+  ;; a symlink to the profile) correctly.
+  (match (string-split arg (char-set #\=))
+    ((source target)
+     (let ((symlinks (assoc-ref result 'symlinks)))
+       (alist-cons 'symlinks
+                   `((,source -> ,target) ,@symlinks)
+                   (alist-delete 'symlinks result eq?))))
+    (x
+     (leave (G_ "~a: invalid symlink specification~%")
+            arg))))
+
 
 ;;;
 ;;; Tarball format.
@@ -204,30 +221,15 @@  (define (import-module? module)
         (use-modules (guix build pack)
                      (guix build store-copy)
                      (guix build utils)
-                     ((guix build union) #:select (relative-file-name))
                      (gnu build install)
                      (srfi srfi-1)
-                     (srfi srfi-26)
-                     (ice-9 match))
+                     (srfi srfi-26))
 
         (define %root "root")
 
-        (define symlink->directives
-          ;; Return "populate directives" to make the given symlink and its
-          ;; parent directories.
-          (match-lambda
-            ((source '-> target)
-             (let ((target (string-append #$profile "/" target))
-                   (parent (dirname source)))
-               ;; Never add a 'directory' directive for "/" so as to
-               ;; preserve its ownership when extracting the archive (see
-               ;; below), and also because this would lead to adding the
-               ;; same entries twice in the tarball.
-               `(,@(if (string=? parent "/")
-                       '()
-                       `((directory ,parent)))
-                 (,source
-                  -> ,(relative-file-name parent target)))))))
+        ;; Return "populate directives" to make the given symlink and its
+        ;; parent directories.
+        (define symlink->directives (make-symlink->directives #$profile))
 
         (define directives
           ;; Fully-qualified symlinks.
@@ -1208,20 +1210,7 @@  (define %options
                  (lambda (opt name arg result)
                    (alist-cons 'compressor (lookup-compressor arg)
                                result)))
-         (option '(#\S "symlink") #t #f
-                 (lambda (opt name arg result)
-                   ;; Note: Using 'string-split' allows us to handle empty
-                   ;; TARGET (as in "/opt/guile=", meaning that /opt/guile is
-                   ;; a symlink to the profile) correctly.
-                   (match (string-split arg (char-set #\=))
-                     ((source target)
-                      (let ((symlinks (assoc-ref result 'symlinks)))
-                        (alist-cons 'symlinks
-                                    `((,source -> ,target) ,@symlinks)
-                                    (alist-delete 'symlinks result eq?))))
-                     (x
-                      (leave (G_ "~a: invalid symlink specification~%")
-                             arg)))))
+         (option '(#\S "symlink") #t #f symlink-spec-option-parser)
          (option '("save-provenance") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'save-provenance? #t result)))
diff --git a/tests/guix-shell.sh b/tests/guix-shell.sh
index 9a6b055264..32dd997fe7 100644
--- a/tests/guix-shell.sh
+++ b/tests/guix-shell.sh
@@ -20,6 +20,8 @@ 
 # Test the 'guix shell' alias.
 #
 
+. tests/utils.sh
+
 guix shell --version
 
 configdir="t-guix-shell-config-$$"
@@ -32,6 +34,21 @@  export XDG_CONFIG_HOME
 
 guix shell --bootstrap --pure guile-bootstrap -- guile --version
 
+# '--symlink' can only be used with --container.
+! guix shell --bootstrap guile-bootstrap -S /dummy=bin/guile
+
+if has_container_support; then
+    # '--symlink' works.
+    echo "TESTING SYMLINK IN CONTAINER"
+    guix shell --bootstrap guile-bootstrap --container \
+         --symlink=/usr/bin/guile=bin/guile -- \
+         /usr/bin/guile --version
+
+    # A bad symlink spec causes the command to fail.
+    ! guix shell --bootstrap -CS bin/guile=/usr/bin/guile guile-bootstrap \
+      -- exit
+fi
+
 # '--ad-hoc' is a thing of the past.
 ! guix shell --ad-hoc guile-bootstrap