nix-script is no longer maintained at this repo. Check https://github.com/dschrempf/nix-script for the most up-to-date fork as of this writing, or https://github.com/BrianHicks/nix-script/forks to find others. Happy hacking!
nix-script
lets you write quick scripts in compiled languages, transparently compile and cache them, and pull in whatever dependencies you need from the Nix ecosystem.
This README is intended more as a reference, but I also wrote a blog post to explain what this is and why it exists.
You might have guessed this already, but you can install this package with nix
!
nix-env -if https://github.com/BrianHicks/nix-script/archive/main.tar.gz
This project's CI also pushes Linux and macOS builds to nix-script.cachix.org
automatically, meaning cachix add nix-script
should set you up to compile fewer things.
Once added as a flake, we provide these attributes in package
:
nix-script-all
(the default package): contains everything belownix-script
: onlynix-script
nix-script-bash
: onlynix-script-bash
, but referencing the correctnix-script
nix-script-haskell
: onlynix-script-haskell
, but referencing the correctnix-script
We also provide an overlay
, which has all of these.
Once added to Niv (niv add BrianHicks/nix-script
), you should be able to import sources.nix-script { };
and have the same things described in the flakes section above, except you'll have to explicitly reference things like overlay."${builtins.currentSystem}"
.
The normal nix-script
invocation is controlled using shebang lines (lines starting with #!
by default, although you can change it to whatever you like with the --indicator
flag.)
Starting your file with #!/usr/bin/env nix-script
makes these options available:
What? | Shebang line | Notes |
---|---|---|
How to compile the script to a binary | #!build |
The command specified here must read from $SRC and write to $OUT |
Use all files in the given directory | #!buildRoot |
Must be a parent directory of the script |
Specify build-time dependencies | #!buildInputs |
A space-separated list of Nix expressions |
Use an alternative interpreter | #!interpreter |
Run this script with the given binary (must be in runtimeInputs ) |
Specify runtime dependencies | #!runtimeInputs |
This should be a space-separated list of Nix expressions. |
Access auxillary files at runtime | #!runtimeFiles |
Make these files available at runtime (at the path given in RUNTIME_FILES_ROOT ) |
you can also control these options with equivalent command-line flags to nix-script
(see the --help
output for exact names.)
nix-script
also lets your compiled script know where it came from by setting the SCRIPT_FILE
environment variable to what you would have gotten in $0
if it was a shell script.
Building a new version for every change can get a little tiresome while developing.
If you want a quicker feedback loop, you can include --shell
in your nix-script
invocation (e.g. nix-script --shell path/to/script
) to drop into a development shell with your build-time and runtime dependencies.
This won't run your build command, but it will let you run it yourself, play around in REPLs, etc.
If you are making a wrapper script, you may find the --run
flag useful: it allows you to specify what command to run in the shell.
If your language ecosystem has some common watcher script, it might be nice to add a special mode to your wrapper for it!
(For example, nix-script-haskell
has a --ghcid
flag for this purpose.
See the source for how it's set up!)
In nix-script version 1, it was common to run up against the limits of a single file, whether that meant having namespace issues or simply a single file becoming unwieldy. Getting aroung this commonly meant giving up on all the nice things that nix-script provided (like faster feedback loops and transparent compilation caching) so it was a tough tradeoff.
Nix-script version 2 has two new flags to help with this: --build-root
and --export
.
Once you get to the point in your program's life cycle where you need multiple files, tell nix-script where the project root is with #!buildRoot
(or --build-root
) and we'll include all the files in that directory during builds.
This lets you do things like splitting out your source into multiple files, all of which will be checked when we try to determine whether or not we have a cache hit.
Once even that is not enough, you can include --export
in your nix-script invocation to print out the default.nix
that we would have used to build your script.
If you put that (or any default.nix
) inside the directory specified in #!buildRoot
, we'll use that instead of generating our own.
Once you get to the point of having a fully-realized directory with a default.nix
inside, you've arrived at a "real" derivation, and you can then use any Nix tooling you like to further modify your project.
If you are making a wrapper script for a new language, you can also use --build-root
to hold package manager files and extremely custom build.nix
files.
We also provide a --parse
flag which will ask nix-script
to parse any directives in the script and give them to you as JSON on stdout.
Caution: be aware that the format here is not stable yet and may change in backwards-incompatible ways without a corresponding major version bump in nix-script.
If you have any feedback on the data returned by --parse
, please open an issue!
nix-script-bash
exists to let you specify exact versions of your dependencies via Nix.
For example:
#!/usr/bin/env nix-script-bash
#!runtimeInputs jq
jq --help
nix-script-haskell
is a convenience wrapper for Haskell scripts.
In addition to the regular nix-script
options, nix-script-haskell
lets you specify some Haskell-specific options:
Shebang line | Notes | Example |
---|---|---|
#!haskellPackages |
Haskell packages to build with (you can get a list of available names by running nix-env -qaPA nixpkgs.haskellPackages .) |
#!haskellPackages text aeson |
#!ghcFlags |
Additional flags to pass to the compiler. | #!ghcFlags -threaded |
You can get quick compilation feedback with ghcid
by running nix-script-haskell --ghcid path/to/your/script.hs
.
nix-script
will generate derivations that import <nixpkgs> {}
by default.
That means all you need to do to control which nixpkgs
your scripts are built with is to set NIX_PATH
, for example to NIX_PATH=nixpkgs=/nix/store/HASHHASHHASH-source
.
For projects, this is reasonably easy to do in a mkShell
(for example by setting NIX_PATH = "nixpkgs=${pkgs.path}"
,) or by using makeWrapper
on nix-script
in a custom derivation.
NIX_PATH
is included in cache key calculations, so if you change your package set your scripts will automatically be rebuilt the next time you run them.
I want my open-source work to support projects addressing the climate crisis (for example, projects in clean energy, public transit, reforestation, or sustainable agriculture.) If you are working on such a project, and find a bug or missing feature in any of my libraries, please let me know and I will treat your issue as high priority. I'd also be happy to support such projects in other ways, just ask!
nix-script
is licensed under the BSD 3-Clause license, located at LICENSE
.