diff mbox series

[bug#57050,v2,04/13] gnu: Add Zuo.

Message ID 285372112d70bcb4011d6c085592787df4f57d44.1660215295.git.philip@philipmcgrath.com
State Accepted
Headers show
Series gnu: Update Racket to 8.6. Add Zuo. | expand

Checks

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

Commit Message

Philip McGrath Aug. 11, 2022, 11:08 a.m. UTC
* gnu/packages/patches/racket-backport-8.6-zuo.patch,
gnu/packages/patches/racket-zuo-bin-sh.patch: New patches.
* gnu/local.mk (dist_patch_DATA): Add them.
* gnu/packages/racket.scm (%zuo-version): New variable.
(zuo): New variable.
---
 gnu/local.mk                                  |   2 +
 .../patches/racket-backport-8.6-zuo.patch     | 481 ++++++++++++++++++
 gnu/packages/patches/racket-zuo-bin-sh.patch  |  72 +++
 gnu/packages/racket.scm                       |  64 +++
 4 files changed, 619 insertions(+)
 create mode 100644 gnu/packages/patches/racket-backport-8.6-zuo.patch
 create mode 100644 gnu/packages/patches/racket-zuo-bin-sh.patch

Comments

Liliana Marie Prikler Aug. 11, 2022, 11:31 a.m. UTC | #1
Am Donnerstag, dem 11.08.2022 um 07:08 -0400 schrieb Philip McGrath:
> [...]
>  
>  (define %racket-version "8.5") ; Remember to update chez-scheme-for-
> racket!
> +(define %zuo-version "1.0") ; defined in racket/src/zuo/zuo.c
Is that publicly visible?
>  (define %racket-commit
>    (string-append "v" %racket-version))
>  (define %racket-origin
> @@ -233,6 +234,69 @@ (define %racket-origin
>           ;; Unbundle libffi.
>           (delete-file-recursively
> "racket/src/bc/foreign/libffi")))))
>  
> +
> +(define-public zuo
> +  (let ((revision #f))
> +    (package
> +      (name "zuo")
> +      (version (string-append %zuo-version
> +                              "-racket"
> +                              "8.6"
> +                              (if revision "-guix" "")
> +                              (or revision "")))
Is this really needed?  If we expect to maintain zuo split from racket,
I'd rather go with https://github.com/racket/zuo and git-version.
> +      (source
> +       (origin
> +         (inherit %racket-origin)
> +         (uri (git-reference
> +               (url "https://github.com/racket/racket")
> +               (commit "v8.6")))
> +         (sha256
> +          (base32
> "1yi36nr7zrdwrnvpmliirxxjz4pyfyhkar6yvk3rapvmg4q2vmnk"))
> +         (patches (append (origin-patches %racket-origin)
> +                          (search-patches "racket-backport-8.6-
> zuo.patch"
> +                                          "racket-zuo-bin-
> sh.patch")))
> +         (file-name (git-file-name "racket" "8.6"))))
> +      (outputs '("out" "debug"))
> +      (build-system gnu-build-system)
> +      (inputs (list bash-minimal))
> +      (native-inputs (list bash-minimal))
> +      (arguments
> +       (list
> +        #:configure-flags
> +        #~`(,(string-append "CPPFLAGS=-DGUIX_RKTIO_BIN_SH="
> +                            #$(file-append (this-package-input
> "bash-minimal")
> +                                           "/bin/sh"))
As with chez-scheme, I do think using a Racket-agnostic macro name is
helpful here.
> +            #+@(if (%current-target-system)
> +                   (list #~,(string-append
> +                             "CPPFLAGS_FOR_BUILD=-
> DGUIX_RKTIO_BIN_SH="
> +                             #+(file-append
> +                                (this-package-native-input "bash-
> minimal")
> +                                "/bin/sh")))
> +                   '()))
> +        #:out-of-source? #t
> +        #:phases
> +        #~(modify-phases %standard-phases
> +            (add-after 'unpack 'chdir
> +              (lambda args
> +                (chdir "racket/src/zuo"))))))
> +      (home-page "https://github.com/racket/zuo")
> +      ;; ^ This is downstream of https://github.com/racket/racket,
> +      ;; but it's designed to be a friendly landing place
> +      (synopsis "Tiny Racket for build scripts")
> +      (description "You should use Racket to write scripts.
Sorry, but I prefer Guile.

>   But what if you
> +need something much smaller than Racket for some reason—or what if
> you're
> +trying to script a build of Racket itself?  Zuo is a tiny Racket
> with
> +primitives for dealing with files and running processes, and it
> comes with a
> +@command{make}-like embedded DSL.
> +
> +Zuo (作) is a Racket variant in the sense that program files start
> with
> +@code{#lang}, and the module path after @code{#lang} determines the
> parsing
> +and expansion of the file content.  That's how the @command{make}-
> like DSL is
> +defined, and even the base Zuo language is defined by layers of
> @code{#lang}s.
> +One of the early layers implements macros.")
> +      (license (list license:asl2.0 license:expat)))))
Rarely have I read a description this long, yet uninformative.  Let me
shorten that a little: "Zuo is a tiny Racket variant that can be used
to bootstrap Racket.  It comes with primitives for dealing with files
and processes, as well as a @command{make}-like DSL."

Cheers
Philip McGrath Aug. 11, 2022, 2 p.m. UTC | #2
Hi,

On Thu, Aug 11, 2022, at 7:31 AM, Liliana Marie Prikler wrote:
> Am Donnerstag, dem 11.08.2022 um 07:08 -0400 schrieb Philip McGrath:
>> [...]
>>  
>>  (define %racket-version "8.5") ; Remember to update chez-scheme-for-
>> racket!
>> +(define %zuo-version "1.0") ; defined in racket/src/zuo/zuo.c
> Is that publicly visible?

For example:

    echo "#lang zuo (hash-ref (runtime-env) 'version)" | zuo ""

See also https://racket.discourse.group/t/writing-the-zuo-version-number/1105

I likewise have comments in the definitions of 'chez-scheme' and 'chez-scheme-for-racket' explaining where in the source code to find the version number.

>> +
>> +(define-public zuo
>> +  (let ((revision #f))
>> +    (package
>> +      (name "zuo")
>> +      (version (string-append %zuo-version
>> +                              "-racket"
>> +                              "8.6"
>> +                              (if revision "-guix" "")
>> +                              (or revision "")))
> Is this really needed?  If we expect to maintain zuo split from racket,
> I'd rather go with https://github.com/racket/zuo and git-version.

I mean, I don't think it's profoundly essential, but I've been using it for the last four months. I don't see any harm in leaving the level of indentation there so it can be used more easily if it is needed or wanted.

Building from https://github.com/racket/zuo would basically never be useful. It is generated intermittently from https://github.com/racket/racket using `git subtree`.

>> +         (file-name (git-file-name "racket" "8.6"))))
>> +      (outputs '("out" "debug"))
>> +      (build-system gnu-build-system)
>> +      (inputs (list bash-minimal))
>> +      (native-inputs (list bash-minimal))
>> +      (arguments
>> +       (list
>> +        #:configure-flags
>> +        #~`(,(string-append "CPPFLAGS=-DGUIX_RKTIO_BIN_SH="
>> +                            #$(file-append (this-package-input
>> "bash-minimal")
>> +                                           "/bin/sh"))
> As with chez-scheme, I do think using a Racket-agnostic macro name is
> helpful here.

I'm planning to respond in the other thread about the possibility of a truly generic macro name, but I hope it doesn't need to become an issue blocking this patch series. For now, I'm not entirely sure what "Racket-agnostic" means; the bottom line for my is I think it would be absurdly awful to have to write, e.g. if cross-compiling using `distro-build` with the top-level Makefile:

    ./configure CPPFLAGS="GUIX_RKTIO_BIN_SH=/input/bin/sh GUIX_ZUO_BIN_SH=/input/bin/sh GUIX_CHEZ_BIN_SH=/input/bin/sh" CPPFLAGS_FOR_BUILD="GUIX_RKTIO_BIN_SH=/native-input/bin/sh GUIX_ZUO_BIN_SH=/native-input/bin/sh GUIX_CHEZ_BIN_SH=/native-input/bin/sh"

>> +      (home-page "https://github.com/racket/zuo")
>> +      ;; ^ This is downstream of https://github.com/racket/racket,
>> +      ;; but it's designed to be a friendly landing place
>> +      (synopsis "Tiny Racket for build scripts")
>> +      (description "You should use Racket to write scripts.
> Sorry, but I prefer Guile.

(At the risk of responding seriously to what was probably meant to be a joke:) I've never tried to use Guile on Windows, but, given that the manual chapter is called "POSIX System Calls and Networking", it's not clear to me that Guile provides as portable and powerful "primitives for dealing with files and running processes" as Zuo, let alone Racket.

>
>>   But what if you
>> +need something much smaller than Racket for some reason—or what if
>> you're
>> +trying to script a build of Racket itself?  Zuo is a tiny Racket
>> with
>> +primitives for dealing with files and running processes, and it
>> comes with a
>> +@command{make}-like embedded DSL.
>> +
>> +Zuo (作) is a Racket variant in the sense that program files start
>> with
>> +@code{#lang}, and the module path after @code{#lang} determines the
>> parsing
>> +and expansion of the file content.  That's how the @command{make}-
>> like DSL is
>> +defined, and even the base Zuo language is defined by layers of
>> @code{#lang}s.
>> +One of the early layers implements macros.")
>> +      (license (list license:asl2.0 license:expat)))))
> Rarely have I read a description this long, yet uninformative.  Let me
> shorten that a little: "Zuo is a tiny Racket variant that can be used
> to bootstrap Racket.  It comes with primitives for dealing with files
> and processes, as well as a @command{make}-like DSL."
>

This is the description from the Zuo documentation and readme file. I find your shortened version less clear than the original. The details about `#lang` as a Racketeer are important to understand in what sense Zuo is a "Racket variant", given that it is not currently possible to run `#lang zuo` programs using the `racket` executable. (IIUC someone is working on a Racket package implementing `#lang zuo/kernel`, at which point that will change.) I also think "can be used to bootstrap Racket" greatly underplays the potential of Zuo. A number people have already taken an interest in or started experimenting with Zuo outside of building Racket.

-Philip
Liliana Marie Prikler Aug. 11, 2022, 3:34 p.m. UTC | #3
Hi,

Am Donnerstag, dem 11.08.2022 um 10:00 -0400 schrieb Philip McGrath:
> 
> > If we expect to maintain zuo split from racket, I'd rather go with
> > https://github.com/racket/zuo and git-version.
> 
> I mean, I don't think it's profoundly essential, but I've been using
> it for the last four months. I don't see any harm in leaving the
> level of indentation there so it can be used more easily if it is
> needed or wanted.
> 
> Building from https://github.com/racket/zuo would basically never be
> useful. It is generated intermittently from
> https://github.com/racket/racket using `git subtree`.
I don't think that matters much in the grand scheme of things (pun
intended).  Rather, having such subtrees available is beneficial in and
of itself, because it means we don't have to split the monorepo.  At
the very least, it's one chdir less.  Now, building from the zuo
subtree is not a requirement, but imho it would make a better case for
versioning.

> 
> > > +         (file-name (git-file-name "racket" "8.6"))))
> > > +      (outputs '("out" "debug"))
> > > +      (build-system gnu-build-system)
> > > +      (inputs (list bash-minimal))
> > > +      (native-inputs (list bash-minimal))
> > > +      (arguments
> > > +       (list
> > > +        #:configure-flags
> > > +        #~`(,(string-append "CPPFLAGS=-DGUIX_RKTIO_BIN_SH="
> > > +                            #$(file-append (this-package-input
> > > "bash-minimal")
> > > +                                           "/bin/sh"))
> > As with chez-scheme, I do think using a Racket-agnostic macro name
> > is helpful here.
> 
> I'm planning to respond in the other thread about the possibility of
> a truly generic macro name, but I hope it doesn't need to become an
> issue blocking this patch series. For now, I'm not entirely sure what
> "Racket-agnostic" means; the bottom line for my is I think it would
> be absurdly awful to have to write, e.g. if cross-compiling using
> `distro-build` with the top-level Makefile:
> 
>     ./configure CPPFLAGS="GUIX_RKTIO_BIN_SH=/input/bin/sh
> GUIX_ZUO_BIN_SH=/input/bin/sh GUIX_CHEZ_BIN_SH=/input/bin/sh"
> CPPFLAGS_FOR_BUILD="GUIX_RKTIO_BIN_SH=/native-input/bin/sh
> GUIX_ZUO_BIN_SH=/native-input/bin/sh GUIX_CHEZ_BIN_SH=/native-
> input/bin/sh"
GUIX_CHEZ_BIN_SH would semantically cover all four however, no? 
(Ignoring more generic options for now.)

> > > +      (home-page "https://github.com/racket/zuo")
> > > +      ;; ^ This is downstream of
> > > https://github.com/racket/racket,
> > > +      ;; but it's designed to be a friendly landing place
> > > +      (synopsis "Tiny Racket for build scripts")
> > > +      (description "You should use Racket to write scripts.
> > Sorry, but I prefer Guile.
> 
> (At the risk of responding seriously to what was probably meant to be
> a joke:) I've never tried to use Guile on Windows, but, given that
> the manual chapter is called "POSIX System Calls and Networking",
> it's not clear to me that Guile provides as portable and powerful
> "primitives for dealing with files and running processes" as Zuo, let
> alone Racket.
At the risk of responding seriously to what was probably meant to be a
joke, I don't use Winblows 😉️

> > 
> > >   But what if you
> > > +need something much smaller than Racket for some reason—or what
> > > if
> > > you're
> > > +trying to script a build of Racket itself?  Zuo is a tiny Racket
> > > with
> > > +primitives for dealing with files and running processes, and it
> > > comes with a
> > > +@command{make}-like embedded DSL.
> > > +
> > > +Zuo (作) is a Racket variant in the sense that program files
> > > start
> > > with
> > > +@code{#lang}, and the module path after @code{#lang} determines
> > > the
> > > parsing
> > > +and expansion of the file content.  That's how the
> > > @command{make}-
> > > like DSL is
> > > +defined, and even the base Zuo language is defined by layers of
> > > @code{#lang}s.
> > > +One of the early layers implements macros.")
> > > +      (license (list license:asl2.0 license:expat)))))
> > Rarely have I read a description this long, yet uninformative.  Let
> > me
> > shorten that a little: "Zuo is a tiny Racket variant that can be
> > used
> > to bootstrap Racket.  It comes with primitives for dealing with
> > files
> > and processes, as well as a @command{make}-like DSL."
> > 
> 
> This is the description from the Zuo documentation and readme file. I
> find your shortened version less clear than the original. The details
> about `#lang` as a Racketeer are important to understand in what
> sense Zuo is a "Racket variant", given that it is not currently
> possible to run `#lang zuo` programs using the `racket` executable.
Would it make sense to call zuo a "dialect" then?

> (IIUC someone is working on a Racket package implementing `#lang
> zuo/kernel`, at which point that will change.) I also think "can be
> used to bootstrap Racket" greatly underplays the potential of Zuo. A
> number people have already taken an interest in or started
> experimenting with Zuo outside of building Racket.
I have not claimed that it's use is limited to bootstrapping Racket. 
Rather, I think this bootstrapping capability shows that Zuo can be
used to do real programming™.

Cheers
Philip McGrath Aug. 11, 2022, 11:32 p.m. UTC | #4
Hi,

On Thu, Aug 11, 2022, at 11:34 AM, Liliana Marie Prikler wrote:
> Am Donnerstag, dem 11.08.2022 um 10:00 -0400 schrieb Philip McGrath:
>> I'm planning to respond in the other thread about the possibility of
>> a truly generic macro name, but I hope it doesn't need to become an
>> issue blocking this patch series. For now, I'm not entirely sure what
>> "Racket-agnostic" means; the bottom line for my is I think it would
>> be absurdly awful to have to write, e.g. if cross-compiling using
>> `distro-build` with the top-level Makefile:
>> 
>>     ./configure CPPFLAGS="GUIX_RKTIO_BIN_SH=/input/bin/sh
>> GUIX_ZUO_BIN_SH=/input/bin/sh GUIX_CHEZ_BIN_SH=/input/bin/sh"
>> CPPFLAGS_FOR_BUILD="GUIX_RKTIO_BIN_SH=/native-input/bin/sh
>> GUIX_ZUO_BIN_SH=/native-input/bin/sh GUIX_CHEZ_BIN_SH=/native-
>> input/bin/sh"
> GUIX_CHEZ_BIN_SH would semantically cover all four however, no? 
> (Ignoring more generic options for now.)
>

I don't think so. Zuo and Racket BC have at least as little to do with Chez as upstream Chez has to do with rktio.

>> > > +      (home-page "https://github.com/racket/zuo")
>> > > +      ;; ^ This is downstream of
>> > > https://github.com/racket/racket,
>> > > +      ;; but it's designed to be a friendly landing place
>> > > +      (synopsis "Tiny Racket for build scripts")
>> > > +      (description "You should use Racket to write scripts.
>> > Sorry, but I prefer Guile.
>> 
>> (At the risk of responding seriously to what was probably meant to be
>> a joke:) I've never tried to use Guile on Windows, but, given that
>> the manual chapter is called "POSIX System Calls and Networking",
>> it's not clear to me that Guile provides as portable and powerful
>> "primitives for dealing with files and running processes" as Zuo, let
>> alone Racket.
> At the risk of responding seriously to what was probably meant to be a
> joke, I don't use Winblows 😉️
>

If you want to be horrified, read <https://docs.racket-lang.org/reference/windowspaths.html>. I'm very glad Racket manages all that complexity for me. (And in fairness, even on Unix, there are valid paths which can not be represented as Scheme strings.)

-Philip
M Aug. 16, 2022, 2:47 p.m. UTC | #5
On 11-08-2022 16:00, Philip McGrath wrote:
>>> +        #~`(,(string-append "CPPFLAGS=-DGUIX_RKTIO_BIN_SH="
>>> +                            #$(file-append (this-package-input
>>> "bash-minimal")
>>> +                                           "/bin/sh"))
>> As with chez-scheme, I do think using a Racket-agnostic macro name is
>> helpful here.
> I'm planning to respond in the other thread about the possibility of a truly generic macro name, but I hope it doesn't need to become an issue blocking this patch series. For now, I'm not entirely sure what "Racket-agnostic" means; the bottom line for my is I think it would be absurdly awful to have to write, e.g. if cross-compiling using `distro-build` with the top-level Makefile:
>
>      ./configure CPPFLAGS="GUIX_RKTIO_BIN_SH=/input/bin/sh GUIX_ZUO_BIN_SH=/input/bin/sh GUIX_CHEZ_BIN_SH=/input/bin/sh" CPPFLAGS_FOR_BUILD="GUIX_RKTIO_BIN_SH=/native-input/bin/sh GUIX_ZUO_BIN_SH=/native-input/bin/sh GUIX_CHEZ_BIN_SH=/native-input/bin/sh"

Example: GUIX_SH=/inputs/bin/sh.

I haven't been following the discussion on the other patches, but didn't 
I give an example of something independent of the Racket component in 
use and even independent of Racket itself? See the suggestion of using 
the already existing _PATH_BSHELL from <paths.h>. It's even not 
Guix-specific, apparently it's a BSD-ism!

Greetings,

Maxime.
Philip McGrath Aug. 23, 2022, 1:40 a.m. UTC | #6
Hi,

On Tue, Aug 16, 2022, at 10:47 AM, Maxime Devos wrote:
> On 11-08-2022 16:00, Philip McGrath wrote:
>>>> +        #~`(,(string-append "CPPFLAGS=-DGUIX_RKTIO_BIN_SH="
>>>> +                            #$(file-append (this-package-input
>>>> "bash-minimal")
>>>> +                                           "/bin/sh"))
>>>> 
>>> As with chez-scheme, I do think using a Racket-agnostic macro name is
>>> helpful here.
>>> 
>> I'm planning to respond in the other thread about the possibility of a truly generic macro name, but I hope it doesn't need to become an issue blocking this patch series. For now, I'm not entirely sure what "Racket-agnostic" means; the bottom line for my is I think it would be absurdly awful to have to write, e.g. if cross-compiling using `distro-build` with the top-level Makefile:
>> 
>>     ./configure CPPFLAGS="GUIX_RKTIO_BIN_SH=/input/bin/sh GUIX_ZUO_BIN_SH=/input/bin/sh GUIX_CHEZ_BIN_SH=/input/bin/sh" CPPFLAGS_FOR_BUILD="GUIX_RKTIO_BIN_SH=/native-input/bin/sh GUIX_ZUO_BIN_SH=/native-input/bin/sh GUIX_CHEZ_BIN_SH=/native-input/bin/sh"
>
> Example: GUIX_SH=/inputs/bin/sh.
> 

I will use GUIX_SH in v3 of this series.

My concern with it originally was that it's generic enough that it might be used in other ways elsewhere in Guix, but, since I'm hoping it's only going to be a medium-term solution, it seems good enough, and I haven't heard any objections to it.

> I haven't been following the discussion on the other patches, but didn't I give an example of something independent of the Racket component in use and even independent of Racket itself? See the suggestion of using the already existing _PATH_BSHELL from <paths.h>. It's even not Guix-specific, apparently it's a BSD-ism!
> 

On Wed, Aug 10, 2022, at 7:46 AM, Maxime Devos wrote:
> On 09-08-2022 23:58, Philip McGrath wrote:
>
>> On Tuesday, August 9, 2022 5:38:56 PM EDT ( wrote:
>>> On Tue Aug 9, 2022 at 10:24 PM BST, Maxime Devos wrote:
>>>> In the glibc headers, there's some (POSIX?) standard macro that points
>>>> at "/gnu/store/.../bin/sh" (I don't recall the name), any reason we
>>>> aren't using that macro?  That would be Guix-independent. I'm not sure
>>>> if a /gnu/store/... prefix is included, but if not, maybe we could try
>>>> overriding it with -D...="/gnu/store/...", or failing that, add a
>>>> post-unpack substitute* replacing [the macro name] ->
>>>> "/gnu/store/.../bin/sh".
>>> I believe you might be referring to <paths.h>, which defines _PATH_BSHELL.
>>>
>>> It's not standard C <https://en.cppreference.com/w/c/header> nor POSIX
>>> <https://pubs.opengroup.org/onlinepubs/9699919799/idx/head.html> though.
>>>
>>>      -- (
>
> Looking at the "paths.h" header, it appears to be a BSDism. Not really 
> standard but still better than a Guix-ism.
>
>> I'd love to be wrong, but I also can't find such a macro. In the glibc source
>> tree, "stdlib/system.c" defines a stub implementation that always fails with
>> ENOSYS, and "sysdeps/posix/system.c" contains:
>>
>>      #define	SHELL_PATH	"/bin/sh"	/* Path of the shell.  */
>>      #define	SHELL_NAME	"sh"	/* Name to give it.  */
>>
>> Concretely, I think Guix's glibc currently uses /bin/sh dynamically: in my
>> Chez example above, if you replace `process` with `system` (which uses libc's
>> `system`), the result is always "/bin/sh\n".
>
> If so, that's a bug.  I do not know what result you are referring to.

(Disregard this part; I think I was thinking about some other way I had tried things.)

>
> Anyway, the Guix package definition of glibc substitutes _PATH_BSHELL 
> and SHELL_PATH, so unless there's a bug, it doesn't depend on /bin/sh.
>

I have been looking further into options for addressing this upstream.

First of all, I have found that there *is* another Unix-like system where "/bin/sh" doesn't exist: on Android, the POSIX shell is usually at "/system/bin/sh". Also, at least on some versions, _PATH_BSHELL isn't a compile-time constant. It is:

    #define _PATH_BSHELL __bionic_get_shell_path()

(There are also systems where "/bin/sh" is some non-POSIX shell and the POSIX shell is at "/usr/xpg4/bin/sh". If changing this upstream, Racket may need to decide whether POSIX compatibility or historical compatibility is more important there.)

I've found that there does seem to be a POSIX recommendation for finding "sh". The POSIX spec for `system` <https://pubs.opengroup.org/onlinepubs/9699919799/functions/system.html> says, under "Application Usage", "There is no defined way for an application to find the specific path for the shell. However, confstr() can provide a value for PATH that is guaranteed to find the sh utility." Similarly, <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html> says that "applications should note that the standard PATH to the shell cannot be assumed to be either /bin/sh or /usr/bin/sh, and should be determined by interrogation of the PATH returned by getconf PATH, ensuring that the returned pathname is an absolute pathname and not a shell built-in." Most emphatically, <https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html> says in the normative "Description":

>
> If the implementation supports the POSIX shell option, the string stored in buf after a call to:
>
>     confstr(_CS_PATH, buf, sizeof(buf))
>
> can be used as a value of the PATH environment variable that accesses all of the standard utilities of POSIX.1-2017, that are provided in a manner accessible via the exec family of functions, if the return value is less than or equal to sizeof(buf).
>

However, apparently using `confstr` with `_CS_PATH` does not give a useful result in Guix build environments. Try building the following package with `guix build -f`: I've put the interesting log output in the description. In particular, note that *both* bash-minimal and bash-static are present!

--8<---------------cut here---------------start------------->8---
(use-modules
 (guix build-system gnu)
 (guix gexp)
 ((guix licenses) #:prefix license:)
 (guix packages))
(define src
  (plain-file "demo.c"
              "#include <stdlib.h>
#include <stdio.h>
#include <paths.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
  puts(_PATH_BSHELL);
  size_t buf_len = confstr(_CS_PATH, NULL, 0);
  char* buf = malloc(buf_len);
  if (NULL == buf) {
    return 1;
  };
  confstr(_CS_PATH, buf, buf_len);
  puts(buf);
  fflush(stdout);
  int status = system(\"echo $BASH\");
  fflush(stdout);
  printf(\"status: %i\\n\", status);
  return 0;
}
"))
(package
  (name "libc-system-demo")
  (version "0")
  (source src)
  (build-system gnu-build-system)
  (arguments
   (list
    #:phases
    #~(modify-phases %standard-phases
        (delete 'configure)
        (replace 'build
          (lambda args
            (invoke "gcc" "-o" "demo" #$src)))
        (replace 'check
          (lambda args
            (invoke "./demo")))
        (replace 'install
          (lambda args
            (install-file "demo" (string-append #$output "/bin")))))))
  (home-page "https://issues.guix.gnu.org/57050")
  (synopsis "Some 'sh'-related values from glibc")
  (description "starting phase `check'
/gnu/store/720rj90bch716isd8z7lcwrnvz28ap4y-bash-static-5.1.8/bin/sh
/bin:/usr/bin
/gnu/store/4y5m9lb8k3qkb1y9m02sw9w9a6hacd16-bash-minimal-5.1.8/bin/sh
status: 0
phase `check' succeeded after 0.0 seconds")
  (license license:cc0))
--8<---------------cut here---------------end--------------->8---

AFAICT, Glibc's `confstr` implementation for `_CS_PATH` doesn't have any mechanism for configuring the search path; it simply uses the compile-time version, `CS_PATH`, which is:

    #define	CS_PATH	"/bin:/usr/bin"

More generally, it seems questionable for our glibc to retain a store reference to Bash (let alone two). Wouldn't that prevent creating containers or packs without a shell present?

After I've sent a v3 of this series, I plan to raise these questions on the guix-devel list. Then, once I have a sense of whether Guix would like to support `confstr` with  `_CS_PATH` as a way of finding the shell, I'll propose some changes to Racket upstream.

-Philip
M Aug. 23, 2022, 9:11 a.m. UTC | #7
On 23-08-2022 03:40, Philip McGrath wrote:

> More generally, it seems questionable for our glibc to retain a store reference to Bash (let alone two). Wouldn't that prevent creating containers or packs without a shell present?

glibc needs to retain a reference to a shell for the 'system' function 
to work.

I don't see what containers or packs have to do with anything, it's the 
same for profiles in general.

Greetings,
Maxime.
M Aug. 23, 2022, 9:20 a.m. UTC | #8
> [...]
> I will use GUIX_SH in v3 of this series.
>
> My concern with it originally was that it's generic enough that it might be used in other ways elsewhere in Guix, but, since I'm hoping it's only going to be a medium-term solution, it seems good enough, and I haven't heard any objections to it.
_PATH_BSHELL seems better to me, as it is not Guix-specific and does not 
require adding preprocessor arguments. Even simpler would be to 
substitute* the /bin/sh to (search-input-file inputs "bin/sh") like done 
for other packages.

I have previously objected to it, though not explicitly:

> I haven't been following the discussion on the other patches, but 
> didn't I give an example of something independent of the Racket 
> component in use and even independent of Racket itself? See the 
> suggestion of using the already existing _PATH_BSHELL from <paths.h>. 
> It's even not Guix-specific, apparently it's a BSD-ism!
>
I don't see the point of a GUIX_SH macro when the standard-ish 
_PATH_BSHELL appears to suffice.

On 23-08-2022 03:40, Philip McGrath wrote:
> First of all, I have found that there*is*  another Unix-like system where "/bin/sh" doesn't exist: on Android, the POSIX shell is usually at "/system/bin/sh". Also, at least on some versions, _PATH_BSHELL isn't a compile-time constant. It is:
>
>      #define _PATH_BSHELL __bionic_get_shell_path()

Looking at the patch, it not being a compile-time constant does not 
appear to be a problem to me.

Greetings,
Maxime.
Philip McGrath Aug. 23, 2022, 11:24 p.m. UTC | #9
On Tue, Aug 23, 2022, at 5:11 AM, Maxime Devos wrote:
> On 23-08-2022 03:40, Philip McGrath wrote:
>
>> More generally, it seems questionable for our glibc to retain a store reference to Bash (let alone two). Wouldn't that prevent creating containers or packs without a shell present?
>
> glibc needs to retain a reference to a shell for the 'system' function 
> to work.
>
> I don't see what containers or packs have to do with anything, it's the 
> same for profiles in general.
>

Without involving Guix, it's possible to create a chroot, container, or various other kinds of environments where a shell is not present. Inside such an environment, the 'system' function "works" by returning 0 if the command is NULL, 127 otherwise.

AFAICT, even when Guix creates a container or otherwise isolated environment, any program linking to glibc pulls along Bash. (Indeed, two different versions of Bash!)

-Philip
Philip McGrath Aug. 24, 2022, 12:27 a.m. UTC | #10
On Tue, Aug 23, 2022, at 5:20 AM, Maxime Devos wrote:
>> [...]
>> I will use GUIX_SH in v3 of this series.
>>
>> My concern with it originally was that it's generic enough that it might be used in other ways elsewhere in Guix, but, since I'm hoping it's only going to be a medium-term solution, it seems good enough, and I haven't heard any objections to it.
> _PATH_BSHELL seems better to me, as it is not Guix-specific and does not 
> require adding preprocessor arguments.

Not needing preprocessor arguments would be a nice advantage. I will try _PATH_BSHELL.

> Even simpler would be to 
> substitute* the /bin/sh to (search-input-file inputs "bin/sh") like done 
> for other packages.
>

Unfortunately, that doesn't work well here. We cannot refer to store paths in Racket or Chez source code (as opposed to C code) because the grafter cannot handle the format of compiled code: see <https://issues.guix.gnu.org/47180#6>. (I don't know about Zuo image dumps, but the binary format is unspecified, so it seems safer not to.)

Also, especially for Racket, there are many entry points where "/bin/sh" might come from, including both functions like 'system' and also programs that explicitly use "/bin/sh" with functions like 'system*'. When I first wrote the current patch, maintaining Guix-specific patches for all of them seemed unsustainable, so I instead patched the low-level function for running subprocesses to interpose on any attempt to execute "/bin/sh", regardless of where it came from.

If I addressed this upstream, I'd probably extend an existing function to support `(find-system-path 'shell)`, change `system` and friends to use it, and likewise change scripts that currently use "/bin/sh" explicitly.

> On 23-08-2022 03:40, Philip McGrath wrote:
>> First of all, I have found that there*is*  another Unix-like system where "/bin/sh" doesn't exist: on Android, the POSIX shell is usually at "/system/bin/sh". Also, at least on some versions, _PATH_BSHELL isn't a compile-time constant. It is:
>>
>>      #define _PATH_BSHELL __bionic_get_shell_path()
>
> Looking at the patch, it not being a compile-time constant does not 
> appear to be a problem to me.
>

It's not an issue with the current patch. It could be relevant for some ways of addressing this upstream. Mostly, it's part of my thinking that "where is the shell, if any?" is a question best answered at runtime rather than compile time.

-Philip
Liliana Marie Prikler Aug. 24, 2022, 5:42 a.m. UTC | #11
Am Dienstag, dem 23.08.2022 um 20:27 -0400 schrieb Philip McGrath:
> 
> > Even simpler would be to substitute* the /bin/sh to (search-input-
> > file inputs "bin/sh") like
> > done for other packages.
> 
> Unfortunately, that doesn't work well here. We cannot refer to store
> paths in Racket or Chez source code (as opposed to C code) because
> the grafter cannot handle the format of compiled code: see
> <https://issues.guix.gnu.org/47180#6>. (I don't know about Zuo image
> dumps, but the binary format is unspecified, so it seems safer not
> to.)
We are talking about some C source code, though, so I doubt this point
is relevant, is it?
Philip McGrath Aug. 24, 2022, 5:47 a.m. UTC | #12
On Wed, Aug 24, 2022, at 1:42 AM, Liliana Marie Prikler wrote:
> Am Dienstag, dem 23.08.2022 um 20:27 -0400 schrieb Philip McGrath:
>> 
>> > Even simpler would be to substitute* the /bin/sh to (search-input-
>> > file inputs "bin/sh") like
>> > done for other packages.
>> 
>> Unfortunately, that doesn't work well here. We cannot refer to store
>> paths in Racket or Chez source code (as opposed to C code) because
>> the grafter cannot handle the format of compiled code: see
>> <https://issues.guix.gnu.org/47180#6>. (I don't know about Zuo image
>> dumps, but the binary format is unspecified, so it seems safer not
>> to.)
> We are talking about some C source code, though, so I doubt this point
> is relevant, is it?

For rktio and Zuo, the string "/bin/sh" doesn't appear in the C source code, only in the Racket/Zuo libraries.

-Philip
Thiago Jung Bauermann Aug. 26, 2022, 9:15 p.m. UTC | #13
Hello Philip,

Philip McGrath <philip@philipmcgrath.com> writes:

> Hi,
>
> Here is a v3 of this series. The major changes from v2 are:
>
>  1. Reflecting the change upstream to the v8.6 tag (which they will not
>     do again);
>
>  2. Changing the patches to use _PATH_BSHELL for "/bin/sh"; and
>
>  3. Incorporating all of the powerpc64le fixes, including the 10x higher
>     STACK_SAFETY_MARGIN that got the build working for Thiago.
>
> I've also made many of the smaller changes requested.
>
> For convienience, I've also pushed this series to the 'zuo' branch of
> https://gitlab.com/philip1/guix-patches and tagged it as
> guix-issue-57050-v3 (commit 9b429caf1d9cd6574debcb11065b957874d2a31a).

Thank you! This version built successfully on powerpc64le-linux.
Ludovic Courtès Sept. 4, 2022, 8:53 p.m. UTC | #14
Hi Philip,

Philip McGrath <philip@philipmcgrath.com> skribis:

> Here is a v4 with the revised description for Zuo (07/14) and the typo fix in
> the commit message for 12/14.
>
> I've once again pushed this series to the 'zuo' branch of
> https://gitlab.com/philip1/guix-patches and tagged it as
> guix-issue-57050-v4 (commit b9652c2a85201f8183346a6e3a4f4fd245649d69).

I applied this series (looks like it was about to be forgotten!), it
seems to work like a charm.  :-)

Thank you Philip for the work, & thanks Liliana, Thiago, Maxime, Efraim,
and all for the review process!

Ludo’.
diff mbox series

Patch

diff --git a/gnu/local.mk b/gnu/local.mk
index 076fa341e9..4a87380350 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1774,8 +1774,10 @@  dist_patch_DATA =						\
   %D%/packages/patches/ripperx-missing-file.patch		\
   %D%/packages/patches/rpcbind-CVE-2017-8779.patch		\
   %D%/packages/patches/rtags-separate-rct.patch			\
+  %D%/packages/patches/racket-backport-8.6-zuo.patch		\
   %D%/packages/patches/racket-chez-scheme-bin-sh.patch		\
   %D%/packages/patches/racket-rktio-bin-sh.patch		\
+  %D%/packages/patches/racket-zuo-bin-sh.patch			\
   %D%/packages/patches/remake-impure-dirs.patch			\
   %D%/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch	\
   %D%/packages/patches/retroarch-LIBRETRO_DIRECTORY.patch	\
diff --git a/gnu/packages/patches/racket-backport-8.6-zuo.patch b/gnu/packages/patches/racket-backport-8.6-zuo.patch
new file mode 100644
index 0000000000..b86679b7ec
--- /dev/null
+++ b/gnu/packages/patches/racket-backport-8.6-zuo.patch
@@ -0,0 +1,481 @@ 
+From 8761fc06b188b9ca2f4b7f2b7d1235075c44a321 Mon Sep 17 00:00:00 2001
+From: Matthew Flatt <mflatt@racket-lang.org>
+Date: Sat, 23 Jul 2022 17:10:58 -0600
+Subject: [PATCH 1/4] Zuo: support cross compilation via `configure` and
+ `CC_FOR_BUILD`
+
+(cherry picked from commit 798a989ba6d1a30c491a3120b2c2f1570ecab911)
+---
+ racket/src/zuo/Makefile.in  |  7 ++++++-
+ racket/src/zuo/README.md    | 10 ++++++++++
+ racket/src/zuo/configure    | 15 +++++++++++++++
+ racket/src/zuo/configure.ac | 11 +++++++++++
+ 4 files changed, 42 insertions(+), 1 deletion(-)
+
+diff --git a/racket/src/zuo/Makefile.in b/racket/src/zuo/Makefile.in
+index 5d16e145bf..747b584c5c 100644
+--- a/racket/src/zuo/Makefile.in
++++ b/racket/src/zuo/Makefile.in
+@@ -17,6 +17,11 @@ CPPFLAGS = @CPPFLAGS@
+ LDFLAGS = @LDFLAGS@
+ LIBS = @LIBS@
+ 
++CC_FOR_BUILD = @CC_FOR_BUILD@
++CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
++LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
++LIBS_FOR_BUILD = @LIBS_FOR_BUILD@
++
+ EMBED_LIBS = @EMBED_LIBS@
+ 
+ .PHONY: zuos-to-run-and-install
+@@ -24,7 +29,7 @@ zuos-to-run-and-install: zuo
+ 	./zuo . zuos-to-run-and-install
+ 
+ zuo: $(srcdir)/zuo.c
+-	$(CC) $(CPPFLAGS) $(CFLAGS) -DZUO_LIB_PATH='"'"$(srcdir)/lib"'"' -o zuo $(srcdir)/zuo.c $(LDFLAGS) $(LIBS)
++	$(CC_FOR_BUILD) $(FLAGS_FOR_BUILD) -DZUO_LIB_PATH='"'"$(srcdir)/lib"'"' -o zuo $(srcdir)/zuo.c $(LDFLAGS_FOR_BUILD) $(LIBS_FOR_BUILD)
+ 
+ .PHONY: check
+ check: zuo
+diff --git a/racket/src/zuo/README.md b/racket/src/zuo/README.md
+index 17c88ee9ec..3aad504b7e 100644
+--- a/racket/src/zuo/README.md
++++ b/racket/src/zuo/README.md
+@@ -84,6 +84,16 @@ A boot image is machine-independent, whether in a stand-alone file or
+ embedded in `.c` source.
+ 
+ 
++Cross Compiling
++---------------
++
++If you use `./configure --host=...` to cross compile, then you will
++also need to add something like `CC_FOR_BUILD=cc` as a `./configure`
++argument to specify the compiler for a `zuo` to use on the build
++machine. If necessary, you can also specify `CFLAGS_FOR_BUILD`,
++`LDFLAGS_FOR_BUILD`, and/or `LIBS_FOR_BUILD`.
++
++
+ Embedding Zuo in Another Application
+ ------------------------------------
+ 
+diff --git a/racket/src/zuo/configure b/racket/src/zuo/configure
+index 1fa34a3fe8..575ce07d96 100755
+--- a/racket/src/zuo/configure
++++ b/racket/src/zuo/configure
+@@ -589,6 +589,10 @@ enable_embed="zuo"
+ ac_subst_vars='LTLIBOBJS
+ LIBOBJS
+ EMBED_LIBS
++LIBS_FOR_BUILD
++LDFLAGS_FOR_BUILD
++CFLAGS_FOR_BUILD
++CC_FOR_BUILD
+ OBJEXT
+ EXEEXT
+ ac_ct_CC
+@@ -2584,6 +2588,17 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
+ ac_compiler_gnu=$ac_cv_c_compiler_gnu
+ 
+ 
++if test "${CC_FOR_BUILD}" = ""; then
++  CC_FOR_BUILD='$(CC) -O2'
++  CFLAGS_FOR_BUILD='$(CPPFLAGS) $(CFLAGS)'
++  LDFLAGS_FOR_BUILD='$(LDFLAGS)'
++  LIBS_FOR_BUILD='$(LIBS)'
++fi
++
++
++
++
++
+ 
+ { $as_echo "$as_me:${as_lineno-$LINENO}: zuo libraries to embed: \"${EMBED_LIBS}\"" >&5
+ $as_echo "$as_me: zuo libraries to embed: \"${EMBED_LIBS}\"" >&6;}
+diff --git a/racket/src/zuo/configure.ac b/racket/src/zuo/configure.ac
+index 89b3c6391d..598ff79629 100644
+--- a/racket/src/zuo/configure.ac
++++ b/racket/src/zuo/configure.ac
+@@ -25,6 +25,17 @@ AS_IF([test "x$enable_embed" = xno],
+ AC_PROG_MAKE_SET()
+ AC_PROG_CC
+ 
++if test "${CC_FOR_BUILD}" = ""; then
++  CC_FOR_BUILD='$(CC) -O2'
++  CFLAGS_FOR_BUILD='$(CPPFLAGS) $(CFLAGS)'
++  LDFLAGS_FOR_BUILD='$(LDFLAGS)'
++  LIBS_FOR_BUILD='$(LIBS)'
++fi
++AC_SUBST(CC_FOR_BUILD)
++AC_SUBST(CFLAGS_FOR_BUILD)
++AC_SUBST(LDFLAGS_FOR_BUILD)
++AC_SUBST(LIBS_FOR_BUILD)
++
+ AC_SUBST(EMBED_LIBS)
+ AC_MSG_NOTICE([zuo libraries to embed: "${EMBED_LIBS}"])
+ 
+-- 
+2.32.0
+
+
+From f65194ea41eb472fbdd45d5f6c13eabe5e681704 Mon Sep 17 00:00:00 2001
+From: Matthew Flatt <mflatt@racket-lang.org>
+Date: Sat, 23 Jul 2022 17:47:03 -0600
+Subject: [PATCH 2/4] Zuo: sort hash keys
+
+Printing in a sorted order is helpful to make things more
+deterministic independent of symbol inputs. Making `hash-keys`
+produce a sorted list generalizes that determinism.
+
+(cherry picked from commit 4e7ffd3b365d01c5d0993c0b3fd24c9623962edf)
+---
+ racket/src/zuo/build.zuo              |  5 ++-
+ racket/src/zuo/tests/hash.zuo         |  8 ++--
+ racket/src/zuo/zuo-doc/lang-zuo.scrbl | 18 +++++++--
+ racket/src/zuo/zuo.c                  | 57 ++++++++++++++++++++++++++-
+ 4 files changed, 78 insertions(+), 10 deletions(-)
+
+diff --git a/racket/src/zuo/build.zuo b/racket/src/zuo/build.zuo
+index c1b5e8ce66..129240120a 100644
+--- a/racket/src/zuo/build.zuo
++++ b/racket/src/zuo/build.zuo
+@@ -47,7 +47,10 @@
+     (target (at-dir (add-exe name))
+             (lambda (path token)
+               (rule (list image_zuo.c
+-                          (input-data-target 'config config)
++                          (input-data-target 'config (cons
++                                                      lib-path
++                                                      (map (lambda (key) (hash-ref config key))
++                                                           '(CC CPPFLAGS CFLAGS LDFLAGS LIBS))))
+                           (quote-module-path))
+                     (lambda ()
+                       (define l (split-path path))
+diff --git a/racket/src/zuo/tests/hash.zuo b/racket/src/zuo/tests/hash.zuo
+index a35741c730..0d3d7f3af6 100644
+--- a/racket/src/zuo/tests/hash.zuo
++++ b/racket/src/zuo/tests/hash.zuo
+@@ -35,9 +35,7 @@
+ 
+ (check (hash-keys (hash)) '())
+ (check (hash-keys (hash 'a 1)) '(a))
+-(check (let ([keys (hash-keys (hash 'a 1 'b 2))])
+-         (or (equal? keys '(a b))
+-             (equal? keys '(b a)))))
++(check (hash-keys (hash 'a 1 'b 2)) '(a b)) ; always in order
+ (check (length (hash-keys (hash 'a 1 'b 2 'c 3))) 3)
+ (check (length (hash-keys (hash 'a 1 'b 2 'a 3))) 2)
+ (check-arg-fail (hash-keys 0) "not a hash table")
+@@ -50,3 +48,7 @@
+ (check (hash-keys-subset? (hash 'a 1 'b 2) (hash 'b 1)) #f)
+ (check-arg-fail (hash-keys-subset? 0 (hash)) "not a hash table")
+ (check-arg-fail (hash-keys-subset? (hash) 0) "not a hash table")
++
++;; print sorts keys alphabetically:
++(check (~a (hash 'a 1 'b 2)) "#hash((a . 1) (b . 2))")
++(check (~a (hash 'b 2 'a 1)) "#hash((a . 1) (b . 2))")
+diff --git a/racket/src/zuo/zuo-doc/lang-zuo.scrbl b/racket/src/zuo/zuo-doc/lang-zuo.scrbl
+index 94641d041e..4605e47471 100644
+--- a/racket/src/zuo/zuo-doc/lang-zuo.scrbl
++++ b/racket/src/zuo/zuo-doc/lang-zuo.scrbl
+@@ -538,10 +538,20 @@ support to convert the textual form back into a hash table value.
+ 
+ Analogous to @realracket*[hash? hash hash-ref hash-set hash-remove
+ hash-keys hash-count hash-keys-subset?] from @racketmodname[racket].
+-Besides being constrained to symbol keys, there is one additional
+-difference: the third argument to @racket[hash-ref], when supplied,
+-is always used as a value to return if a key is missing, as
+-opposed to a failure thunk.}
++
++Besides being constrained to symbol keys, there are two additional
++differences:
++
++@itemlist[
++
++ @item{the third argument to @racket[hash-ref], when supplied, is
++       always used as a value to return if a key is missing, as
++       opposed to a failure thunk; and}
++
++ @item{the @racket[hash-keys] function returns interned keys sorted
++       alphabetically.}
++
++]}
+ 
+ 
+ @section{Procedures}
+diff --git a/racket/src/zuo/zuo.c b/racket/src/zuo/zuo.c
+index 2957d478af..88d5747326 100644
+--- a/racket/src/zuo/zuo.c
++++ b/racket/src/zuo/zuo.c
+@@ -1298,6 +1298,59 @@ static zuo_t *zuo_trie_keys(zuo_t *trie_in, zuo_t *accum) {
+   return accum;
+ }
+ 
++/*======================================================================*/
++/* symbol-list sorting                                                  */
++/*======================================================================*/
++
++/* merge sort used to make hash printing deterministic */
++static zuo_t *zuo_symbol_list_sort(zuo_t *l_in) {
++  zuo_t *l, *left, *right, *first, *last;
++  zuo_uint_t len = 0, i;
++
++  for (l = l_in, len = 0; l != z.o_null; l = _zuo_cdr(l))
++    len++;
++
++  if (len < 2)
++    return l_in;
++
++  left = z.o_null;
++  for (l = l_in, i = len >> 1; i > 0; l = _zuo_cdr(l), i--)
++    left = zuo_cons(_zuo_car(l), left);
++  right = l;
++
++  left = zuo_symbol_list_sort(left);
++  right = zuo_symbol_list_sort(right);
++
++  first = last = z.o_null;
++  while ((left != z.o_null) && (right != z.o_null)) {
++    zuo_t *p;
++
++    if (strcmp(ZUO_STRING_PTR(((zuo_symbol_t *)_zuo_car(left))->str),
++               ZUO_STRING_PTR(((zuo_symbol_t *)_zuo_car(right))->str))
++        < 1) {
++      p = zuo_cons(_zuo_car(left), z.o_null);
++      left = _zuo_cdr(left);
++    } else {
++      p = zuo_cons(_zuo_car(right), z.o_null);
++      right = _zuo_cdr(right);
++    }
++
++    if (first == z.o_null)
++      first = p;
++    else
++      ((zuo_pair_t *)last)->cdr = p;
++    last = p;
++  }
++
++  ((zuo_pair_t *)last)->cdr = ((left != z.o_null) ? left : right);
++
++  return first;
++}
++
++static zuo_t *zuo_trie_sorted_keys(zuo_t *trie_in, zuo_t *accum) {
++  return zuo_symbol_list_sort(zuo_trie_keys(trie_in, accum));
++}
++
+ /*======================================================================*/
+ /* terminal support                                                     */
+ /*======================================================================*/
+@@ -1571,7 +1624,7 @@ static void zuo_out(zuo_out_t *out, zuo_t *obj, zuo_print_mode_t mode) {
+         out_string(out, "opaque");
+       out_string(out, ">");
+     } else if (obj->tag == zuo_trie_node_tag) {
+-      zuo_t *keys = zuo_trie_keys(obj, z.o_null);
++      zuo_t *keys = zuo_trie_sorted_keys(obj, z.o_null);
+       if (mode == zuo_print_mode) {
+         out_string(out, "(hash");
+         if (keys != z.o_null)
+@@ -2587,7 +2640,7 @@ static zuo_t *zuo_hash_remove(zuo_t *ht, zuo_t *sym) {
+ 
+ static zuo_t *zuo_hash_keys(zuo_t *ht) {
+   check_hash("hash-keys", ht);
+-  return zuo_trie_keys(ht, z.o_null);
++  return zuo_trie_sorted_keys(ht, z.o_null);
+ }
+ 
+ static zuo_t *zuo_hash_keys_subset_p(zuo_t *ht, zuo_t *ht2) {
+-- 
+2.32.0
+
+
+From f2eecaa1dd875479d2cf51566223b3d0d7b9f738 Mon Sep 17 00:00:00 2001
+From: Matthew Flatt <mflatt@racket-lang.org>
+Date: Sat, 23 Jul 2022 18:06:41 -0600
+Subject: [PATCH 3/4] Zuo: check for nul characters in `string->symbol`
+
+(cherry picked from commit e20022ccfad40d0ba2e77aa75bc4f775018c781f)
+---
+ racket/src/zuo/tests/symbol.zuo       |  3 +++
+ racket/src/zuo/zuo-doc/lang-zuo.scrbl |  4 ++-
+ racket/src/zuo/zuo.c                  | 37 +++++++++++++++++----------
+ 3 files changed, 29 insertions(+), 15 deletions(-)
+
+diff --git a/racket/src/zuo/tests/symbol.zuo b/racket/src/zuo/tests/symbol.zuo
+index 7775aeeb04..5600a89755 100644
+--- a/racket/src/zuo/tests/symbol.zuo
++++ b/racket/src/zuo/tests/symbol.zuo
+@@ -19,3 +19,6 @@
+ (check (not (equal? 'apple (string->uninterned-symbol "apple"))))
+ (check-arg-fail (string->symbol 'apple) not-string)
+ (check-arg-fail (string->uninterned-symbol 'apple) not-string)
++
++(check-arg-fail (string->symbol "apple\0spice") "without a nul character")
++(check (symbol? (string->uninterned-symbol "apple\0spice")))
+diff --git a/racket/src/zuo/zuo-doc/lang-zuo.scrbl b/racket/src/zuo/zuo-doc/lang-zuo.scrbl
+index 4605e47471..07dd5815b0 100644
+--- a/racket/src/zuo/zuo-doc/lang-zuo.scrbl
++++ b/racket/src/zuo/zuo-doc/lang-zuo.scrbl
+@@ -500,7 +500,9 @@ back into Zuo.
+ )]{
+ 
+ Analogous to @realracket*[symbol? symbol->string string->symbol
+-string->uninterned-symbol] from @racketmodname[racket].}
++string->uninterned-symbol] from @racketmodname[racket], but
++@racket[string->symbol] accepts only strings that do not contain the
++null character.}
+ 
+ 
+ @section{Hash Tables (Persistent Maps)}
+diff --git a/racket/src/zuo/zuo.c b/racket/src/zuo/zuo.c
+index 88d5747326..17f161826d 100644
+--- a/racket/src/zuo/zuo.c
++++ b/racket/src/zuo/zuo.c
+@@ -1323,7 +1323,7 @@ static zuo_t *zuo_symbol_list_sort(zuo_t *l_in) {
+ 
+   first = last = z.o_null;
+   while ((left != z.o_null) && (right != z.o_null)) {
+-    zuo_t *p;
++    zuo_t *p, *s_left, *s_right;
+ 
+     if (strcmp(ZUO_STRING_PTR(((zuo_symbol_t *)_zuo_car(left))->str),
+                ZUO_STRING_PTR(((zuo_symbol_t *)_zuo_car(right))->str))
+@@ -2573,8 +2573,28 @@ static zuo_t *zuo_substring(zuo_t *obj, zuo_t *start_i, zuo_t *end_i) {
+   return zuo_sized_string((const char *)&((zuo_string_t *)obj)->s[s_idx], e_idx - s_idx);
+ }
+ 
++static int zuo_is_string_without_nul(zuo_t *obj) {
++  zuo_int_t i;
++
++  if ((obj->tag != zuo_string_tag)
++      || ZUO_STRING_LEN(obj) == 0)
++    return 0;
++
++  for (i = ZUO_STRING_LEN(obj); i--; ) {
++    if (((zuo_string_t *)obj)->s[i] == 0)
++      return 0;
++  }
++
++  return 1;
++}
++
+ static zuo_t *zuo_string_to_symbol(zuo_t *obj) {
+-  check_string("string->symbol", obj);
++  if (!zuo_is_string_without_nul(obj)) {
++    const char *who = "string->symbol";
++    check_string(who, obj);
++    zuo_fail_arg(who, "string without a nul character", obj);
++  }
++
+   return zuo_symbol_from_string(ZUO_STRING_PTR(obj), obj);
+ }
+ 
+@@ -3577,18 +3597,7 @@ static void *zuo_envvars_block(const char *who, zuo_t *envvars)
+ #endif
+ 
+ static int zuo_is_path_string(zuo_t *obj) {
+-  zuo_int_t i;
+-
+-  if ((obj->tag != zuo_string_tag)
+-      || ZUO_STRING_LEN(obj) == 0)
+-    return 0;
+-
+-  for (i = ZUO_STRING_LEN(obj); i--; ) {
+-    if (((zuo_string_t *)obj)->s[i] == 0)
+-      return 0;
+-  }
+-
+-  return 1;
++  return zuo_is_string_without_nul(obj);
+ }
+ 
+ static zuo_t *zuo_path_string_p(zuo_t *obj) {
+-- 
+2.32.0
+
+
+From de6618cb3819d25580e3cd400ea09c8cf4f673a9 Mon Sep 17 00:00:00 2001
+From: Matthew Flatt <mflatt@racket-lang.org>
+Date: Sat, 23 Jul 2022 19:50:46 -0600
+Subject: [PATCH 4/4] Zuo: CPPFLAGS_FOR_BUILD, too
+
+(cherry picked from commit cf82706c4b298f654a04c4bc8d98dff39b62a2ac)
+---
+ racket/src/zuo/Makefile.in  | 3 ++-
+ racket/src/zuo/configure    | 5 ++++-
+ racket/src/zuo/configure.ac | 4 +++-
+ 3 files changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/racket/src/zuo/Makefile.in b/racket/src/zuo/Makefile.in
+index 747b584c5c..0376c038a8 100644
+--- a/racket/src/zuo/Makefile.in
++++ b/racket/src/zuo/Makefile.in
+@@ -19,6 +19,7 @@ LIBS = @LIBS@
+ 
+ CC_FOR_BUILD = @CC_FOR_BUILD@
+ CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
++CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+ LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+ LIBS_FOR_BUILD = @LIBS_FOR_BUILD@
+ 
+@@ -29,7 +30,7 @@ zuos-to-run-and-install: zuo
+ 	./zuo . zuos-to-run-and-install
+ 
+ zuo: $(srcdir)/zuo.c
+-	$(CC_FOR_BUILD) $(FLAGS_FOR_BUILD) -DZUO_LIB_PATH='"'"$(srcdir)/lib"'"' -o zuo $(srcdir)/zuo.c $(LDFLAGS_FOR_BUILD) $(LIBS_FOR_BUILD)
++	$(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) -DZUO_LIB_PATH='"'"$(srcdir)/lib"'"' -o zuo $(srcdir)/zuo.c $(LDFLAGS_FOR_BUILD) $(LIBS_FOR_BUILD)
+ 
+ .PHONY: check
+ check: zuo
+diff --git a/racket/src/zuo/configure b/racket/src/zuo/configure
+index 575ce07d96..7ac453e3bc 100755
+--- a/racket/src/zuo/configure
++++ b/racket/src/zuo/configure
+@@ -591,6 +591,7 @@ LIBOBJS
+ EMBED_LIBS
+ LIBS_FOR_BUILD
+ LDFLAGS_FOR_BUILD
++CPPFLAGS_FOR_BUILD
+ CFLAGS_FOR_BUILD
+ CC_FOR_BUILD
+ OBJEXT
+@@ -2590,7 +2591,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
+ 
+ if test "${CC_FOR_BUILD}" = ""; then
+   CC_FOR_BUILD='$(CC) -O2'
+-  CFLAGS_FOR_BUILD='$(CPPFLAGS) $(CFLAGS)'
++  CPPFLAGS_FOR_BUILD='$(CPPFLAGS)'
++  CFLAGS_FOR_BUILD='$(CFLAGS)'
+   LDFLAGS_FOR_BUILD='$(LDFLAGS)'
+   LIBS_FOR_BUILD='$(LIBS)'
+ fi
+@@ -2600,6 +2602,7 @@ fi
+ 
+ 
+ 
++
+ { $as_echo "$as_me:${as_lineno-$LINENO}: zuo libraries to embed: \"${EMBED_LIBS}\"" >&5
+ $as_echo "$as_me: zuo libraries to embed: \"${EMBED_LIBS}\"" >&6;}
+ 
+diff --git a/racket/src/zuo/configure.ac b/racket/src/zuo/configure.ac
+index 598ff79629..051ea0beb5 100644
+--- a/racket/src/zuo/configure.ac
++++ b/racket/src/zuo/configure.ac
+@@ -27,12 +27,14 @@ AC_PROG_CC
+ 
+ if test "${CC_FOR_BUILD}" = ""; then
+   CC_FOR_BUILD='$(CC) -O2'
+-  CFLAGS_FOR_BUILD='$(CPPFLAGS) $(CFLAGS)'
++  CPPFLAGS_FOR_BUILD='$(CPPFLAGS)'
++  CFLAGS_FOR_BUILD='$(CFLAGS)'
+   LDFLAGS_FOR_BUILD='$(LDFLAGS)'
+   LIBS_FOR_BUILD='$(LIBS)'
+ fi
+ AC_SUBST(CC_FOR_BUILD)
+ AC_SUBST(CFLAGS_FOR_BUILD)
++AC_SUBST(CPPFLAGS_FOR_BUILD)
+ AC_SUBST(LDFLAGS_FOR_BUILD)
+ AC_SUBST(LIBS_FOR_BUILD)
+ 
+-- 
+2.32.0
+
diff --git a/gnu/packages/patches/racket-zuo-bin-sh.patch b/gnu/packages/patches/racket-zuo-bin-sh.patch
new file mode 100644
index 0000000000..392ea05129
--- /dev/null
+++ b/gnu/packages/patches/racket-zuo-bin-sh.patch
@@ -0,0 +1,72 @@ 
+From 4888106cdfd80d1af925e5a485a2812d35a83b46 Mon Sep 17 00:00:00 2001
+From: Philip McGrath <philip@philipmcgrath.com>
+Date: Mon, 11 Apr 2022 20:43:18 -0400
+Subject: [PATCH] Zuo: patch zuo_process for "/bin/sh" on Guix
+
+This patch reuses the C preprocessor macro `GUIX_RKTIO_BIN_SH`
+from a previous patch.
+
+If:
+
+    1. The `GUIX_RKTIO_BIN_SH` macro is defined; and
+
+    2. `zuo_process` is called with the exact path "/bin/sh"; and
+
+    3. The path specified by `GUIX_RKTIO_BIN_SH` exists;
+
+then `zuo_process` will execute the file specified by
+`GUIX_RKTIO_BIN_SH` instead of "/bin/sh".
+---
+ racket/src/zuo/zuo.c | 27 +++++++++++++++++++++++++--
+ 1 file changed, 25 insertions(+), 2 deletions(-)
+
+diff --git a/racket/src/zuo/zuo.c b/racket/src/zuo/zuo.c
+index 17f161826d..10e7a2a297 100644
+--- a/racket/src/zuo/zuo.c
++++ b/racket/src/zuo/zuo.c
+@@ -5730,7 +5730,18 @@ static void zuo_pipe(zuo_raw_handle_t *_r, zuo_raw_handle_t *_w)
+ zuo_t *zuo_process(zuo_t *command_and_args)
+ {
+   const char *who = "process";
+-  zuo_t *command = _zuo_car(command_and_args);
++  /* BEGIN PATCH for Guix */
++  zuo_t *_guix_orig_command = _zuo_car(command_and_args);
++  zuo_t *command;
++#if defined(GUIX_RKTIO_BIN_SH)
++# define GUIX_AS_a_STR_HELPER(x) #x
++# define GUIX_AS_a_STR(x) GUIX_AS_a_STR_HELPER(x)
++  /* A level of indirection makes `#` work as needed: */
++  const char *guix_sh = GUIX_AS_a_STR(GUIX_RKTIO_BIN_SH);
++# undef GUIX_AS_a_STR
++# undef GUIX_AS_a_STR_HELPER
++#endif
++  /* END PATCH for Guix */
+   zuo_t *args = _zuo_cdr(command_and_args), *rev_args = z.o_null;
+   zuo_t *options = z.o_empty_hash, *opt;
+   zuo_t *dir, *l, *p_handle, *result;
+@@ -5741,7 +5752,19 @@ zuo_t *zuo_process(zuo_t *command_and_args)
+   void *env;
+   int as_child, exact_cmdline;
+ 
+-  check_path_string(who, command);
++  /* BEGIN PATCH for Guix */
++  check_path_string(who, _guix_orig_command);
++#if defined(GUIX_RKTIO_BIN_SH)
++  command =
++    ((z.o_false == zuo_string_eql(_guix_orig_command, zuo_string("/bin/sh")))
++     || (z.o_false == zuo_stat(zuo_string(guix_sh), z.o_false, z.o_true)))
++    ? _guix_orig_command
++    : zuo_string(guix_sh);
++#else
++  command = _guix_orig_command;
++#endif
++  /* END PATCH for Guix */
++
+   for (l = args; l->tag == zuo_pair_tag; l = _zuo_cdr(l)) {
+     zuo_t *a = _zuo_car(l);
+     if (a == z.o_null) {
+
+base-commit: 87eee6e2adb8c6bc11e60619c706fa6295096085
+-- 
+2.32.0
+
diff --git a/gnu/packages/racket.scm b/gnu/packages/racket.scm
index cddb617232..0c8b3b6bb8 100644
--- a/gnu/packages/racket.scm
+++ b/gnu/packages/racket.scm
@@ -201,6 +201,7 @@  (define* (racket-vm-for-system #:optional
       racket-vm-bc))
 
 (define %racket-version "8.5") ; Remember to update chez-scheme-for-racket!
+(define %zuo-version "1.0") ; defined in racket/src/zuo/zuo.c
 (define %racket-commit
   (string-append "v" %racket-version))
 (define %racket-origin
@@ -233,6 +234,69 @@  (define %racket-origin
          ;; Unbundle libffi.
          (delete-file-recursively "racket/src/bc/foreign/libffi")))))
 
+
+(define-public zuo
+  (let ((revision #f))
+    (package
+      (name "zuo")
+      (version (string-append %zuo-version
+                              "-racket"
+                              "8.6"
+                              (if revision "-guix" "")
+                              (or revision "")))
+      (source
+       (origin
+         (inherit %racket-origin)
+         (uri (git-reference
+               (url "https://github.com/racket/racket")
+               (commit "v8.6")))
+         (sha256
+          (base32 "1yi36nr7zrdwrnvpmliirxxjz4pyfyhkar6yvk3rapvmg4q2vmnk"))
+         (patches (append (origin-patches %racket-origin)
+                          (search-patches "racket-backport-8.6-zuo.patch"
+                                          "racket-zuo-bin-sh.patch")))
+         (file-name (git-file-name "racket" "8.6"))))
+      (outputs '("out" "debug"))
+      (build-system gnu-build-system)
+      (inputs (list bash-minimal))
+      (native-inputs (list bash-minimal))
+      (arguments
+       (list
+        #:configure-flags
+        #~`(,(string-append "CPPFLAGS=-DGUIX_RKTIO_BIN_SH="
+                            #$(file-append (this-package-input "bash-minimal")
+                                           "/bin/sh"))
+            #+@(if (%current-target-system)
+                   (list #~,(string-append
+                             "CPPFLAGS_FOR_BUILD=-DGUIX_RKTIO_BIN_SH="
+                             #+(file-append
+                                (this-package-native-input "bash-minimal")
+                                "/bin/sh")))
+                   '()))
+        #:out-of-source? #t
+        #:phases
+        #~(modify-phases %standard-phases
+            (add-after 'unpack 'chdir
+              (lambda args
+                (chdir "racket/src/zuo"))))))
+      (home-page "https://github.com/racket/zuo")
+      ;; ^ This is downstream of https://github.com/racket/racket,
+      ;; but it's designed to be a friendly landing place
+      (synopsis "Tiny Racket for build scripts")
+      (description "You should use Racket to write scripts.  But what if you
+need something much smaller than Racket for some reason—or what if you're
+trying to script a build of Racket itself?  Zuo is a tiny Racket with
+primitives for dealing with files and running processes, and it comes with a
+@command{make}-like embedded DSL.
+
+Zuo (作) is a Racket variant in the sense that program files start with
+@code{#lang}, and the module path after @code{#lang} determines the parsing
+and expansion of the file content.  That's how the @command{make}-like DSL is
+defined, and even the base Zuo language is defined by layers of @code{#lang}s.
+One of the early layers implements macros.")
+      (license (list license:asl2.0 license:expat)))))
+
+
 (define (racket-vm-common-configure-flags)
   ;; under a lambda abstraction to avoid evaluating bash-minimal too early.
   #~`(,@(cond