Basic flake: run existing (python, bash) script

Hello,

I have a python script that depends on the selenium library and chromedriver; it constantly breaks due to system updates. I thought it might be a good candidate to employ nix and hopefully get something that works reliably over time. I also want to learn more about flakes.

I’ve spent the morning trying to figure out how to make the most basic and bare-bones flake possible to do this, hoping to avoid extra dependencies like flake-utils or poetry. I’m only interested in running on aarch64-darwin for now.

My goal is to be able to run nix run and have that result in python myscript.py being called.

Unfortunately, I can’t seem to figure out what the best approach for even this simple task is. It looks like I might need to use buildPythonApplication? Nixpkgs 23.11 manual | Nix & NixOS

Here is what I have so far, although I’ve now figured out that shellHook doesn’t do what I thought it would.

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
    system = "aarch64-darwin";
  pkgs = nixpkgs.legacyPackages.${system};
  python = pkgs.python3.withPackages (ps: with ps; [ selenium ]);
  in
  {
    packages.${system}.default =
      with import nixpkgs { inherit system; };
    pkgs.mkShell {
      nativeBuildInputs = [ python ];
      shellHook = ''
        ${python} ./myscript.py
        '';
    };
  };
}

I thought it might help me if anyone would be willing to contribute an example flake or two, ideally not depending on flake-utils just for instructive purposes, that upon nix run would:

  1. run a bash command: echo foo
  2. run an existing bash script, myscript.sh
  3. run an existing bash script that depends on e.g. ripgrep
  4. run a python command: print("foo")
  5. run an existing python script: ./myscript.py
  6. run an existing python script that depends on an arbitrary pypi package

My goal is mainly for adapting several existing single-purpose scripts. Obviously asking for 6 example flakes is a lot, but if anyone things they can pitch in a canonical, best-practices, dependency-free example of a flake that would work on a single system (aarch64-darwin in my case), I’d really appreciate it! I thought this would be fairly trivial, but after tinkering, googling, and reading all morning, I’d love to have some pointers.

Many thanks in advance.

EDIT: Changed unordered list to numbered to allow specifying which task something was directed at accomplishing.

Here’s an example for the first few, at least:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
  let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    python = pkgs.python3.withPackages (ps: with ps; [ selenium ]);
  in
  {
    packages.${system} = {
      output1 = pkgs.writeScriptBin "myscript" ''
        echo foo
      '';

      output2 = pkgs.writeScriptBin "myscript" ./myscript.sh;

      output3 = pkgs.writeScriptBin "myscript" ''
        export PATH=${pkgs.lib.makeBinPath [ pkgs.hello ]}:$PATH
        ${./run-hello.sh}
      '';

      output4 =
        let
          tmpPyScript = pkgs.writeTextFile {
            name = "tmp-py-script";
            text = ''
              print("foo from python")
            '';
          };
        in
        pkgs.writeScriptBin "run-python" ''
          ${python}/bin/python ${tmpPyScript}
        '';
    };
  };
}

and these can be run with nix run .#output1 etc. in some directory.

e.g. as a gist at https://gist.github.com/rgoulter/35daa2c1152ac0b4381fd38c4ddb4069 with nix flakes, you can even run nix run git+https://gist.github.com/rgoulter/35daa2c1152ac0b4381fd38c4ddb4069#output1 which isn’t very helpful for this task, but seems a neat feature to keep in mind.

(IIRC, buildPythonApplication involves stuff like setup.py or whatever, which is more than I’m interested in).

For more intense Python stuff… at that point, you might want to take a look at GitHub - DavHau/mach-nix: Create highly reproducible python environments or GitHub - nix-community/poetry2nix: Convert poetry projects to nix automagically [maintainer=@adisbladis]

4 Likes

… Hmmm, lost my original reply unfortunately … oh well.

Thanks for your time!

It looks like adding default = self.packages.${system}.output1; lets you choose what is run with a bare nix run, which is great.

I found a few more helpful links while searching for lib.makeBinPath and a few other things that were new to me from your post:

I think searching specifically for "flake’ material may have excluded these from my earlier search attempts.

I also found a really great / thorough post here: https://ertt.ca/nix/shell-scripts/

It looks like symlinkJoin may be a good way to accomplish #3, but for some reason it doesn’t seem to be actually setting the PATH:

#!/usr/bin/env bash
# foo.sh

echo "$SHELL"
command -v rg
echo foo | rg o
# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
      script = pkgs.writeScriptBin "say_foo" ''
        #!${pkgs.stdenv.shell}
        ${builtins.readFile ./foo.sh}
      '';
    in
    {
      packages.${system} = {
        default = pkgs.symlinkJoin {
          name = "say_foo";
          paths = [
            script
            pkgs.ripgrep
          ];
        };
      };
    };
}
$ nix run
/opt/homebrew/bin/bash
/opt/homebrew/bin/rg
foo

I thought nix flakes were “pure” and shouldn’t be grabbing these binaries from the parent environment’s PATH. Why is that happening? Shouldn’t pkgs.symlinkJoin be directing it to the nix-installed rg? Weird.

1 Like

Aha, writeShellApplication: https://github.com/NixOS/nixpkgs/blob/588c4f214ee5c98d3925666d84555025e5e6ea5c/pkgs/build-support/trivial-builders.nix patches the path based on inputs.

Unfortunately, attempting it results in the below error right now:

error: Package ‘python3.10-pyopenssl-22.0.0’ in /nix/store/1njdlszpc3bchdcwk45c3ndb0nfmwqcr-source/pkgs/development/python-modules/pyopenssl/default.nix:73 is marked as broken, refusing to evaluate.

I’m not sure what is bringing in Python, it looks like it just runs shellcheck on the input, which I think is in Haskell, right?

Also, NIXPKGS_ALLOW_BROKEN=1 nix run --impure now downloads over a Gb of new stuff :man_facepalming:

Maybe I’d be better off with the manual writeScriptBin from @rgoulter above!

1 Like

Overriding the checkPhase works well, thought not sure why I’m still using my system bash.

#!/usr/bin/env bash
# foo.sh

echo "$SHELL"
command -v rg
echo foo | rg o
# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      packages.${system}.default = pkgs.writeShellApplication {
        name = "say_foo";
        runtimeInputs = [ pkgs.ripgrep ];
        text = ''
          #!${pkgs.stdenv.shell}
          ${builtins.readFile ./foo.sh}
        '';
        checkPhase = "${pkgs.stdenv.shellDryRun} $target";
      };
    };
}
$ nix run
/opt/homebrew/bin/bash
/nix/store/c39pjjw72rcnli5fwbnhv529kwa6rksm-ripgrep-13.0.0/bin/rg
foo

EDIT: Nevermind; adding ps -p $$ to my foo.sh shows that it is indeed running in nix’s bash. Can also prove that by adding pkgs.ps to the runtimeInputs and running like so, where the path doesn’t even contain my system bash:

$ PATH=~/.nix-profile/bin nix run
38964 ttys037    0:00.00 /nix/store/l81df76j5jxr8lymk9zp9af94llkir94-bash-5.1-p16/bin/bash /nix/store/n5dnnq00ihhplg84hsll95ffnc3423i4-say_foo/bin/say_foo
/opt/homebrew/bin/bash
/nix/store/c39pjjw72rcnli5fwbnhv529kwa6rksm-ripgrep-13.0.0/bin/rg
foo

It is interesting that $SHELL is still set to my system bash. Even with:

$ env -i ~/.nix-profile/bin/nix run
  PID TTY           TIME CMD
39676 ttys037    0:00.00 /nix/store/l81df76j5jxr8lymk9zp9af94llkir94-bash-5.1-p16/bin/bash /nix/store/n5dnnq00ihhplg84hsll95ffnc3423i4-say_foo/bin/say_foo
/opt/homebrew/bin/bash
/nix/store/c39pjjw72rcnli5fwbnhv529kwa6rksm-ripgrep-13.0.0/bin/rg
foo

I guess that must be because it’s the shell used to spawn the nix process itself.

1 Like

writePython3Bin seems like the way to accomplish #4:

An example including dependencies:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
      py3pkgs = pkgs.python3.pkgs;
    in
    {
      packages.${system}.default = pkgs.writers.writePython3Bin "say_foo" { libraries = [ py3pkgs.requests ]; } ''
        import requests
        status = requests.get("https://n8henrie.com").status_code
        print(status)
      '';
    };
}
$ NIXPKGS_ALLOW_BROKEN=1 nix run --impure
200

Accomplishing #5 and #6 seem pretty simple leveraging the same builtins.readFile as above:

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
      py3pkgs = pkgs.python3.pkgs;
    in
    {
      packages.${system}.default = pkgs.writers.writePython3Bin "say_foo"
        {
          libraries = [ py3pkgs.requests ];
        } "${builtins.readFile ./foo.py}";
    };
}
# foo.py
import sys

import requests

print(sys.executable)
status = requests.get("https://n8henrie.com").status_code
print(status)
$ NIXPKGS_ALLOW_BROKEN=1 nix run --impure
/nix/store/gg1vps3aljdismx7rqps4m738gjcf9fp-python3-3.10.4-env/bin/python3.10
200

I guess the only part remaining is the “arbitrary PyPI dependency part.”

2 Likes

For number 6 (and 5 I think counts as well), I’ve spend several days reading and tinkering, and haven’t found a great solution.

mach-nix failed to install / work on my M1 mac. Other attempts at solving this problem (pypi2nix for example) are abandoned. It seems that the overall state of using Python with Nix is… not great. Which seems concerning, given that the popularity and utilization of Python dwarfs that of nix.

For the solution that more-or-less answers my initial question / concerns (no extra dependencies, nix run results in python myscript.py being called, using flakes):

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
      py3pkgs = pkgs.python3.pkgs;
      venvDir = "./.venv";
      pypiDeps = [ "simplenet" ];
      shell = pkgs.mkShell
        {
          name = "python-env";
          buildInputs = [ py3pkgs.python py3pkgs.venvShellHook ];
          venvDir = ".venv";
          postVenvCreation = ''
            python3 -m pip install "${toString pypiDeps}"
          '';
        };

      runner = pkgs.writeShellApplication
        {
          name = "runme";
          checkPhase = ":";
          runtimeInputs = [ py3pkgs.python ];
          text = ''
            if [ -e "${venvDir}/bin/python" ]; then
              echo "Skipping venv creation, '${venvDir}' already exists"
            else
              rm -rf ./"${venvDir}"
              echo "Creating new venv environment in path: '${venvDir}'"
              ${py3pkgs.python.interpreter} -m venv "${venvDir}"
             "${venvDir}"/bin/python -m pip install --upgrade pip
             "${venvDir}"/bin/python -m pip install "${toString pypiDeps}"
            fi
            "${venvDir}"/bin/python < ./foo.py
          '';
        };
    in
    {
      apps.${system}.default = {
        type = "app";
        program = "${runner}/bin/runme";
      };
      devShells.${system}.default = shell;
    };
}
# foo.py
import sys

import simplenet

print(sys.executable)
print(simplenet.__version__)

With something like this, nix develop drops you into a dev environment within a virtualenv, and nix run runs foo.py with dependencies made available in the virtualenv. Both with create the virtualenv (only if it doesn’t seem to exist and have a bin/python that exists) and install dependencies in pypiDeps.

Unfortunately runner is more complex than I had hoped, as I don’t see a simple way to manually execute hooks (I get runHook: command not found if I try to add venvShellHook to runtimeInputs and then runHook venvShellHook, I don’t see any other way to run a hook with pkgs.writeShellApplication), which is what makes nix develop comparatively simple.

There was recently a thread with someone else discussing the same woes Why is it so hard to use a Python package?

Both with create the virtualenv (only if it doesn’t seem to exist and have a bin/python that exists) and install dependencies in pypiDeps .

For “I want to use the venv in the current directory”, I wonder if direnv / Python would be more suitable. Home · direnv/direnv Wiki · GitHub

Rather, I think having a nix run application make use of the current directory, and installing runtime dependencies with pip in order to do so, isn’t very idiomatic/‘clean’.

e.g. I updated my gist with your runner example, and

nix run git+https://gist.github.com/rgoulter/35daa2c1152ac0b4381fd38c4ddb4069#runner

for me runs

/nix/store/nznji0b6g1r2gl97bb6m27irqvppzn03-python3-3.10.4-env/bin/python3.10
v0.1.4

The Nix for the runner being:


      runner = pkgs.writeShellApplication
        {
          name = "runme";
          checkPhase = ":";
          runtimeInputs = [pythonWithSimplenet];
          text = ''
            "${pythonWithSimplenet}/bin/python" ${./foo.py}
          '';
        };

and pythonWithSimplenet being:

    simplenet = ps: ps.callPackage ./simplenet.nix { };
    pythonWithSimplenet = pkgs.python3.withPackages (ps: [ (simplenet ps) ]);

and the Nix description of simplenet as a Python package is in the flake. (With caveats that I skipped including dependencies for running the tests; and the numpy in nixpkgs that the flake points to is older than your simplenet required).

Yes, I saw that thread – and I agree with some of the sentiments expressed. For such an extremely popular and beginner-friendly language, I’m surprised that nix doesn’t seem to have an smooth process.

Perhaps – I’ve used direnv and similar tools in the past and didn’t care for them; I generally just have a config.env and use an alias like alias sv='source ./.venv/bin/activate; source config.env;'. But this also is adding a dependency, with one of the goals of this question being finding a solution that didn’t also add new dependencies, as I’m having a hard enough time troubleshooting nix at this point.

On that thread, I’ve been trying and trying to get mach-nix to work, as it seems to be the currently recommended approach in many similar conversations, and it’s gone nowhere. Installed as a flake, in nix-shell, and via pip, and on my Macbook I get odd errors when trying to generate a nix expression with virtually the easiest possible situation requirements.txt. With the flake + nix shell on my Macbook it doesn’t seem to install correctly (`command not found` in nix flake on M1 Mac · Issue #479 · DavHau/mach-nix · GitHub). With a pip or nix-shell installation I get different errors entirely (`error: cannot coerce null to a string` on M1 Mac · Issue #482 · DavHau/mach-nix · GitHub). On Linux it seems to work for old versions, but since I went to the currently recommended pyproject.toml project format, it can’t install simplenet>=0.1.3 even on linux (even though a wheel is available: https://files.pythonhosted.org/packages/8c/1b/d7b4ee5c49fc3213cebe53e55bd76bb2069b81e80696ba6cfa4959c16e94/simplenet-0.1.4-py3-none-any.whl). There doesn’t seem to be a way to specify a different revision of the pypi database from the CLI tool.

I would hope for a more obvious “right” way to do things for an incredibly popular language like python.

I agree, but it seems like a way to get things working for the moment as the alternative paths are… thorny, as far as I can tell.

Thank you again for your time and the example. Assuming that one doesn’t want to patch the dependencies (for example if I depended on a bugfix that was in the pinned version of numpy, as I haven’t run tests on the version you used), is the “right” way to just iteratively fetchPypi with each dependency, and all of its dependencies, get the hash once it gives you a hash mismatch error, and then do it again… until you’ve recursively gone through the entire dependency tree? I intentionally chose one of my projects with only a single dependency to simplify things, but I assume that one would need to walk the entire dependency tree… which seems like an exhausting process to do by hand!

I haven’t looked into the prospect too far, but one alternative that came to mind was:

$ pip download --dest vendor simplenet==0.1.4
...
$ ls -l vendor/
total 12476
-rw-r--r-- 1 n8henrie staff 12764532 Jul  1 08:35 numpy-1.22.4-cp39-cp39-macosx_11_0_arm64.whl
-rw-r--r-- 1 n8henrie staff     7437 Jul  1 08:35 simplenet-0.1.4-py3-none-any.whl

This seems to do a great job getting all of the dependencies in one fell swoop, afterwards it would seem relatively (?) easy to hash them and make them reproducible-ish inputs for a derivation (at least for a single machine, I don’t know about their metadata).

Perhaps – I’ve used direnv and similar tools in the past and didn’t care for them

Ah, fair that it may not be the tool to solve what you’re after.

I’ll say though that I like the combination of direnv+nix. (The nix integration means not needing to worry about a system-wide python, the python integration means not needing to worry about activating the virtual environment).

which seems like an exhausting process to do by hand!

Heh. Thus the motivation for tools like mach-nix or poetry2nix, which I see in the issue you’ve filed doesn’t support the way your package was packaged.

Without using a project like that, I guess it’s down to either “hope someone wrote the package in nixpkgs already”, or, yeah, come up with the expressions.

Just another small example to remind myself how (and that I need) to use pkgs.symlinkJoin and PATH manipulation with wrapProgram in order to use nix’s chromedriver instead of my system one.

# flake.nix
{
  inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-22.05;

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
      py3pkgs = pkgs.python3.pkgs;
      venvDir = "./.venv";

      pydeps = [ py3pkgs.python py3pkgs.selenium ];

      shell = pkgs.mkShell
        {
          name = "python-env";
          buildInputs = pydeps;
        };

      pyscript = pkgs.writers.writePython3Bin "tester"
        {
          libraries = pydeps;
        } "${builtins.readFile ./tester.py}";

      runner = pkgs.symlinkJoin
        {
          name = "run tester";
          paths = [ pyscript pkgs.chromedriver ];
          buildInputs = [ pkgs.makeBinaryWrapper ];
          postBuild = "wrapProgram $out/bin/tester --prefix PATH : $out/bin";
        };
    in
    {
      apps.${system}.default = {
        type = "app";
        program = "${runner}/bin/tester";
      };
      devShells.${system}.default = shell;
    };
}
# tester.py
import subprocess

import selenium

print(selenium.__version__, selenium.__file__, sep="\n")

subprocess.run(
    "which chromedriver; chromedriver --version;", shell=True
)
$ nix run
3.141.0
/nix/store/1hgvcb1fnw9gyz9sd7xplsf5d97cijrb-python3-3.9.13-env/lib/python3.9/site-packages/selenium/__init__.py
/nix/store/pc1qixi6pkyijrxmb9cqlc5di7zaqfnn-run-tester/bin/chromedriver
ChromeDriver 103.0.5060.24 (e47b049c438cd0a74dc95a011fceb27db18cb080-refs/branch-heads/5060@{#232})
2 Likes

As mach-nix was one of the most recommended solutions in this thread, I thought I’d note that it is now unmaintained, with a recommendation to investigate dream2nix instead.

Also some relevant info at packaging - How to package my software in nix or write my own package derivation for nixpkgs - Unix & Linux Stack Exchange

I don’t think symlinkJoin is really needed, you can just add all the paths to the tools you need to the wrapProgram command, e.g. --prefix PATH : ${lib.makeBinPath [ chromedriver ] }. That’s what I do for my scripts (adding "$out" to that list if I am packaging scripts calling each other as one derivation).

An alternative to wrapProgram for when you need to package shell scripts is resholve which tries to figure out (and substitute the paths to) the dependencies in shell scripts automatically but I haven’t used it yet.

1 Like

Any chance you could provide a more complete example? When I try with that approach it ends up still using the system chromedriver instead of the nix-installed one.

The --prefix PATH method will work provided the tool you are wrapping actually uses PATH (i.e. try calling the dependencies without any path and let the exec syscall do the PATH search). Many tools have their own configuration or just use some arbitrary defaults like look for dependencies in the same directory (that way symlinkJoin will provide that) then fall back on hardcoded default paths.

Great response on wrapping an existing bash script for use in NixOS via runCommandLocal and makeWrapper: Can I package a shell script without rewriting it? - #5 by lilyball

You may have found this answer elsewhere in the meantime, but:

Flakes are ~pure (or can be), but the purity we’re talking about is in the context of evaluating and building it, not in the context of running (or otherwise using) the result.

Once you run a built artifact, it does what it does. If it’s a compiled program that picks up executables from the environment, it’ll pick them up from the invocation-time environment. If it’s a python script that creates files in the current directory, it’ll create them in the current directory at invocation time.

Sometimes we’ll patch/alter this kind of behavior. (i.e., some program that looks for plugins in ~/.some_program/plugins/ might be patched to have a withPlugins function, and to use that path for the plugins). Most of the time, the program will need to touch stuff like this to work the way people expect.

The commands invoked by a shell script are usually found via PATH at runtime. Nix doesn’t really know Shell–scripts are just big balls of strings. Nix has no clue what dependencies they use. Even if you tell it one’s present, it won’t know where.

When we want them to work differently, we have to fix them. There are quite a few ways, but I laid out the main patterns in a post last year (i.e., most of the other ways are variations on these approaches with more/worse tradeoffs):

2 Likes

Thanks for a thoughtful response.

Yes, I’m still (slowly) learning quite a bit as I go.

I think part of my confusion stems from nix-shell --pure setting up a fairly clean runtime environment:

$ nix-shell --pure -p bash --command $'printf "${PATH//:/\'\n\'}\n"'
/nix/store/2pkv7vksd0z60vlinz2hdkjwikhwm8ay-bash-interactive-5.2-p15/bin
/nix/store/5frk2kjzncmvrxv4n36vm7pfpwiwmjw1-clang-wrapper-11.1.0/bin
/nix/store/inv8q9239whri3kq8jvv1fxg71vh8sk2-clang-11.1.0/bin
/nix/store/v53sx7z98j0qsiljnc87m72rv5sbjyd0-coreutils-9.1/bin
/nix/store/i0imb4808x3rikxfmrcadnnhyy2iajrb-cctools-binutils-darwin-wrapper-973.0.1/bin
/nix/store/3iby92iv1b15zfvyid68nzcljvkyf0mp-cctools-binutils-darwin-973.0.1/bin
/nix/store/yay8xkbj1gd0imx8i175qfqr961w5n5x-bash-5.2-p15/bin
/nix/store/v53sx7z98j0qsiljnc87m72rv5sbjyd0-coreutils-9.1/bin
/nix/store/sg9hzy17b37x7spcgl9sn2fgal84bkfd-findutils-4.9.0/bin
/nix/store/hpl2nv6z3a77if54j9mcgn47bny8jkxc-diffutils-3.9/bin
/nix/store/acxn433jn0lgx6kb69x01knwra457xwp-gnused-4.9/bin
/nix/store/524by75kqcds6090ywcgx3jmyzl1hn22-gnugrep-3.7/bin
/nix/store/6awh5jcca7m9vxi4vlw3cw6y0wh6y1ki-gawk-5.2.1/bin
/nix/store/lwllldj9j8dmhk6768412akiwqdbix6h-gnutar-1.35/bin
/nix/store/z8s3clp93jz752r4phh1fh9hq2hqma2k-gzip-1.12/bin
/nix/store/qzcsr84kf84llrwslrkgcili4qfnqlzm-bzip2-1.0.8-bin/bin
/nix/store/q7hivcg3yfdw30v69z5vkcq0vszwlza1-gnumake-4.4.1/bin
/nix/store/yay8xkbj1gd0imx8i175qfqr961w5n5x-bash-5.2-p15/bin
/nix/store/gqwjp3rcvks26iv201d9sxvsvhy3v0x8-patch-2.7.6/bin
/nix/store/hl9z5vdf0kx7yza83kdpi297ijqjqc2d-xz-5.4.3-bin/bin
/nix/store/lqdah76ip864n5i8gsr8akfvgpwr08a9-file-5.44/bin

In contrast with the “pure by default” flakes equivalent: nix shell, contaminated by the rest of my default PATH:

$ nix shell nixpkgs#bash --command bash -c $'printf "${PATH//:/\'\n\'}\n"'
/nix/store/ypiwg0x6l2pqgr2a402rjmans9k4np1k-bash-5.2-p15-man/bin
/nix/store/yay8xkbj1gd0imx8i175qfqr961w5n5x-bash-5.2-p15/bin
/Users/n8henrie/.cargo/bin
/Users/n8henrie/go/bin
/Users/n8henrie/.rbenv/shims
/Users/n8henrie/.local/bin
/opt/homebrew/sbin
/opt/homebrew/bin
/Users/n8henrie/.nix-profile/bin
/etc/profiles/per-user/n8henrie/bin
/run/current-system/sw/bin
/nix/var/nix/profiles/default/bin
/usr/local/bin
/usr/bin
/usr/sbin
/bin
/sbin
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources

unless i additionally include -i:

$ nix shell -i nixpkgs#bash --command bash -c $'printf "${PATH//:/\'\n\'}\n"'
/nix/store/ypiwg0x6l2pqgr2a402rjmans9k4np1k-bash-5.2-p15-man/bin
/nix/store/yay8xkbj1gd0imx8i175qfqr961w5n5x-bash-5.2-p15/bin

but now I recognize that I need to have different expectations for the build-time and run-time environments, and – as you explained – having flakes be free from build-time impurities doesn’t have much to do with the runtime (or shell) environments!

I’ll read that post, thanks for the link.

1 Like

The main impurities that flakes react against are channels and NIX_PATH. The newer cli is different, and it is ~correlated with flakes, but isn’t quite synonymous with them.

The default (flagless) modes for both shell commands are similar in what they let in because they’re meeting similar user needs. The move from --pure to --ignore-environment is probably good when it comes to helping us avoid conflating purity with a shell environment.

2 Likes