{"article":{"slug":"script-dependencies-with-nix","title":"Script dependencies with Nix","subtitle":null,"summary":"kqr of Entropic Thoughts shows how to use a Nix flake on any Linux distribution to manage a simple script's dependencies, packaging a Python text-to-speech script as a binary and then adapting the approach to other languages.","content_type":"tutorial","language":"en","canonical_url":"https://entropicthoughts.com/python-dependencies-with-nix","author":{"name":"kqr","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Entropic Thoughts","url":"https://entropicthoughts.com/","listing_slug":null,"listing":null},"topics":[{"name":"Nix","slug":"nix","url":"https://listedarticles.com/topics/nix"},{"name":"Python","slug":"python","url":"https://listedarticles.com/topics/python"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1907,"reading_minutes":8,"published_at":"2026-10-21T22:00:00.000Z","added_at":"2026-10-22T14:17:53.594Z","updated_at":"2026-10-22T14:17:53.594Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/script-dependencies-with-nix","markdown_url":"https://listedarticles.com/articles/script-dependencies-with-nix.md","example":false,"citation":"kqr, Entropic Thoughts. \"Script dependencies with Nix.\" 21 Oct 2026. https://entropicthoughts.com/python-dependencies-with-nix (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://entropicthoughts.com/python-dependencies-with-nix"},"body_markdown":"# Script dependencies with Nix\n\nby kqr,\npublished 2026-10-22\n\nTags:\n\n- [programming](https://entropicthoughts.com/tags.html#programming)\n- [python](https://entropicthoughts.com/tags.html#python)\n- [nix](https://entropicthoughts.com/tags.html#nix)\n\n![python-dependencies-with-nix.jpg](https://entropicthoughts.com/image/banner/python-dependencies-with-nix.jpg)\n\nOn the go? [Listen to an ai-narrated version of this article](https://entropicthoughts.com/audio/python-dependencies-with-nix.opus).\n\nConfused? [More about ai narration](https://entropicthoughts.com/ai-narration).\n\nIn the theme of [NixOS enabling fearlessness](https://entropicthoughts.com/nixos-is-making-me-less-afraid), here’s a way I use Nix on *other*\nLinux distributions to sleep better at night: managing dependencies of simple\nscripts.\n\nThis way of using Nix is for everyone, not just those willing to try NixOS.\nWe’ll look at it first for a Python script, and then see how it can be adapted\nto any other language.\n\n# Managing script dependencies with Nix\n\nI wanted to create a script that would read the verbatim content of my articles\nout loud to me with a synthesised voice.11 This is entirely separate from the\nrecent ai narration experiment. Reading the article out loud is one of my\nself-editing steps, to detect flow problems and awkward phrasings. I’ve\ndiscovered that hearing someone else read the article is slightly more effective\nthan reading it out loud myself, though both are useful. When I read it out loud\nmyself, I tend to read what I intended to say, not the words that are actually\non the page. One of the more convenient ways to do this is with Python, where\nI’ve heard good things about [Trafilatura](https://trafilatura.readthedocs.io/en/latest/) for extracting the contents out of a\nweb page, filtering on types of content, etc.\n\nBut Trafilatura is a third-party Python dependency. Those suck because they\nbreak your scripts when you so much as glance sideways at `pip`. The code also\nended up depending on `aplay`, which is a system dependency, making it two\ndifferent sources of future breakage. A couple of years ago, I would probably\nhave judged that to be too much maintenance, so I wouldn’t have created the\nscript. Fortunately, now I know about Nix.\n\n## General outline of a Nix flake\n\nWe have a Python script, and we want to make it trivially runnable anywhere,\nwithout having to worry about dependencies. To get to that position, we first\ncreate a file called `flake.nix` in the same directory as the Python script, and\nwe start to fill it in:\n\nIn[1]:\n\n```\n{\n  # Pick the package versions of the most recent NixOS.\n  # Change these numbers to upgrade later if desired.\n  # If the upgrade breaks, it can be rolled back by\n  # decrementing these numbers again.\n  inputs.nixpkgs.url = \"github:NixOS/nixpkgs/nixos-26.05\";\n\n  outputs = { nixpkgs, ... }:\n    let\n      # Generate outputs for both Intel and ARM. In this\n      # case, I do not include macOS among the supported\n      # systems because aplay is linux-only.\n      #\n      # This references \"legacyPackages\" which might sound\n      # odd, but in Nix flake contexts, the entire nixpkgs\n      # package set is called \"legacy\" because it uses an\n      # older structure for organisation.\n      forAllSystems = f: nixpkgs.lib.genAttrs\n        [ \"x86_64-linux\" \"aarch64-linux\" ]\n        (system: f nixpkgs.legacyPackages.${system});\n    in {\n      # The default entry point of a Nix flake are the\n      # derivations named \"packages.<system>.default\". Here\n      # they are defined for the two Linux architectures.\n      packages = forAllSystems (pkgs: {\n        default =\n          # Default package definition to be filled in!\n      });\n    };\n}\n```\n\nThis outline is the same every time we use Nix to manage the dependencies of a\nprogram, regardless of language, choice of dependencies, etc. The specifics that\ndiffer are those in the definition of the `default` package.\n\n## Creating a binary from a Python script\n\nSince we have a Python script, we’ll use the `writePython3Bin` helper function\nto turn that into an executable with Nix-managed dependencies. It takes two\narguments we need:\n\n- `makeWrapperArgs` replaces the main script with a small wrapper script that\n  sets up the right environment and then `execve`s into the main script. We will\n  use it to set up the `PATH` variable to point at Nix-managed versions of\n  system dependencies.22 This may be the most common way to use it, but it can\n  be used to fix up the environment in any number of other ways. This means the\n  Python script doesn’t have to be written with Nix-managed dependencies in\n  mind. As far as the script cares, it thinks it’s running in a normal\n  environment. But the commands it runs come from the Nix store, and are of the\n  specific versions we requested.\n- `libraries` lets us specify which Python dependencies should be available to\n  the Python interpreter when it runs our script.\n\nFilling in the right values for these arguments, our `flake.nix` will look\nsomething like the following.\n\nIn[2]:\n\n```\n{\n  inputs.nixpkgs.url = \"github:NixOS/nixpkgs/nixos-26.05\";\n\n  outputs = { nixpkgs, ... }:\n    let\n      forAllSystems = f: nixpkgs.lib.genAttrs\n        [ \"x86_64-linux\" \"aarch64-linux\" ]\n        (system: f nixpkgs.legacyPackages.${system});\n    in {\n      packages = forAllSystems (pkgs: {\n        # Create executable from Python script.\n        default = pkgs.writers.writePython3Bin \"readarticle\" {\n          # Make system dependencies available.\n          makeWrapperArgs = [\n            \"--prefix\" \"PATH\" \":\" (pkgs.lib.makeBinPath [\n              pkgs.alsa-utils\n            ])\n          ];\n          # Make libraries available.\n          libraries = [\n            pkgs.python3Packages.requests\n            pkgs.python3Packages.trafilatura\n          ];\n        } (builtins.readFile ./main.py);\n      });\n    };\n}\n```\n\nAnd that’s it. After this, we can run the script with `nix run .` and the first\ntime we do it, Nix is going to set up all the dependencies and then run the\nscript. On subsequent runs, Nix recognises that everything is in place and runs\nthe script right away.33 Actually, the `writePython3Bin` function does one\nmore thing: it runs the script through a linting pass. This can be disabled, but\nI generally like keeping the default behaviour. The corresponding writeBashBin\nit runs the script through ShellCheck and fails the build before running the\nscript if it doesn’t ShellCheck cleanly. *That* is very valuable!\n\nIf we copy this script to another computer, it will continue to work exactly the\nsame as on the first computer, because Nix will set up all the same dependencies\nthe same way before running the script. If we forget about it and run it a year\nlater, it will continue to run just the same as before, because the executable\ncreated by Nix refers to the exact same set of dependencies regardless of what\nelse has changed on the system.\n\n# Adapting to other languages\n\nI imagine that of those reading this article, a small crowd starts murmuring\nabout `uv`. From what I understand, `uv` does take a lot of the headache out of\nmanaging Python dependencies.44 I don’t know, honestly. It came around after I\nstopped working professionally with Python, so learning and understanding it\nwould happen in my limited spare time, and now that I have Nix, I think I have\nbetter things to learn and understand. But it is only for Python. *Nix works\nfor any dependency, with any language.* If we have an R script we want to manage\nthe dependencies for, we’d do the exact same thing and use `writeRscriptBin`\ninstead.\n\nThe way to find the right helper function is by knowing that they are called\n*writers*, and then [searching Noogle for them](https://noogle.dev/q/?term=writers+python). By excluding the language from\nthat search, we can see there are writers for other languages too, like C,\nJavaScript, Shell, Haskell, Perl, and more.\n\nBut there’s no writer helper function specifically for Emacs Lisp. What if we\nhave an Emacs Lisp script we want Nix to manage the dependencies of? Then we\ncan still use the lower level `makeScriptWriter` function. Here’s a generic\nexample of that:\n\nIn[3]:\n\n```\ndefault = pkgs.writers.makeScriptWriter\n  {\n    # System dependencies work the same as before.\n    makeWrapperArgs = [\n      \"--prefix\" \"PATH\" \":\" (pkgs.lib.makeBinPath [\n        pkgs.some-package-with-executable\n      ])\n    ];\n\n    # Make sure the interpreter is produced with the\n    # necessary run-time dependencies. Most packaged\n    # scripting languages support the \"withPackages\"\n    # convention for this.\n    interpreter = \"${(pkgs.interpreter.withPackages (ps: [\n      ps.dependency-1\n      ps.dependency-2\n    ]))}/bin/interpreter-binary\";\n  }\n  \"/bin/our-executable-name\"\n  (builtins.readFile ./source-file-path);\n```\n\nFor Emacs Lisp, the interpreter package we’d use would be Emacs. The output path\n(`/bin/our-executable-name`) is package relative, so we don’t have to worry\nabout it clobbering the file system.\n\nOf course, we also have compiled languages, like Ada. Ada also doesn’t have a\nready-made writer function, so we’d use the generic `makeBinWriter` helper,\nwhich takes a `compileScript` argument where we specify how to compile.\n\nIn[4]:\n\n```\ndefault = pkgs.writers.makeBinWriter\n  {\n    # System dependencies work the same again.\n    makeWrapperArgs = [\n      \"--prefix\" \"PATH\" \":\" (pkgs.lib.makeBinPath [\n        pkgs.some-package-with-executable\n      ])\n    ];\n\n    # The compilation script can reference any compiler and\n    # any libraries in nixpkgs, and they will be managed by\n    # Nix during the build. Any runtime dependencies will be\n    # managed by Nix automatically too.\n    #\n    # The file we read with readFile is called $contentPath\n    # in this script, and the output must be written to the\n    # path in the $out variable.\n    compileScript = ''\n      ${pkgs.some-compiler}/bin/compiler-binary \\\n        -o $out $contentPath\n    '';\n  }\n  \"/bin/our-binary-name\"\n  (builtins.readFile ./source-file-path);\n```\n\nAlthough this looks like deep, black magic, it’s not. What’s so neat about Nix\nis that it uses regular Unix paths, regular Unix environment variables, and\ncontent hashing to figure out what depends on what and whether an input has\nchanged, necessitating rebuilding.\n\n# Learning more\n\nThe important question I haven’t yet answered is “How do you know all this?”\n\nI wish I could answer “I have read the documentation” but that’s not it. There\nare two parts to the knowledge, and the learning paths are different for them:\n\n- At its core, Nix-the-system is a simple machine that can be grasped in few\n  hours of reading. Similarly, Nix-the-language is small enough to be learned in\n  a day or two. Both of these can be learned from Eelco Dolstra’s PhD thesis,\n  which is a pleasant read and highly recommended, even if you end up not using\n  Nix.55 Though I have a hard time imagining how anyone could read the thesis\n  and not end up with a desire to use Nix.\n- Then there’s the huge library of code called nixpkgs. That’s where we find the\n  writers we’ve used in this article, and it’s not well documented anywhere. The\n  way to learn how e.g. writers work is to [read their source code](https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/writers/scripts.nix). A lot of\n  what’s in nixpkgs is only learnable by reading the code. That might not sound\n  like a big deal, since Nix is a small and easy language. Unfortunately, plenty\n  of what’s in nixpkgs depends on other things in nixpkgs, and it is common to\n  pass named arguments implicitly through attribute sets (key–value\n  dictionaries), so it’s not always obvious which things are actually passed\n  down intentionally and which are not, nor how they are used. It’s easy to end\n  up reading a lot of code.\n\nBut as long as the scripts you write are in languages that already have writers\nin nixpkgs, you can mostly use the same template for all of them except replace\nthe specific writer you use.\n\n# Sidenotes\n\n1\n\nThis is entirely separate from the\nrecent ai narration experiment. Reading the article out loud is one of my\nself-editing steps, to detect flow problems and awkward phrasings. I’ve\ndiscovered that hearing someone else read the article is slightly more effective\nthan reading it out loud myself, though both are useful. When I read it out loud\nmyself, I tend to read what I intended to say, not the words that are actually\non the page.\n\n2\n\nThis may be the most common way to use it, but it can\nbe used to fix up the environment in any number of other ways.\n\n3\n\nActually, the `writePython3Bin` function does one\nmore thing: it runs the script through a linting pass. This can be disabled, but\nI generally like keeping the default behaviour. The corresponding writeBashBin\nit runs the script through ShellCheck and fails the build before running the\nscript if it doesn’t ShellCheck cleanly. *That* is very valuable!\n\n4\n\nI don’t know, honestly. It came around after I\nstopped working professionally with Python, so learning and understanding it\nwould happen in my limited spare time, and now that I have Nix, I think I have\nbetter things to learn and understand.\n\n5\n\nThough I have a hard time imagining how anyone could read the thesis\nand not end up with a desire to use Nix.\n","body_html":"<h1 id=\"script-dependencies-with-nix\">Script dependencies with Nix</h1>\n<p>by kqr,\npublished 2026-10-22</p>\n<p>Tags:</p>\n<ul><li><a href=\"https://entropicthoughts.com/tags.html#programming\" rel=\"nofollow ugc noopener\">programming</a></li><li><a href=\"https://entropicthoughts.com/tags.html#python\" rel=\"nofollow ugc noopener\">python</a></li><li><a href=\"https://entropicthoughts.com/tags.html#nix\" rel=\"nofollow ugc noopener\">nix</a></li></ul>\n<figure><img src=\"https://entropicthoughts.com/image/banner/python-dependencies-with-nix.jpg\" alt=\"python-dependencies-with-nix.jpg\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></figure>\n<p>On the go? <a href=\"https://entropicthoughts.com/audio/python-dependencies-with-nix.opus\" rel=\"nofollow ugc noopener\">Listen to an ai-narrated version of this article</a>.</p>\n<p>Confused? <a href=\"https://entropicthoughts.com/ai-narration\" rel=\"nofollow ugc noopener\">More about ai narration</a>.</p>\n<p>In the theme of <a href=\"https://entropicthoughts.com/nixos-is-making-me-less-afraid\" rel=\"nofollow ugc noopener\">NixOS enabling fearlessness</a>, here’s a way I use Nix on <em>other</em>\nLinux distributions to sleep better at night: managing dependencies of simple\nscripts.</p>\n<p>This way of using Nix is for everyone, not just those willing to try NixOS.\nWe’ll look at it first for a Python script, and then see how it can be adapted\nto any other language.</p>\n<h1 id=\"managing-script-dependencies-with-nix\">Managing script dependencies with Nix</h1>\n<p>I wanted to create a script that would read the verbatim content of my articles\nout loud to me with a synthesised voice.11 This is entirely separate from the\nrecent ai narration experiment. Reading the article out loud is one of my\nself-editing steps, to detect flow problems and awkward phrasings. I’ve\ndiscovered that hearing someone else read the article is slightly more effective\nthan reading it out loud myself, though both are useful. When I read it out loud\nmyself, I tend to read what I intended to say, not the words that are actually\non the page. One of the more convenient ways to do this is with Python, where\nI’ve heard good things about <a href=\"https://trafilatura.readthedocs.io/en/latest/\" rel=\"nofollow ugc noopener\">Trafilatura</a> for extracting the contents out of a\nweb page, filtering on types of content, etc.</p>\n<p>But Trafilatura is a third-party Python dependency. Those suck because they\nbreak your scripts when you so much as glance sideways at <code>pip</code>. The code also\nended up depending on <code>aplay</code>, which is a system dependency, making it two\ndifferent sources of future breakage. A couple of years ago, I would probably\nhave judged that to be too much maintenance, so I wouldn’t have created the\nscript. Fortunately, now I know about Nix.</p>\n<h2 id=\"general-outline-of-a-nix-flake\">General outline of a Nix flake</h2>\n<p>We have a Python script, and we want to make it trivially runnable anywhere,\nwithout having to worry about dependencies. To get to that position, we first\ncreate a file called <code>flake.nix</code> in the same directory as the Python script, and\nwe start to fill it in:</p>\n<p>In[1]:</p>\n<pre><code>{\n  # Pick the package versions of the most recent NixOS.\n  # Change these numbers to upgrade later if desired.\n  # If the upgrade breaks, it can be rolled back by\n  # decrementing these numbers again.\n  inputs.nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-26.05&quot;;\n\n  outputs = { nixpkgs, ... }:\n    let\n      # Generate outputs for both Intel and ARM. In this\n      # case, I do not include macOS among the supported\n      # systems because aplay is linux-only.\n      #\n      # This references &quot;legacyPackages&quot; which might sound\n      # odd, but in Nix flake contexts, the entire nixpkgs\n      # package set is called &quot;legacy&quot; because it uses an\n      # older structure for organisation.\n      forAllSystems = f: nixpkgs.lib.genAttrs\n        [ &quot;x86_64-linux&quot; &quot;aarch64-linux&quot; ]\n        (system: f nixpkgs.legacyPackages.${system});\n    in {\n      # The default entry point of a Nix flake are the\n      # derivations named &quot;packages.&lt;system&gt;.default&quot;. Here\n      # they are defined for the two Linux architectures.\n      packages = forAllSystems (pkgs: {\n        default =\n          # Default package definition to be filled in!\n      });\n    };\n}</code></pre>\n<p>This outline is the same every time we use Nix to manage the dependencies of a\nprogram, regardless of language, choice of dependencies, etc. The specifics that\ndiffer are those in the definition of the <code>default</code> package.</p>\n<h2 id=\"creating-a-binary-from-a-python-script\">Creating a binary from a Python script</h2>\n<p>Since we have a Python script, we’ll use the <code>writePython3Bin</code> helper function\nto turn that into an executable with Nix-managed dependencies. It takes two\narguments we need:</p>\n<ul><li><p><code>makeWrapperArgs</code> replaces the main script with a small wrapper script that</p><p>sets up the right environment and then <code>execve</code>s into the main script. We will\nuse it to set up the <code>PATH</code> variable to point at Nix-managed versions of\nsystem dependencies.22 This may be the most common way to use it, but it can\nbe used to fix up the environment in any number of other ways. This means the\nPython script doesn’t have to be written with Nix-managed dependencies in\nmind. As far as the script cares, it thinks it’s running in a normal\nenvironment. But the commands it runs come from the Nix store, and are of the\nspecific versions we requested.</p></li><li><p><code>libraries</code> lets us specify which Python dependencies should be available to</p><p>the Python interpreter when it runs our script.</p></li></ul>\n<p>Filling in the right values for these arguments, our <code>flake.nix</code> will look\nsomething like the following.</p>\n<p>In[2]:</p>\n<pre><code>{\n  inputs.nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-26.05&quot;;\n\n  outputs = { nixpkgs, ... }:\n    let\n      forAllSystems = f: nixpkgs.lib.genAttrs\n        [ &quot;x86_64-linux&quot; &quot;aarch64-linux&quot; ]\n        (system: f nixpkgs.legacyPackages.${system});\n    in {\n      packages = forAllSystems (pkgs: {\n        # Create executable from Python script.\n        default = pkgs.writers.writePython3Bin &quot;readarticle&quot; {\n          # Make system dependencies available.\n          makeWrapperArgs = [\n            &quot;--prefix&quot; &quot;PATH&quot; &quot;:&quot; (pkgs.lib.makeBinPath [\n              pkgs.alsa-utils\n            ])\n          ];\n          # Make libraries available.\n          libraries = [\n            pkgs.python3Packages.requests\n            pkgs.python3Packages.trafilatura\n          ];\n        } (builtins.readFile ./main.py);\n      });\n    };\n}</code></pre>\n<p>And that’s it. After this, we can run the script with <code>nix run .</code> and the first\ntime we do it, Nix is going to set up all the dependencies and then run the\nscript. On subsequent runs, Nix recognises that everything is in place and runs\nthe script right away.33 Actually, the <code>writePython3Bin</code> function does one\nmore thing: it runs the script through a linting pass. This can be disabled, but\nI generally like keeping the default behaviour. The corresponding writeBashBin\nit runs the script through ShellCheck and fails the build before running the\nscript if it doesn’t ShellCheck cleanly. <em>That</em> is very valuable!</p>\n<p>If we copy this script to another computer, it will continue to work exactly the\nsame as on the first computer, because Nix will set up all the same dependencies\nthe same way before running the script. If we forget about it and run it a year\nlater, it will continue to run just the same as before, because the executable\ncreated by Nix refers to the exact same set of dependencies regardless of what\nelse has changed on the system.</p>\n<h1 id=\"adapting-to-other-languages\">Adapting to other languages</h1>\n<p>I imagine that of those reading this article, a small crowd starts murmuring\nabout <code>uv</code>. From what I understand, <code>uv</code> does take a lot of the headache out of\nmanaging Python dependencies.44 I don’t know, honestly. It came around after I\nstopped working professionally with Python, so learning and understanding it\nwould happen in my limited spare time, and now that I have Nix, I think I have\nbetter things to learn and understand. But it is only for Python. *Nix works\nfor any dependency, with any language.* If we have an R script we want to manage\nthe dependencies for, we’d do the exact same thing and use <code>writeRscriptBin</code>\ninstead.</p>\n<p>The way to find the right helper function is by knowing that they are called\n<em>writers</em>, and then <a href=\"https://noogle.dev/q/?term=writers+python\" rel=\"nofollow ugc noopener\">searching Noogle for them</a>. By excluding the language from\nthat search, we can see there are writers for other languages too, like C,\nJavaScript, Shell, Haskell, Perl, and more.</p>\n<p>But there’s no writer helper function specifically for Emacs Lisp. What if we\nhave an Emacs Lisp script we want Nix to manage the dependencies of? Then we\ncan still use the lower level <code>makeScriptWriter</code> function. Here’s a generic\nexample of that:</p>\n<p>In[3]:</p>\n<pre><code>default = pkgs.writers.makeScriptWriter\n  {\n    # System dependencies work the same as before.\n    makeWrapperArgs = [\n      &quot;--prefix&quot; &quot;PATH&quot; &quot;:&quot; (pkgs.lib.makeBinPath [\n        pkgs.some-package-with-executable\n      ])\n    ];\n\n    # Make sure the interpreter is produced with the\n    # necessary run-time dependencies. Most packaged\n    # scripting languages support the &quot;withPackages&quot;\n    # convention for this.\n    interpreter = &quot;${(pkgs.interpreter.withPackages (ps: [\n      ps.dependency-1\n      ps.dependency-2\n    ]))}/bin/interpreter-binary&quot;;\n  }\n  &quot;/bin/our-executable-name&quot;\n  (builtins.readFile ./source-file-path);</code></pre>\n<p>For Emacs Lisp, the interpreter package we’d use would be Emacs. The output path\n(<code>/bin/our-executable-name</code>) is package relative, so we don’t have to worry\nabout it clobbering the file system.</p>\n<p>Of course, we also have compiled languages, like Ada. Ada also doesn’t have a\nready-made writer function, so we’d use the generic <code>makeBinWriter</code> helper,\nwhich takes a <code>compileScript</code> argument where we specify how to compile.</p>\n<p>In[4]:</p>\n<pre><code>default = pkgs.writers.makeBinWriter\n  {\n    # System dependencies work the same again.\n    makeWrapperArgs = [\n      &quot;--prefix&quot; &quot;PATH&quot; &quot;:&quot; (pkgs.lib.makeBinPath [\n        pkgs.some-package-with-executable\n      ])\n    ];\n\n    # The compilation script can reference any compiler and\n    # any libraries in nixpkgs, and they will be managed by\n    # Nix during the build. Any runtime dependencies will be\n    # managed by Nix automatically too.\n    #\n    # The file we read with readFile is called $contentPath\n    # in this script, and the output must be written to the\n    # path in the $out variable.\n    compileScript = &#39;&#39;\n      ${pkgs.some-compiler}/bin/compiler-binary \\\n        -o $out $contentPath\n    &#39;&#39;;\n  }\n  &quot;/bin/our-binary-name&quot;\n  (builtins.readFile ./source-file-path);</code></pre>\n<p>Although this looks like deep, black magic, it’s not. What’s so neat about Nix\nis that it uses regular Unix paths, regular Unix environment variables, and\ncontent hashing to figure out what depends on what and whether an input has\nchanged, necessitating rebuilding.</p>\n<h1 id=\"learning-more\">Learning more</h1>\n<p>The important question I haven’t yet answered is “How do you know all this?”</p>\n<p>I wish I could answer “I have read the documentation” but that’s not it. There\nare two parts to the knowledge, and the learning paths are different for them:</p>\n<ul><li><p>At its core, Nix-the-system is a simple machine that can be grasped in few</p><p>hours of reading. Similarly, Nix-the-language is small enough to be learned in\na day or two. Both of these can be learned from Eelco Dolstra’s PhD thesis,\nwhich is a pleasant read and highly recommended, even if you end up not using\nNix.55 Though I have a hard time imagining how anyone could read the thesis\nand not end up with a desire to use Nix.</p></li><li><p>Then there’s the huge library of code called nixpkgs. That’s where we find the</p><p>writers we’ve used in this article, and it’s not well documented anywhere. The\nway to learn how e.g. writers work is to <a href=\"https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/writers/scripts.nix\" rel=\"nofollow ugc noopener\">read their source code</a>. A lot of\nwhat’s in nixpkgs is only learnable by reading the code. That might not sound\nlike a big deal, since Nix is a small and easy language. Unfortunately, plenty\nof what’s in nixpkgs depends on other things in nixpkgs, and it is common to\npass named arguments implicitly through attribute sets (key–value\ndictionaries), so it’s not always obvious which things are actually passed\ndown intentionally and which are not, nor how they are used. It’s easy to end\nup reading a lot of code.</p></li></ul>\n<p>But as long as the scripts you write are in languages that already have writers\nin nixpkgs, you can mostly use the same template for all of them except replace\nthe specific writer you use.</p>\n<h1 id=\"sidenotes\">Sidenotes</h1>\n<p>1</p>\n<p>This is entirely separate from the\nrecent ai narration experiment. Reading the article out loud is one of my\nself-editing steps, to detect flow problems and awkward phrasings. I’ve\ndiscovered that hearing someone else read the article is slightly more effective\nthan reading it out loud myself, though both are useful. When I read it out loud\nmyself, I tend to read what I intended to say, not the words that are actually\non the page.</p>\n<p>2</p>\n<p>This may be the most common way to use it, but it can\nbe used to fix up the environment in any number of other ways.</p>\n<p>3</p>\n<p>Actually, the <code>writePython3Bin</code> function does one\nmore thing: it runs the script through a linting pass. This can be disabled, but\nI generally like keeping the default behaviour. The corresponding writeBashBin\nit runs the script through ShellCheck and fails the build before running the\nscript if it doesn’t ShellCheck cleanly. <em>That</em> is very valuable!</p>\n<p>4</p>\n<p>I don’t know, honestly. It came around after I\nstopped working professionally with Python, so learning and understanding it\nwould happen in my limited spare time, and now that I have Nix, I think I have\nbetter things to learn and understand.</p>\n<p>5</p>\n<p>Though I have a hard time imagining how anyone could read the thesis\nand not end up with a desire to use Nix.</p>","headings":[{"level":1,"text":"Script dependencies with Nix","id":"script-dependencies-with-nix"},{"level":1,"text":"Managing script dependencies with Nix","id":"managing-script-dependencies-with-nix"},{"level":2,"text":"General outline of a Nix flake","id":"general-outline-of-a-nix-flake"},{"level":2,"text":"Creating a binary from a Python script","id":"creating-a-binary-from-a-python-script"},{"level":1,"text":"Adapting to other languages","id":"adapting-to-other-languages"},{"level":1,"text":"Learning more","id":"learning-more"},{"level":1,"text":"Sidenotes","id":"sidenotes"}]}}