Repository: cosmos72/schemesh
Pull request #13: feat(nix): Add default.nix
Author: login: sshine
Created: 2025-03-27T21:34:22Z | Merged: 2025-04-08T20:44:45Z

This makes it easier to install schemesh on NixOS.

It would be even neater if it was added to nixpkgs.

But they have a higher bar for admittance and a long queue for approving.


Diff:
diff --git a/README.md b/README.md
index 3d576765..77667746 100644
--- a/README.md
+++ b/README.md
@@ -493,6 +493,32 @@ make -j
 sudo make install
 ```
 
+#### Nix/NixOS
+```shell
+# Build and run by cloning source repository
+git clone https://github.com/cosmos72/schemesh
+cd schemesh
+nix-build && ./result/bin/schemesh
+```
+
+```nix
+# Install on NixOS as package
+{ pkgs, ... }:
+let
+  schemesh = pkgs.fetchgit {
+    url = "https://github.com/cosmos72/schemesh.git";
+    rev = "refs/heads/main"; # or: "refs/tags/v0.8.4"
+    sha256 = ""; # insert sha256 when ready
+  };
+{
+  environment.systemPackages = [
+    # ...
+    pkgs.chez
+    (callPackage schemesh {})
+  ];
+}
+```
+
 #### other systems
 For Unix-like systems not listed above, the instructions above can (hopefully) be adapted as needed.
 
diff --git a/default.nix b/default.nix
new file mode 100644
index 00000000..c1b951a8
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,39 @@
+{ pkgs ? import <nixpkgs> {} }:
+let
+  versionLatest =
+    builtins.head
+      (builtins.match ".*Schemesh Version ([0-9.]+).*"
+        (builtins.readFile ./bootstrap/functions.ss));
+in
+  pkgs.stdenv.mkDerivation {
+    name = "schemesh";
+    version = versionLatest;
+    src = ./.;
+
+    buildInputs = [
+      pkgs.chez    # Ubuntu: chezscheme-dev
+      pkgs.lz4     # Ubuntu: liblz4-dev
+      pkgs.ncurses # Ubuntu: libncurses-dev
+      pkgs.libuuid # Ubuntu: uuid-dev
+      pkgs.zlib    # Ubuntu: zlib1g-dev
+    ];
+
+    nativeBuildInputs = [
+      pkgs.patchelf
+    ];
+
+    buildPhase = ''
+      make -j prefix=$out
+    '';
+
+    installPhase = ''
+      mkdir -p $out/bin $out/lib/schemesh
+
+      cp schemesh $out/bin/schemesh
+      cp "libschemesh_${versionLatest}.so" $out/lib/schemesh/
+      chmod +x $out/bin/schemesh
+
+      patchelf $out/bin/schemesh --set-rpath \
+        "${pkgs.lib.makeLibraryPath [ pkgs.ncurses pkgs.libuuid ]}"
+    '';
+  }


Reviews:
user: login: sshine
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T06:45:05Z
user: login: sshine
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T07:03:30Z

Review comments:
diff_hunk: @@ -0,0 +1,74 @@
+{
+  description = "Schemesh - A Unix shell and Lisp REPL, fused together";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    flake-parts.url = "github:hercules-ci/flake-parts";
+  };
+
+  outputs = inputs@{ self, flake-parts, ... }:
+
+    flake-parts.lib.mkFlake { inherit inputs; } {
+      systems = [
+        "x86_64-linux"
+        "aarch64-darwin"
+        # "aarch64-linux" # not tested
+        # "x86_64-darwin" # not tested
+      ];
+
+      perSystem = { config, self', inputs', pkgs, system, ... }:
+        let
+          sharedBuildInputs = [
+            pkgs.chez    # Ubuntu: chezscheme-dev
+            pkgs.lz4     # Ubuntu: liblz4-dev
+            pkgs.ncurses # Ubuntu: libncurses-dev
+            pkgs.git     # Ubuntu: git
+            pkgs.libuuid # Ubuntu: uuid-dev
+            pkgs.zlib    # Ubuntu: zlib1g-dev
+            pkgs.makeWrapper
+          ];
+        in
+          {
+            packages = {
+              default = pkgs.stdenv.mkDerivation rec {
+                name = "schemesh";
+                version = "0.8.1";
+                src = self;
+
+                buildInputs = sharedBuildInputs;
+
+                buildPhase = ''
+                  make -j prefix=$out
+                '';
+
+                installPhase = ''
+                  mkdir -p $out/bin $out/lib/schemesh
+
+                  cp schemesh $out/bin/schemesh.unwrapped
+                  chmod +x $out/bin/schemesh.unwrapped
+
+                  cp libschemesh_${version}.so $out/lib/schemesh/
+                  ${pkgs.patchelf}/bin/patchelf --set-rpath "$out/lib/schemesh" $out/bin/schemesh.unwrapped
+
+                  makeWrapper $out/bin/schemesh.unwrapped $out/bin/schemesh \
+                    --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [ pkgs.ncurses pkgs.libuuid ]}:$out/lib/"
+                '';
path: flake.nix
user: login: sshine
body: Perhaps I should justify `installPhase`:

Copying to `$out/bin` and `$out/lib/schemesh` are evident.

If this were the `installPhase`:

```sh
mkdir -p $out/bin $out/lib/schemesh
cp schemesh $out/bin/schemesh
cp libschemesh_${version}.so $out/lib/schemesh/
chmod +x $out/bin/schemesh
```

I'd get the following problem:

```
$ nix build
$ ./result/bin/schemesh
./result/bin/schemesh: error while loading shared libraries: libncursesw.so.6: cannot open shared object file: No such file or directory
$ ldd result/bin/schemesh
        linux-vdso.so.1 (0x00007fc20b40f000)
        libncursesw.so.6 => not found
        libdl.so.2 => /nix/store/81mi7m3k3wsiz9rrrg636sx21psj20hc-glibc-2.40-66/lib/libdl.so.2 (0x00007fc20b404000)
        libm.so.6 => /nix/store/81mi7m3k3wsiz9rrrg636sx21psj20hc-glibc-2.40-66/lib/libm.so.6 (0x00007fc20b31b000)
        libpthread.so.0 => /nix/store/81mi7m3k3wsiz9rrrg636sx21psj20hc-glibc-2.40-66/lib/libpthread.so.0 (0x00007fc20b316000)
        libuuid.so.1 => not found
        libc.so.6 => /nix/store/81mi7m3k3wsiz9rrrg636sx21psj20hc-glibc-2.40-66/lib/libc.so.6 (0x00007fc20b000000)
        /nix/store/rmy663w9p7xb202rcln4jjzmvivznmz8-glibc-2.40-66/lib/ld-linux-x86-64.so.2 => /nix/store/81mi7m3k3wsiz9rrrg636sx21psj20hc-glibc-2.40-66/lib64/ld-linux-x86-64.so.2 (0x00007fc20b411000)
```

In order to inject libncursesw.so.6 and libuuid.so.1 into `$LD_LIBRARY_PATH`, the [`pkgs.makeWrapper`](https://nixos.org/manual/nixpkgs/stable/#fun-makeWrapper) script is used.

This creates a script that might look like:

```sh
#! /nix/store/58br4vk3q5akf4g8lx0pqzfhn47k3j8d-bash-5.2p37/bin/bash -e
LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+':'$LD_LIBRARY_PATH':'}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH/':''/nix/store/vsnhinpcgj8khqqmp1pf6f8rj7dh85bp-schemesh/lib/'':'/':'}
LD_LIBRARY_PATH='/nix/store/vsnhinpcgj8khqqmp1pf6f8rj7dh85bp-schemesh/lib/'$LD_LIBRARY_PATH
LD_LIBRARY_PATH=${LD_LIBRARY_PATH#':'}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH%':'}
export LD_LIBRARY_PATH
LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+':'$LD_LIBRARY_PATH':'}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH/':''/nix/store/bd12z7swxj5kzqw9m1gf99d6v570h0b3-util-linux-minimal-2.40.4-lib/lib'':'/':'}
LD_LIBRARY_PATH='/nix/store/bd12z7swxj5kzqw9m1gf99d6v570h0b3-util-linux-minimal-2.40.4-lib/lib'$LD_LIBRARY_PATH
LD_LIBRARY_PATH=${LD_LIBRARY_PATH#':'}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH%':'}
export LD_LIBRARY_PATH
LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+':'$LD_LIBRARY_PATH':'}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH/':''/nix/store/80l1sb3vcmrkcdd7ihlizkcnv19rq9fj-ncurses-6.5/lib'':'/':'}
LD_LIBRARY_PATH='/nix/store/80l1sb3vcmrkcdd7ihlizkcnv19rq9fj-ncurses-6.5/lib'$LD_LIBRARY_PATH
LD_LIBRARY_PATH=${LD_LIBRARY_PATH#':'}
LD_LIBRARY_PATH=${LD_LIBRARY_PATH%':'}
export LD_LIBRARY_PATH
exec "/nix/store/vsnhinpcgj8khqqmp1pf6f8rj7dh85bp-schemesh/bin/schemesh.unwrapped"  "$@"
```

Upon this walkthrough, it has become apparent to me that `patchelf` is not necessary, and that `makeWrapper` should be in `nativeBuildInputs` according to the manual. When seeking to understand what the difference between the two is, in non-legacy terms, [@jonringer points out that packaging compilers and interpreters you may wish to use `depsXXXYYY` variants](https://discourse.nixos.org/t/use-buildinputs-or-nativebuildinputs-for-nix-shell/8464/2), whatever that is.

I've removed the call to `patchelf` from the PR.
created_at: 2025-04-03T06:45:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+{
+  description = "Schemesh - A Unix shell and Lisp REPL, fused together";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    flake-parts.url = "github:hercules-ci/flake-parts";
+  };
+
+  outputs = inputs@{ self, flake-parts, ... }:
+
+    flake-parts.lib.mkFlake { inherit inputs; } {
+      systems = [
+        "x86_64-linux"
+        "aarch64-darwin"
+        # "aarch64-linux" # not tested
+        # "x86_64-darwin" # not tested
+      ];
+
+      perSystem = { config, self', inputs', pkgs, system, ... }:
+        let
+          sharedBuildInputs = [
+            pkgs.chez    # Ubuntu: chezscheme-dev
+            pkgs.lz4     # Ubuntu: liblz4-dev
+            pkgs.ncurses # Ubuntu: libncurses-dev
+            pkgs.git     # Ubuntu: git
+            pkgs.libuuid # Ubuntu: uuid-dev
+            pkgs.zlib    # Ubuntu: zlib1g-dev
+            pkgs.makeWrapper
+          ];
+        in
+          {
+            packages = {
+              default = pkgs.stdenv.mkDerivation rec {
+                name = "schemesh";
+                version = "0.8.1";
+                src = self;
+
+                buildInputs = sharedBuildInputs;
+
+                buildPhase = ''
+                  make -j prefix=$out
+                '';
+
+                installPhase = ''
+                  mkdir -p $out/bin $out/lib/schemesh
+
+                  cp schemesh $out/bin/schemesh.unwrapped
+                  chmod +x $out/bin/schemesh.unwrapped
+
+                  cp libschemesh_${version}.so $out/lib/schemesh/
+                  ${pkgs.patchelf}/bin/patchelf --set-rpath "$out/lib/schemesh" $out/bin/schemesh.unwrapped
+
+                  makeWrapper $out/bin/schemesh.unwrapped $out/bin/schemesh \
+                    --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [ pkgs.ncurses pkgs.libuuid ]}:$out/lib/"
+                '';
path: flake.nix
user: login: sshine
body: Note that it is technically possible to set

```
environment.variables.LD_LIBRARY_PATH =
  pkgs.lib.makeLibraryPath [ pkgs.ncurses pkgs.libuuid ];
```

in a configuration.nix or shell.nix, in order to fix the linking but I have been told that this is not the Nix way.
created_at: 2025-04-03T07:03:30Z
author_association: CONTRIBUTOR

PR comments:
user: login: cosmos72
created_at: 2025-03-30T18:37:17Z
body: Thanks for the interest and for your contribution @sshine 

At the moment I know nothing about NixOS, and I am unable to validate your pull request:
it looks too different from other build instructions for typical Unix-like systems.

This is my fault, but it means I cannot check that your code does what it should,
and I am not confident enough to merge it.

If you can be so kind and provide some links to official NixOS documentation,
that showcase how to download via git, compile and install some example program,
I would greatly appreciate your help and it will definitely help my understanding of your pull request.

Thanks for your patience!
author_association: OWNER
user: login: sshine
created_at: 2025-03-30T21:30:46Z
body: > showcase how to download via git, compile and install some example program

There's a lot of ways to do things with Nix, and the flake is one specific.

Let me outline the alternatives, so perhaps you can better judge if this is the right way to support Nix.

## Option: nixpkgs

Normally, programs in NixOS are installed via [nixpkgs](https://nixos.org/manual/nixpkgs/stable/#overview-of-nixpkgs).

You can either install a package by including it in your system configuration, [like so](https://jorel.dev/NixOS4Noobs/installingprogs.html#method-2-installing-programs-using-the-system-packages):

```nix
environment.systemPackages = with pkgs; [
    # Packages go here
];
```

or you can try out a package by creating a shell in which the program is injected into `$PATH`:

```sh
nix-shell -p htop --command htop
```

*(There is another way I won't mention, since it leaves your running system out of sync with its declarative config.)*

To put a package in nixpkgs, you need to write the Nix packaging. I don't have a lot of experience with nixpkgs package formats, but for comparison, Chez Scheme is in nixpkgs with the following [chez/package.nix](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ch/chez/package.nix).

Having packages in nixpkgs is preferable from a user perspective, because it makes installation the easiest, and Nix files aren't littered throughout the ecosystem like this PR does. Unfortunately, nixpkgs is not an easy place to contribute. (There's a barrier to entry, [a lot of traffic](https://github.com/NixOS/nixpkgs/pulls) (6.6k open PRs), a lot of waiting.)

When packages are not in nixpkgs, but instead on GitHub, you can clone the repository, enter the directory and type `make -j` or whatever is necessary, just like on any other Linux distro. If you have the necessary tools installed.

That won't necessarily get the program installed on your system, just available in your directory. So I'm addressing two things here: Compiling the project (e.g. for working on it), and installing schemesh as a shell on your system.

The Nix way, in both cases, is to state your assumptions up front: My NixOS doesn't have gcc or gmake in `$PATH`, and `/lib`, `/usr/lib` and `/usr/local/lib` are all empty. Because unless you specify exactly what is required to build, building won't be reproducible.

## Option: shell.nix

The simplest way to get a dev shell with the necessary tools installed is via a file called shell.nix. If it were around, it'd look like:

```nix
{ pkgs ? import <nixpkgs> {} }:
  pkgs.mkShell {
    buildInputs = [
      pkgs.chez
      pkgs.lz4
      pkgs.ncurses
      pkgs.libuuid
      pkgs.zlib
    ];
  }
```

Running `nix-shell` in a directory with this shell.nix file, it adjusts the shell so that those dependencies are installed; this includes executables (`$PATH`), libraries (`$LD_LIBRARY_PATH`), and so on for header files.

Some things are implied by `mkShell`, e.g. gcc and make.

If I were just trying to compile schemesh, and run it from my local git repository, I'd settle with this shell.nix. But since I want to run schemesh as my system shell, or at least have it in my `$PATH` always, I need a little more.

## Option: default.nix

In order to install something on NixOS, I need a [derivation](https://wiki.nixos.org/wiki/Derivations): A Nix derivation is a description of the steps to produce an output from a given input in a deterministic manner. The output of a derivation is a filesystem directory containing whatever makes sense for a package; in schemesh's case, the executable and the .so, since everything else is a dependency.

Most of the complexity comes from NixOS not respecting the Filesystem Hierarchy Standard (FHS).

If default.nix were around, it'd look like:

```nix
# "{ ... }: ..." is a lambda
# "{ pkgs }: pkgs.stdenv.mkDerivation ..." is a lambda that makes a derivation
# "{ pkgs ? import <nixpkgs> {} }: ..." defaults to finding nixpkgs in NIX_PATH
#   and applies it as a function to the empty attrset {}, producing a 'pkgs'.
{ pkgs ? import <nixpkgs> {} }:
  pkgs.stdenv.mkDerivation rec {
    name = "schemesh";
    version = "0.8.1";
    src = ./.;
    buildInputs = [
      pkgs.chez
      pkgs.lz4
      pkgs.ncurses
      pkgs.libuuid
      pkgs.zlib
      pkgs.makeWrapper
    ];

    # Since schemesh links to a .so using an absolute path, it's important
    # to get that path right; $out corresponds to some path in the nix store
    # for this derivation.
    buildPhase = ''
      make -j prefix=$out
    '';

    # Once compiled, the build environment is abandoned in favor of the output
    # environment, so schemesh and its .so is copied out. Because it links to
    # third-party .so's with absolute paths, the easiest way is to create a
    # wrapper script that adjusts LD_LIBRARY_PATH to the actual location of the
    # libraries. Alternatively one can patch the build process, but it requires
    # more maintenance in case of changes.
    installPhase = ''
      mkdir -p $out/bin $out/lib/schemesh
      cp schemesh $out/bin/schemesh.unwrapped
      chmod +x $out/bin/schemesh.unwrapped
      cp libschemesh_${version}.so $out/lib/schemesh/
      ${pkgs.patchelf}/bin/patchelf --set-rpath "$out/lib/schemesh" $out/bin/schemesh.unwrapped
      makeWrapper $out/bin/schemesh.unwrapped $out/bin/schemesh \
        --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath [ pkgs.ncurses pkgs.libuuid ]}:$out/lib/"
    '';
  };
```

With this file present, it is possible to install the package as a system package on NixOS.

Since the buildInputs are the same (+/- `pkgs.makeWrapper`) in case of default.nix and shell.nix, running `nix-shell` in a directory with only a default.nix actually also works, so having just that one file would be better than simply having shell.nix, in case you want to be able to both develop on schemesh on NixOS, and install schemesh as a system package on NixOS.

This would be the "least effort" in terms of getting something that works.

## Option: flake.nix

Flakes contain both a `devShells` section (mimicking shell.nix), and a `packages` section (mimicking default.nix) and a bunch of other sections.

Flakes do a lot of things, but most significantly, they provide a way to pin hash digests of packages in a lockfile, and a common input/output format for chaining packages together. The introduction to flakes that I like the best is [NixOS & Flakes Book](https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes)'s Introduction section.

The README I provided in this PR is an example of how a system flake can take a schemesh flake as input and install schemesh as a package by cloning from github and compiling in the way the derivation describes.

The downside to providing a flake is that if people refuse to use flakes, they can't conveniently fetch schemesh. The upside is that the way of importing flakes is conventional; i.e. you don't need to understand what kind of duck-typed object is at the other end: A flake has a specification.

It is possible to install schemesh using just a default.nix by altering the system configuration snippet like so:

```nix
# configuration.nix (the NixOS system configuration file)
{ config, pkgs, ... }:
let
  schemesh = import (pkgs.fetchFromGitHub {
    owner = "cosmos72";  # Replace with actual GitHub owner
    repo = "schemesh";     # Replace with actual repository name
    rev = "v0.8.1";        # Replace with desired commit/tag
    sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # Replace with actual hash
  }) { inherit pkgs; };
in
{
  environment.systemPackages = with pkgs; [
    schemesh
  ];
}
```

Another downside is that your hash-pinning is mixed into your system configuration code, instead of living inside a dedicated lockfile. There are non-flake solutions to hash-pinning in lockfiles, like [niv](https://github.com/nmattia/niv) and [npins](https://github.com/andir/npins).

And a third downside is, default.nix doesn't have the concept of an "app", which is what `nix run github:cosmos72/schemesh` lets you do. (It's just a thin wrapper around the package itself.)

## Direnv

I added a .envrc with the content "use flake", which, when direnv and nix-direnv are installed, will automatically adjust the current shell's paths to include the tools. This makes typing `nix-shell` unnecessary, and you get to keep all your existing shell configuration.

That reminds me direnv support in schemesh would be very neat, but I've got simpler needs that aren't met yet.

## Summary

If I were to advise you on whether this is the right way to support Nix / NixOS, I can present the following options:

1. Add a package.nix to nixpkgs: Easiest to use, most effort to do.
2. Just use a default.nix: More widely accepted, less structured.
3. Just use a shell.nix: Strictly provides compilation, not packaging.

Let me know if you prefer one of these instead.

I may consider adding to nixpkgs instead at a later point in time.

For now I just wanted to be able to compile and install schemesh myself.
author_association: CONTRIBUTOR
user: login: sshine
created_at: 2025-04-03T07:22:57Z
body: I've rebased on `main` to address 0.8.2 and your changes to README.md.

One thing I'd like with the Nix packaging is if the version number was derived from a canonical source. It doesn't seem like there is one, though, since the version number is changed in many places when a new release is made.

So I was unsure from where to derive it.

Ideas are welcome.
author_association: CONTRIBUTOR
user: login: cosmos72
created_at: 2025-04-06T21:25:30Z
body: Many thanks for the detailed explanation!

Just one small detail: despite its name, the file `libschemesh_0.8.1.so` is **not** an ELF shared library. It has a completely different format and contains compiled Scheme code, which is loaded manually (its absolute path is hardcoded in schemesh using the macro SCHEMESH_LIBDIR, or SCHEMESH_DIR in newer versions).

Unluckily, the default extension of Scheme code compiled by Chez Scheme is `.so` which conflicts with ELF shared libraries. That's also why it is installed by default in `/usr/local/lib/schemesh` : putting it in  `/usr/local/lib` or a similar path would confuse `ldconfig`.

Also, needing to wrap schemesh executable with a bash script seems somewhat self-defeating. 

About using schemesh as **login** shell in NixOS: it's surely a mid or long-term target, but for starting we may stop at installing it in $PATH so that it can be executed easily from the user's shell.

[UPDATE] There's no simple mechanism to declare thw version number in a single place - one would need as minimum to manually run a preprocessor on Scheme sources.
The most authoritative version number is the fragment
```lisp
(define (sh-version-number)
  (values X Y Z))
```
In bootstrap/functions.ss
author_association: OWNER
user: login: sshine
created_at: 2025-04-07T13:23:12Z
body: Thanks for clarifying how schemesh'es .so works, I assumed it was a regular ELF file. I've got rid of `makeWrapper` and am using `patchelf` on the failing libraries. That seems to work, and it's simpler.

As for login shells: Once it's an installed package on the system, it can be used as a login shell, so there's no extra work from making it available in `$PATH`.

I've tried to extract the version number, but my Scheme is not so strong, so it might be over-engineered. But this should prevent the flake.nix from needing a version bump; only `sh-version-number` needs a bump.

I've used something called IFD (Import From Derivation), meaning the `default` package derivation depends on running a Scheme script before it can proceed. If the version were available in a serializable format (like JSON or TOML), I could derive the version in pure Nix. But I thought it was in the spirit to provide a Scheme script.

Let me know if you like this, and if I need to clean up `utils/find_schemesh_version.ss`.

It is admittedly a little backwards.
author_association: CONTRIBUTOR
user: login: cosmos72
created_at: 2025-04-07T13:36:29Z
body: The file `utils/find_schemesh_version.ss` looks definitely over-engineered to me: I would have just used a quick-and-dirty
```shell
grep -A1 sh-version-number bootstrap/functions.ss | grep values | sed -e 's/ *(values //' -e 's/ *).*//' | tr ' ' '.'
```
or, reading the version number from `(sh-version)` that returns a string,
```shell
grep "Schemesh Version" bootstrap/functions.ss | sed -e 's/.*Schemesh Version //' -e 's/".*//'
```

Anyway, the main issue I still have with this PR is that it's a lot of code, both for me and for who wants to install schemesh in NixOS. I am really considering whether the "Option: default.nix" you explained above would be easier to understand and follow for everyone - me included.
author_association: OWNER
user: login: sshine
created_at: 2025-04-07T19:34:09Z
body: How about this?
author_association: CONTRIBUTOR
user: login: cosmos72
created_at: 2025-04-07T22:19:43Z
body: Much better! Thanks for the effort and the patience
author_association: OWNER
user: login: sshine
created_at: 2025-04-08T10:06:48Z
body: I'm thrilled to contribute to schemesh. This will get me started.
author_association: CONTRIBUTOR
user: login: sikmir
created_at: 2025-10-29T20:45:34Z
body: Just in case: https://github.com/sikmir/nur-packages/commit/a60b7b7169f3ce71b6121326544c0c01dd24742c
author_association: NONE