n8henrie.com
Technology, medicine, science, superstition... and having fun. Brought to you by Nathan Henrie.
https://n8henrie.com/
Thu, 15 Jan 2026 09:21:54 -0700
Thu, 15 Jan 2026 09:21:54 -0700
Jekyll v3.10.0
-
Nix: Debugging MacOS / Darwin Sandbox Issues
<p><strong>Bottom Line:</strong> Filtering the macos logs for sandbox violations can help flesh out the root cause of sandbox-related crashes when building with nix.
<!--more--></p>
<p>I recently started getting some unexpected build failures when trying to rebuild my <code class="language-plaintext highlighter-rouge">nix-darwin</code> system:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix build -v --print-build-logs github:nixos/nixpkgs/2bdc7039afa38f4330de69360a817e11f7e2f2c5#mpv-unwrapped
...
mpv> buildPhase completed in 38 seconds
mpv> Running phase: installPhase
mpv> mesonInstallPhase flags: ''
mpv> Installing mpv.1 to /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man/share/man/man1
mpv> Installing libmpv.2.dylib to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib
mpv> Installing mpv to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/include/mpv/client.h to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/include/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/include/mpv/render.h to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/include/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/include/mpv/render_gl.h to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/include/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/include/mpv/stream_cb.h to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/include/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/build/meson-private/mpv.pc to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/pkgconfig
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/input.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mplayer-input.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/restore-old-bindings.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/restore-osc-bindings.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv.bash-completion to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/bash-completion/completions
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/_mpv.zsh to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/zsh/site-functions
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv.fish to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/fish/vendor_completions.d
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv.metainfo.xml to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/metainfo
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/encoding-profiles.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/etc/mpv
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv-icon-8bit-16x16.png to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/icons/hicolor/16x16/apps
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv-icon-8bit-32x32.png to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/icons/hicolor/32x32/apps
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv-icon-8bit-64x64.png to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/icons/hicolor/64x64/apps
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv-icon-8bit-128x128.png to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/icons/hicolor/128x128/apps
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv-gradient.svg to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/icons/hicolor/scalable/apps
mpv> Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv-symbolic.svg to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/icons/hicolor/symbolic/apps
mpv> Installing symlink pointing to libmpv.2.dylib to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/libmpv.dylib
mpv> /nix/var/nix/builds/nix-58338-1051359316/source/TOOLS /nix/var/nix/builds/nix-58338-1051359316/source/build
mpv> /nix/var/nix/builds/nix-58338-1051359316/source/build
mpv> Running phase: fixupPhase
mpv> Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc to /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc/share/doc
mpv> Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/pkgconfig to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig
mpv> Patching '/nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig/mpv.pc' includedir to output /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev
mpv> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0...
mpv> patching script interpreter paths in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0
mpv> /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/umpv: interpreter directive changed from "#!/usr/bin/env python3" to "/nix/store/xcjk9ill54kjk8mzgq6yydnx9015lidg-python3-3.13.9/bin/python3"
mpv> /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv_identify.sh: interpreter directive changed from "#!/bin/sh" to "/nix/store/19zw2r9dl44wk3j5ncwsk743zr9fc584-bash-interactive-5.3p3/bin/sh"
mpv> stripping (with command strip and flags -S) in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/Applications
mpv> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev...
mpv> patching script interpreter paths in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev
mpv> stripping (with command strip and flags -S) in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib
mpv> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc...
mpv> patching script interpreter paths in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc
mpv> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man...
mpv> gzipping man pages under /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man/share/man/
mpv> patching script interpreter paths in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man
mpv> Running phase: installCheckPhase
mpv> Executing versionCheckPhase
mpv> Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --help
mpv>
mpv> Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --version
mpv>
error: Cannot build '/nix/store/y75gpq7cpspdlj0pyz7vz2dza8p8vrfb-mpv-0.40.0.drv'.
Reason: builder failed with exit code 2.
Output paths:
/nix/store/0k1fix2idrk4jwvq8m1l8sx6sqqrqc6v-mpv-0.40.0-dev
/nix/store/261j9ny9h3vjd7654wc388j9jibhi9xv-mpv-0.40.0-man
/nix/store/j4k3k512157c73falxnqshmqa35whxg6-mpv-0.40.0-doc
/nix/store/jndgs2x8g080032gma77ikcbw4vrp2bx-mpv-0.40.0
Last 25 log lines:
> /nix/var/nix/builds/nix-58338-1051359316/source/TOOLS /nix/var/nix/builds/nix-58338-1051359316/source/build
> /nix/var/nix/builds/nix-58338-1051359316/source/build
> Running phase: fixupPhase
> Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc to /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc/share/doc
> Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/pkgconfig to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig
> Patching '/nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig/mpv.pc' includedir to output /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev
> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0...
> patching script interpreter paths in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0
> /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/umpv: interpreter directive changed from "#!/usr/bin/env python3" to "/nix/store/xcjk9ill54kjk8mzgq6yydnx9015lidg-python3-3.13.9/bin/python3"
> /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv_identify.sh: interpreter directive changed from "#!/bin/sh" to "/nix/store/19zw2r9dl44wk3j5ncwsk743zr9fc584-bash-interactive-5.3p3/bin/sh"
> stripping (with command strip and flags -S) in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/Applications
> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev...
> patching script interpreter paths in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev
> stripping (with command strip and flags -S) in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib
> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc...
> patching script interpreter paths in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc
> checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man...
> gzipping man pages under /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man/share/man/
> patching script interpreter paths in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man
> Running phase: installCheckPhase
> Executing versionCheckPhase
> Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --help
>
> Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --version
>
For full logs, run:
nix log /nix/store/y75gpq7cpspdlj0pyz7vz2dza8p8vrfb-mpv-0.40.0.drv
</code></pre></div></div>
<p>It was especially unusual because the build phase itself seemed to succeed, only the version check (which had been recently added <a href="https://github.com/NixOS/nixpkgs/commit/fc94594b8988535c2a05cba03be54602adf0eb85">by this commit</a>) appeared to be failing:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>> Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --version
</code></pre></div></div>
<p>(NB: if one has already successfully built a package the <code class="language-plaintext highlighter-rouge">checkPhase</code> doesn’t re-run, so it may be necesarry to add the <code class="language-plaintext highlighter-rouge">--rebuild</code> flag to reproduce this error.)</p>
<p>The <code class="language-plaintext highlighter-rouge">versionCheckHook</code> essentially just runs your binary with <code class="language-plaintext highlighter-rouge">--help</code> and then <code class="language-plaintext highlighter-rouge">--version</code> and looks for the specified package version in the stdout or stderr of either of those.
Oddly, using that exact path and command seemed to work fine outside of the build environment, and indeed shows <code class="language-plaintext highlighter-rouge">0.40.0</code> in the output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --version
mpv v0.40.0 Copyright © 2000-2025 mpv/MPlayer/mplayer2 projects
built on Jan 1 1980 00:00:00
libplacebo version: v7.351.0
FFmpeg version: 8.0
FFmpeg library versions:
libavcodec 62.11.100
libavdevice 62.1.100
libavfilter 11.4.100
libavformat 62.3.100
libavutil 60.8.100
libswresample 6.1.100
libswscale 9.1.100
</code></pre></div></div>
<p>Digging a litle deeper, I added <code class="language-plaintext highlighter-rouge">set -x</code> to the <code class="language-plaintext highlighter-rouge">preVersionCheck</code> step in the package to get some debugging output.
It seemed to be getting no output at all:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+++++ env --chdir=/ --argv0=mpv --ignore-environment /nix/store/0d1f4drwi4ikcmgjwn9asral2a4cbf3m-mpv-0.40.0/bin/mpv --version
+++++ true
++++ versionOutput=
</code></pre></div></div>
<p>Changing the <code class="language-plaintext highlighter-rouge">preVersionCheck</code> to this:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">preVersionCheck</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2"> set -x</span><span class="err">
</span><span class="s2"> $out/bin/mpv --version</span><span class="err">
</span><span class="s2">''</span><span class="p">;</span>
</code></pre></div></div>
<p>revealed mpv to be crashing completely:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mpv> +++++ /nix/store/grzyi5fn7wv5d5v0hc8fbhh3r5zrmzjm-mpv-0.40.0/bin/mpv --version
mpv> /nix/store/dnjd7b7v5vyd8g152ziivp2jaz56bb5l-stdenv-darwin/setup: line 288: 96459 Abort trap: 6
</code></pre></div></div>
<p>I initially suspected this might be a <code class="language-plaintext highlighter-rouge">codesign</code>ing issue, since these often cause headaches on macos machines.
However, I eventually tried building with <code class="language-plaintext highlighter-rouge">--option sandbox relaxed</code> and then <code class="language-plaintext highlighter-rouge">--option sandbox false</code> and found that disabling the sandbox completely let the version check pass, confirming this to be fundamentally a sandbox issue.</p>
<p>I have some basic familiarity with the macos sandbox thanks to <a href="https://github.com/NixOS/nix/issues/4119">this issue</a>.
In the context of nix builds, the sandbox allows one to disallow access to anything outside the nix build environment (including network access, filesystem access, etc.).
This helps ensure that nix builds aren’t “polluted” by a user’s specific environment and helps ensure that builds are reproducible in other contexts.</p>
<p>Additionally, I remember having learned that macos <em>used to</em> have an option to <code class="language-plaintext highlighter-rouge">trace</code> the sandbox execution and help users figure out what was failing, but that this functionality <a href="https://stackoverflow.com/questions/56703697/how-to-sandbox-third-party-applications-when-sandbox-exec-is-deprecated-now">no longer exists</a>.
I also remembered that <code class="language-plaintext highlighter-rouge">nix</code> spits out its sandbox configuration if one builds with debug mode; using the <code class="language-plaintext highlighter-rouge">nix</code> command that means <code class="language-plaintext highlighter-rouge">--debug</code>.
Using <code class="language-plaintext highlighter-rouge">nix-build</code> I had thought one could see the sandbox profile with <code class="language-plaintext highlighter-rouge">-vvvv</code> (4 <code class="language-plaintext highlighter-rouge">v</code>s) or maybe <code class="language-plaintext highlighter-rouge">NIX_DEBUG=4</code>, but I have to admit it’s not working for me right now (please comment if you know how to get <code class="language-plaintext highlighter-rouge">nix-build</code> to spit out a sandbox profile).</p>
<p>Using the <code class="language-plaintext highlighter-rouge">--debug</code> flag to see this output (and <code class="language-plaintext highlighter-rouge">--rebuild</code> in my case, since I had previously built successfully):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build <span class="nt">--rebuild</span> <span class="nt">--debug</span> .#mpv-unwrapped 2>&1 | <span class="nb">tee </span>mpv-debug.log
<span class="c">...
</span><span class="go">sandbox setup: Generated sandbox profile:
sandbox setup: (version 1)
sandbox setup: (deny default (with no-log))
sandbox setup:
sandbox setup:
sandbox setup: (define TMPDIR (param "_GLOBAL_TMP_DIR"))
sandbox setup:
sandbox setup: (deny default)
sandbox setup:
</span><span class="gp">sandbox setup: ;</span><span class="w"> </span>Disallow creating setuid/setgid binaries, since that
<span class="gp">sandbox setup: ;</span><span class="w"> </span>would allow breaking build user isolation.
<span class="go">sandbox setup: (deny file-write-setugid)
</span><span class="c">...
</span></code></pre></div></div>
<p>As we can see, each line seems to be prefixed with <code class="language-plaintext highlighter-rouge">sandbox setup: </code>, and <code class="language-plaintext highlighter-rouge">Generated sandbox profile:</code> lets us know where the interesting stuff starts.
A little <code class="language-plaintext highlighter-rouge">awk</code> should help us strip this out (I “cached” the output to <code class="language-plaintext highlighter-rouge">mpv-debug.log</code> so I could tinker without having to wait for a rebuild each time):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">awk</span> < mpv-debug.log <span class="se">\</span>
<span class="go"> -v header='Generated sandbox profile:' \
-v leader='sandbox setup: ' \
'
</span><span class="gp"> flag && $</span>0 ~ <span class="s2">"^"</span> leader <span class="o">{</span> sub<span class="o">(</span>leader, <span class="s2">""</span><span class="o">)</span><span class="p">;</span> print <span class="o">}</span>
<span class="gp"> $</span>0 ~ header <span class="o">{</span> <span class="nv">flag</span><span class="o">=</span>1 <span class="o">}</span>
<span class="go"> ' |
tee mpv.sb
</span><span class="gp">$</span><span class="w"> </span><span class="nb">head </span>mpv.sb
<span class="go">(version 1)
(deny default (with no-log))
(define TMPDIR (param "_GLOBAL_TMP_DIR"))
(deny default)
</span><span class="gp">;</span><span class="w"> </span>Disallow creating setuid/setgid binaries, since that
<span class="gp">;</span><span class="w"> </span>would allow breaking build user isolation.
<span class="go">(deny file-write-setugid)
</span><span class="gp">;</span><span class="w"> </span>Allow forking.
<span class="go">(allow process-fork)
</span><span class="gp">;</span><span class="w"> </span>Allow reading system information like <span class="c">#CPUs, etc.</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">tail </span>mpv.sb
<span class="go"> (literal "/nix/store")
(literal "/nix/var")
(literal "/nix/var/nix")
(literal "/nix/var/nix/builds")
(literal "/private")
(literal "/private/var")
(literal "/usr")
(literal "/usr/lib")
(literal "/usr/lib/system")
)
</span></code></pre></div></div>
<p>Now that we have a sandbox profile, we can run a command in the context of this profile using <code class="language-plaintext highlighter-rouge">sandbox-exec -f mpv.sb</code>:
Unfortunately we see that the command fails, with a fairly unhelpful message:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>sandbox-exec <span class="nt">-f</span> mpv.sb /nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/bin/mpv <span class="nt">--version</span>
<span class="gp">sandbox-exec: invalid data type of path filter;</span><span class="w"> </span>expected pattern, got boolean
</code></pre></div></div>
<p>I eventually I sorted out that there are some required parameters that must be passed in, including <code class="language-plaintext highlighter-rouge">_GLOBAL_TMP_DIR</code>, <code class="language-plaintext highlighter-rouge">_NIX_BUILD_TOP</code>, and <code class="language-plaintext highlighter-rouge">_ALLOW_LOCAL_NETWORKING</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rg '\bparam\b' mpv.sb
3:(define TMPDIR (param "_GLOBAL_TMP_DIR"))
29: (subpath (param "_NIX_BUILD_TOP")))
38:(if (param "_ALLOW_LOCAL_NETWORKING")
</code></pre></div></div>
<p>These can be specified via <code class="language-plaintext highlighter-rouge">-D param=val</code>, and I think <code class="language-plaintext highlighter-rouge">_ALLOW_LOCAL_NETWORKING</code> can be left empty (which should evaluate to <code class="language-plaintext highlighter-rouge">#f</code> / false).
Wrapping this up in a script for convenience:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># sandbox.sh</span>
<span class="nb">set</span> <span class="nt">-Eeuf</span> <span class="nt">-o</span> pipefail
<span class="nb">set</span> <span class="nt">-x</span>
main<span class="o">()</span> <span class="o">{</span>
sandbox-exec <span class="se">\</span>
<span class="nt">-D</span> <span class="nv">_GLOBAL_TMP_DIR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">TMPDIR</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-D</span> <span class="nv">_NIX_BUILD_TOP</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">TMPDIR</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-f</span> mpv.sb <span class="se">\</span>
/nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/Applications/mpv.app/Contents/MacOS/mpv <span class="nt">--version</span>
<span class="o">}</span>
main <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>
<p>Running this script gives us the same <code class="language-plaintext highlighter-rouge">Abort trap: 6</code> we saw earlier:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>./sandbox.sh
<span class="go">+ main
++ mktemp -d
++ mktemp -d
+ sandbox-exec -D _GLOBAL_TMP_DIR=/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.8LoLZufaEe -D _NIX_BUILD_TOP=/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.YUT5GARrUD -f mpv.sb /nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/Applications/mpv.app/Contents/MacOS/mpv --version
</span><span class="gp">./sandbox.sh: line 6: 95518 Abort trap: 6 sandbox-exec -D _GLOBAL_TMP_DIR="$</span><span class="o">(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="o">)</span><span class="s2">" -D _NIX_BUILD_TOP="</span><span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span><span class="s2">" -f mpv.sb /nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/Applications/mpv.app/Contents/MacOS/mpv --version
</span></code></pre></div></div>
<p>Progress!</p>
<p>(NB: In some cases I imagine it may be helpful to run <code class="language-plaintext highlighter-rouge">nix build</code> with the <code class="language-plaintext highlighter-rouge">--keep-failed</code> flag, search the output for the build directory, then specify this as <code class="language-plaintext highlighter-rouge">_NIX_BUILD_TOP</code>.)</p>
<p>Now that we have an example sandbox profile and can reproduce the error, it’s time to sort out what is missing in the sandbox to allow successful execution of <code class="language-plaintext highlighter-rouge">mpv --version</code>.
One can always use <code class="language-plaintext highlighter-rouge">Console.app</code> to get an idea, but I find that the command line version fits my workflow better, which are <code class="language-plaintext highlighter-rouge">log stream</code> (or <code class="language-plaintext highlighter-rouge">log show --last 1h</code> for reviewing historical logs).</p>
<p>Unfortunately, the log is <em>very</em> busy.
Naively piping its output to <code class="language-plaintext highlighter-rouge">rg</code> helped me find a promising line to help me set up a filter:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>log stream <span class="nt">--info</span> <span class="nt">--debug</span> | rg sandbox
<span class="c">...
</span><span class="go">2025-12-21 09:58:24.425558-0700 0x1093f84 Debug 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] begin container id: 17384080, type: thread container
</span><span class="c">...
</span></code></pre></div></div>
<p>The example in <code class="language-plaintext highlighter-rouge">log help predicates</code> was really helpful in setting up a filter to match this output:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>log <span class="nb">help </span>predicates
<span class="c">...
</span><span class="go"> log fields corresponding to a log line:
2024-05-30 08:40:15.980893-0400 0x26166c Default 0x0 90092 0 log: (libxpc.dylib) [com.apple.xpc:connection]
[0x6000007081e0] activating connection: mach=true listener=false peer=false name=com.apple.logd.admin
where
'2024-05-30 08:40:15.980893-0400' == date
'0x26166c' == threadIdentifier
'Default' == logType
'90092' == processIdentifier
'log' == process
'libxpc.dylib' == sender
'com.apple.xpc' == subsystem
'connection' == category
'activating connection[...]' == composedMessage
</span></code></pre></div></div>
<p>Using that as an example, I found this to work fairly well:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>log stream <span class="nt">--info</span> <span class="nt">--debug</span> <span class="nt">--predicate</span> <span class="s1">'(process == "sandboxd") && (subsystem == "com.apple.sandbox.reporting") && (category == "violation")'</span>
</code></pre></div></div>
<p>Running this in one window and then executing my <code class="language-plaintext highlighter-rouge">sandbox.sh</code> script in another, I get a lot of output from the violations:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to symbolicate: NULL symbolicator
2025-12-21 10:01:59.712321-0700 0x1095684 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96060) deny(1) mach-lookup com.apple.CoreServices.coreservicesd
Process: mpv [96060]
Path: /nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/Applications/mpv.app/Contents/MacOS/mpv
Load Address: 0
Identifier: io.mpv
Version: ??? (0.40.0)
Code Type: unknown (Native)
Parent Process: bash [96057]
Responsible: /Applications/Nix Apps/Alacritty.app/Contents/MacOS/alacritty
User ID: 501
Date/Time: 2025-12-21 10:01:59.712 MST
OS Version: macOS 26.2 (25C56)
Release Type: User
Report Version: 8
MetaData: {"operation":"mach-lookup","responsible-process-sdk":918528,"sandbox_checker":"launchd","signing-id":"mpv","mach_namespace":1,"build":"macOS 26.2 (25C56)","target":"com.apple.CoreServices.coreservicesd","profile-in-collection":false,"hardware":"J614c","uid":501,"platform-binary":false,"translated":false,"primary-filter":"global-name","policy-description":"Sandbox","parent-process-name":"bash","platform_binary":"no","primary-filter-value":"com.apple.CoreServices.coreservicesd","responsible-process-path":"\/Applications\/Nix Apps\/Alacritty.app\/Contents\/MacOS\/alacritty","flags":5,"action":"deny","checker":"launchd","process_path":["nix","store","iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0","Applications","mpv.app","Contents","MacOS","mpv"],"binary-in-trust-cache":false,"apple-internal":false,"process-path":"\/nix\/store\/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0\/Applications\/mpv.app\/Contents\/MacOS\/mpv","normalized_target":["com.apple.CoreServices.coreservicesd"],"profile-flags":0,"global-name":"com.apple.CoreServices.coreservicesd","responsible-process-signing-id":"alacritty","platform-policy":false,"pid":96060,"checker-pid":1,"summary":"deny(1) mach-lookup com.apple.CoreServices.coreservicesd","errno":1,"parent-process-pid":96057,"process":"mpv","release-type":"User"}
Failed to symbolicate: NULL symbolicator
2025-12-21 10:01:59.712538-0700 0x1095684 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96060) deny(1) mach-lookup com.apple.DiskArbitration.diskarbitrationd
Process: mpv [96060]
Path: /nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/Applications/mpv.app/Contents/MacOS/mpv
Load Address: 0
Identifier: io.mpv
Version: ??? (0.40.0)
Code Type: unknown (Native)
Parent Process: bash [96057]
Responsible: /Applications/Nix Apps/Alacritty.app/Contents/MacOS/alacritty
User ID: 501
Date/Time: 2025-12-21 10:01:59.712 MST
OS Version: macOS 26.2 (25C56)
Release Type: User
Report Version: 8
MetaData: {"operation":"mach-lookup","responsible-process-sdk":918528,"sandbox_checker":"launchd","signing-id":"mpv","mach_namespace":1,"build":"macOS 26.2 (25C56)","target":"com.apple.DiskArbitration.diskarbitrationd","profile-in-collection":false,"hardware":"J614c","uid":501,"platform-binary":false,"translated":false,"primary-filter":"global-name","policy-description":"Sandbox","parent-process-name":"bash","platform_binary":"no","primary-filter-value":"com.apple.DiskArbitration.diskarbitrationd","responsible-process-path":"\/Applications\/Nix Apps\/Alacritty.app\/Contents\/MacOS\/alacritty","flags":5,"action":"deny","checker":"launchd","process_path":["nix","store","iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0","Applications","mpv.app","Contents","MacOS","mpv"],"binary-in-trust-cache":false,"apple-internal":false,"process-path":"\/nix\/store\/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0\/Applications\/mpv.app\/Contents\/MacOS\/mpv","normalized_target":["com.apple.DiskArbitration.diskarbitrationd"],"profile-flags":0,"global-name":"com.apple.DiskArbitration.diskarbitrationd","responsible-process-signing-id":"alacritty","platform-policy":false,"pid":96060,"checker-pid":1,"summary":"deny(1) mach-lookup com.apple.DiskArbitration.diskarbitrationd","errno":1,"parent-process-pid":96057,"process":"mpv","release-type":"User"}
Failed to symbolicate: NULL symbolicator
</code></pre></div></div>
<p>NB: I found several times that <code class="language-plaintext highlighter-rouge">log stream</code> would have <strong>no output</strong> the second or third time I ran <code class="language-plaintext highlighter-rouge">sandbox.sh</code>; I suspect there is some filtering for “duplicate lines” happening.
Waiting a minute or two and running again (with no changes) worked for me, YMMV.</p>
<p>Now we can use something like <code class="language-plaintext highlighter-rouge">rg</code> (or <code class="language-plaintext highlighter-rouge">grep</code> would be fine) to further filter results; lines containing <code class="language-plaintext highlighter-rouge">deny(1)</code> look particularly promising:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>log stream <span class="nt">--info</span> <span class="nt">--debug</span> <span class="nt">--predicate</span> <span class="s1">'(process == "sandboxd") && (subsystem == "com.apple.sandbox.reporting") && (category == "violation")'</span> |
<span class="go"> rg 'sandboxd:.*violation.*deny\(1\)'
2025-12-21 10:13:59.160033-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.logd
2025-12-21 10:13:59.187779-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.system.notification_center
2025-12-21 10:13:59.188426-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.pasteboard.1
2025-12-21 10:13:59.188789-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.distributed_notifications@Uv3
2025-12-21 10:13:59.189289-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.tccd.system
2025-12-21 10:13:59.189640-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.windowserver.active
2025-12-21 10:13:59.189994-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.CoreServices.coreservicesd
2025-12-21 10:13:59.190377-0700 0x1099e49 Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96654) deny(1) mach-lookup com.apple.DiskArbitration.diskarbitrationd
</span></code></pre></div></div>
<p>Now we can see pretty specifically <em>what</em> is being denied, which are a number of <code class="language-plaintext highlighter-rouge">mach-lookup</code>s (not an out-of-sandbox <code class="language-plaintext highlighter-rouge">file-read</code> like I had suspected).
Lacking a sharper tool, at this point I searched our sandbox profile for a <code class="language-plaintext highlighter-rouge">mach-lookup</code> line, of which there was only one match:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rg mach-lookup mpv.sb
<span class="go">21:(allow mach-lookup (global-name "com.apple.system.opendirectoryd.libinfo"))
</span></code></pre></div></div>
<p>Because order generally matters in these firewall-ish matters, I thought it would be best to modify the script <em>just after</em> this line.
I matched this format and added the corresponding lines from our logged violations:</p>
<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.logd"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.system.notification_center"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.pasteboard.1"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.distributed_notifications@Uv3"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.tccd.system"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.windowserver.active"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.CoreServices.coreservicesd"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.DiskArbitration.diskarbitrationd"</span><span class="p">))</span>
</code></pre></div></div>
<p>Running <code class="language-plaintext highlighter-rouge">sandbox.sh</code> again at this point showed two new violations:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2025-12-21 10:19:07.327764-0700 0x1099c8f Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96921) deny(1) mach-lookup com.apple.coreservices.launchservicesd
2025-12-21 10:19:07.328418-0700 0x1099c8f Error 0x0 601 0 sandboxd: [com.apple.sandbox.reporting:violation] Sandbox: mpv(96921) deny(1) mach-lookup com.apple.CARenderServer
</code></pre></div></div>
<p>I added <code class="language-plaintext highlighter-rouge">allow</code>s for these as well:</p>
<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.coreservices.launchservicesd"</span><span class="p">))</span>
<span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.CARenderServer"</span><span class="p">))</span>
</code></pre></div></div>
<p>Lo and behold, <code class="language-plaintext highlighter-rouge">sandbox.sh</code> now works!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>./sandbox.sh
<span class="go">+ main
++ mktemp -d
++ mktemp -d
+ sandbox-exec -D _GLOBAL_TMP_DIR=/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.ky3oo6TxVE -D _NIX_BUILD_TOP=/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.9YOoyXB3cd -f mpv.sb /nix/store/iq7mr3dxkq09cg851dzhnlkvb34wcf4k-mpv-0.40.0/Applications/mpv.app/Contents/MacOS/mpv --version
mpv v0.40.0 Copyright © 2000-2025 mpv/MPlayer/mplayer2 projects
built on Jan 1 1980 00:00:00
libplacebo version: v7.351.0
FFmpeg version: 8.0
FFmpeg library versions:
libavcodec 62.11.100
libavdevice 62.1.100
libavfilter 11.4.100
libavformat 62.3.100
libavutil 60.8.100
libswresample 6.1.100
libswscale 9.1.100
</span></code></pre></div></div>
<p>I wasn’t sure if <em>all</em> of these allows were <em>really</em> required, so next I manually deleted each of them from the sandbox profile, eventually finding that the only one required is:</p>
<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">allow</span> <span class="nv">mach-lookup</span> <span class="p">(</span><span class="nf">global-name</span> <span class="s">"com.apple.coreservices.launchservicesd"</span><span class="p">))</span>
</code></pre></div></div>
<p>After a little rummaging around, I found that one can modify the sandbox environment for a package like so:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">sandboxProfile</span> <span class="o">=</span> <span class="nv">lib</span><span class="o">.</span><span class="nv">optionalString</span> <span class="nv">stdenv</span><span class="o">.</span><span class="nv">hostPlatform</span><span class="o">.</span><span class="nv">isDarwin</span> <span class="s2">''</span><span class="err">
</span><span class="s2"> (allow mach-lookup (global-name "com.apple.coreservices.launchservicesd"))</span><span class="err">
</span><span class="s2">''</span><span class="p">;</span>
</code></pre></div></div>
<p>This then allows a successful build if one specifies <code class="language-plaintext highlighter-rouge">--option sandbox relaxed</code>.
I wrapped this up in a <a href="https://github.com/NixOS/nixpkgs/pull/472452">PR</a> which was merged.
Phew!</p>
Sun, 21 Dec 2025 08:30:12 -0700
https://n8henrie.com/2025/12/nix-debugging-macos-darwin-sandbox-issues/
https://n8henrie.com/2025/12/nix-debugging-macos-darwin-sandbox-issues/
debugging
Mac OSX
MacOS
nix
tech
-
How to Set a Conditional Breakpoint When Debugging Rust with LLDB
<p><strong>Bottom Line:</strong> A little Python can help LLDB break on <code class="language-plaintext highlighter-rouge">String</code> contents in Rust.</p>
<p>A few months ago I was trying to debug some Rust code that was misbehaving.
The bug was in a function that was run hundreds of thousands of times, but I had made a logic error somewhere that only seemed to manifest in a very small fraction of the cases.
I had gathered a few examples that were reliably misbehaving, so I wanted to set a breakpoint in this function, but have it <strong>only break depending on the contents of a specific variable</strong> (a <code class="language-plaintext highlighter-rouge">String</code> in this case).</p>
<p>I do a fair amount of work on my Macbook, so I <em>try</em> to use LLDB as my debugger, since it runs on both MacOS and Linux.
Unfortunately for me, it seems far more popular to debug Rust with a Linux + GDB setup, and it’s relatively difficult to find LLDB-specific content.
Thankfully I have the option to switch to a Linux + GDB workstation when needed, but I prefer to sort things out with LLDB when possible.
This post describes my journey sorting out the process of establishing a conditional breakpoint for Rust code, using LLDB.</p>
<p>As a very brief introduction, I can compile this code in debug mode with an unpretentious <code class="language-plaintext highlighter-rouge">cargo build</code>:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">var</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="s">"asdf"</span><span class="p">);</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">var</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">rustup</code> provides a <code class="language-plaintext highlighter-rouge">rust-lldb</code> helper script, often found at <code class="language-plaintext highlighter-rouge">~/.cargo/bin/rust-lldb</code>, and invoking <code class="language-plaintext highlighter-rouge">lldb</code> this way sets up some pretty printers and helpers to make the Rust experience more ergonomic.
I recommend using it.
To debug this executable (in this case creatively named <code class="language-plaintext highlighter-rouge">foo</code>), I’ll run <code class="language-plaintext highlighter-rouge">rust-lldb target/debug/foo</code>.
Once LLDB is set up, I can print the value of <code class="language-plaintext highlighter-rouge">var</code> by:</p>
<ol>
<li>setting a breakpoint at the function named <code class="language-plaintext highlighter-rouge">foo::main</code></li>
<li><code class="language-plaintext highlighter-rouge">run</code> to run the executable until the breakpoint is hit, at which point <code class="language-plaintext highlighter-rouge">var</code> will not yet be defined</li>
<li><code class="language-plaintext highlighter-rouge">n</code> to step to the “next” line (after which <code class="language-plaintext highlighter-rouge">var</code> is defined)</li>
<li><code class="language-plaintext highlighter-rouge">p</code> to print <code class="language-plaintext highlighter-rouge">var</code></li>
</ol>
<p>Here’s how this looks:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rust-lldb target/debug/foo
...skipping some lldb output...
(lldb) break set --name foo::main
Breakpoint 1: where = foo`foo::main::hf5da6d1ecd5155fc + 20 at main.rs:2:15, address = 0x0000000100000ef8
(lldb) run
Process 63389 launched: '/path/to/foo/target/debug/foo' (arm64)
Process 63389 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000ef8 foo`foo::main::hf5da6d1ecd5155fc at main.rs:2:15
1 fn main() {
-> 2 let var = String::from("asdf");
^
3 println!("{:?}", var);
4 }
(lldb) n
Process 63389 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100000f10 foo`foo::main::hf5da6d1ecd5155fc at main.rs:3:5
1 fn main() {
2 let var = String::from("asdf");
-> 3 println!("{:?}", var);
^
4 }
(lldb) p var
(alloc::string::String) "asdf" {
[0] = 'a'
[1] = 's'
[2] = 'd'
[3] = 'f'
}
</code></pre></div></div>
<p>LLDB supports conditional breakpoints by adding <code class="language-plaintext highlighter-rouge">-c 'my condition'</code> to the <code class="language-plaintext highlighter-rouge">break set</code> (in addition to breaking on a specific line in a file, as opposed to in a function, via <code class="language-plaintext highlighter-rouge">-f path/to/file.rs -l line_number</code>).
Unfortunately, attempting to break on a condition like <code class="language-plaintext highlighter-rouge">var.contains("asdf")</code> fails:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(lldb) b -f foo/src/main.rs -l 3 -c 'var.contains("asdf")'
(lldb) run
...
error: stopped due to an error evaluating condition of breakpoint 4.1: "var.contains("asdf")"
Couldn't parse conditional expression:
error: <user expression 3>:1:5: no member named 'contains' in 'alloc::string::String'
1 | var.contains("asdf")
| ~~~ ^
</code></pre></div></div>
<p>With some help from Claude I eventually found a way to cast into a type that worked, but it’s not very ergonomic:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(lldb) b -f foo/src/main.rs -l 3 -c '(char*)strstr((char*)var.vec.buf.inner.ptr.pointer.pointer, "asdf")'
(lldb) run
Process 7605 launched: '/path/to/foo/target/debug/foo' (arm64)
Process 7605 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = breakpoint 19.1
frame #0: 0x0000000100000d8c foo`foo::main::h5c931762e824ef5f at main.rs:3:5
1 fn main() {
2 let var = String::from("asdf");
-> 3 println!("{var}");
^
4 }
</code></pre></div></div>
<p>By far the easiest approach that I came up with was just modifying the source code to do the searching, and then adding a fitting breakpoint.
For example, if one is able to modify the source and is searching for the string <code class="language-plaintext highlighter-rouge">NEEDLE</code> in variable <code class="language-plaintext highlighter-rouge">haystack</code>, just add:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">haystack</span><span class="nf">.contains</span><span class="p">(</span><span class="s">"NEEDLE"</span><span class="p">)</span> <span class="p">{</span>
<span class="nd">dbg!</span><span class="p">(</span><span class="o">&</span><span class="n">haystack</span><span class="p">);</span> <span class="c1">// <- breakpoint here</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Done.</p>
<p>However, my <a href="https://xkcd.com/356/">interest was piqued</a>, so I kept digging for other ways to accomplish this with LLDB.
I asked for suggestions <a href="https://users.rust-lang.org/t/rust-lldb-conditional-breakpoint-on-string-value">on the Rust user forum</a>.
Nobody chimed in to point out an obvious easy way, but one user was kind enough to suggest that I look into creating custom functions.</p>
<p>I found the official documentation to be interesting and fairly approachable.
These pages were particularly helpful:</p>
<ul>
<li><a href="https://lldb.llvm.org/use/tutorials/breakpoint-triggered-scripts.html">https://lldb.llvm.org/use/tutorials/breakpoint-triggered-scripts.html</a></li>
<li><a href="https://lldb.llvm.org/use/tutorials/writing-custom-commands.html">https://lldb.llvm.org/use/tutorials/writing-custom-commands.html</a></li>
<li><a href="https://lldb.llvm.org/use/map.html">https://lldb.llvm.org/use/map.html</a></li>
</ul>
<p>For context, I’m using:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>lldb <span class="nt">--version</span>
<span class="go">lldb version 21.1.2
</span></code></pre></div></div>
<p>I decided to use this Rust code as my test case, ensuring that I can search within a nested structure:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Debug)]</span>
<span class="k">struct</span> <span class="n">Parent</span> <span class="p">{</span>
<span class="n">child</span><span class="p">:</span> <span class="n">Child</span><span class="p">,</span>
<span class="p">}</span>
<span class="nd">#[derive(Debug)]</span>
<span class="k">struct</span> <span class="n">Child</span> <span class="p">{</span>
<span class="n">data</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">Parent</span> <span class="p">{</span>
<span class="n">child</span><span class="p">:</span> <span class="n">Child</span> <span class="p">{</span>
<span class="n">data</span><span class="p">:</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="s">"hello"</span><span class="p">),</span>
<span class="p">},</span>
<span class="p">};</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">parent</span><span class="py">.child.data</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>LLDB supports a couple of scripting language options for conditional breakpoints.
I know Python best, so that was an easy choice in my case.
The conditional breakpoint script I eventually got to work is:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">break_if_contains</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">bp_loc</span><span class="p">,</span> <span class="n">extra_args</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="s">"""Break if a Rust string contains a substring
NB: Returning `False` means "do *not* break". Anything else (including
`return None`, empty return, or no return value specified) means
*do* break
Usage:
(lldb) command script import /path/to/filename.py
(lldb) # set a breakpoint however you like
(lldb) break set -f foo/src/main.rs -l 13
(lldb) break command add --python-function filename.break_if_contains </span><span class="se">\
</span><span class="s"> -k haystack -v UNQUOTED_VAR_NAME </span><span class="se">\
</span><span class="s"> -k needle -v UNQUOTED_STRING </span><span class="se">\
</span><span class="s"> BREAKPOINT_NUMBER
(lldb) run
Example:
(lldb) break command add -F strcompare.break_if_contains </span><span class="se">\
</span><span class="s"> -k haystack -v self.description </span><span class="se">\
</span><span class="s"> -k needle -v Hello </span><span class="se">\
</span><span class="s"> 1
"""</span>
<span class="n">needle</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">extra_args</span><span class="p">.</span><span class="n">GetValueForKey</span><span class="p">(</span><span class="s">"needle"</span><span class="p">))</span>
<span class="n">haystack</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">extra_args</span><span class="p">.</span><span class="n">GetValueForKey</span><span class="p">(</span><span class="s">"haystack"</span><span class="p">))</span>
<span class="n">parts</span> <span class="o">=</span> <span class="n">haystack</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"."</span><span class="p">)</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">frame</span><span class="p">.</span><span class="n">FindVariable</span><span class="p">(</span><span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">current</span><span class="p">.</span><span class="n">IsValid</span><span class="p">():</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">for</span> <span class="n">part</span> <span class="ow">in</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">:]:</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">current</span><span class="p">.</span><span class="n">GetChildMemberWithName</span><span class="p">(</span><span class="n">part</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">current</span><span class="p">.</span><span class="n">IsValid</span><span class="p">():</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="n">summary</span> <span class="o">=</span> <span class="n">current</span><span class="p">.</span><span class="n">summary</span><span class="p">.</span><span class="n">strip</span><span class="p">(</span><span class="s">'"'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">needle</span> <span class="ow">in</span> <span class="n">summary</span>
</code></pre></div></div>
<p>Hopefully I’ve made the setup and usage fairly clear in the docstring.
Passing variables to the function is a little odd; the most obvious way I could find is by passing key-value pairs, specified by <code class="language-plaintext highlighter-rouge">-k</code> and <code class="language-plaintext highlighter-rouge">-v</code> respectively.
The values are then accessible via <code class="language-plaintext highlighter-rouge">extra_args.GetValueForKey()</code> as shown.</p>
<p>Writing and debugging this function was made easier by a few strategies:</p>
<ul>
<li>one can use <code class="language-plaintext highlighter-rouge">breakpoint()</code> in the Python script to drop into PDB (from LLDB) at runtime, just like a normal Python script</li>
<li>lldb (or <code class="language-plaintext highlighter-rouge">rust-lldb</code>) accepts a <code class="language-plaintext highlighter-rouge">-o</code> / <code class="language-plaintext highlighter-rouge">--one-line</code> flag that will automatically run an LLDB command at launch time
<ul>
<li><code class="language-plaintext highlighter-rouge">-o 'another command'</code> can be repeated to run multiple commands in series</li>
</ul>
</li>
<li>the same Python file that contains the <code class="language-plaintext highlighter-rouge">break_if_contains</code> function can also define a <code class="language-plaintext highlighter-rouge">__lldb_init_module(debugger, internal_dict)</code> function that can then automatically run LLDB code at <code class="language-plaintext highlighter-rouge">command script import</code> time via <code class="language-plaintext highlighter-rouge">debugger.HandleCommand("lldb command here")</code>
<ul>
<li>for my purposes, using <code class="language-plaintext highlighter-rouge">-o 'some lldb command'</code> from bash vs <code class="language-plaintext highlighter-rouge">debugger.HandleCommand("some lldb command")</code> within the body of <code class="language-plaintext highlighter-rouge">__lldb_init_module()</code> seems to be different routes to accomplish the same task</li>
</ul>
</li>
</ul>
<p>For example, I used a bash script that I would run after each iteraction of the Python function to launch LLDB and import the script:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
cargo build
rust-lldb target/debug/foo <span class="se">\</span>
<span class="nt">-o</span> <span class="s1">'command script import /path/to/strcompare.py'</span>
</code></pre></div></div>
<p>In the imported Python script, I included this function, which automatically sets a breakpoint, modifies the breakpoint to conditionally break based on the result of the Python function, then tells LLDB to proceed with running the binary:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">__lldb_init_module</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="s">"break set -f foo/src/main.rs -l 13"</span><span class="p">)</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span>
<span class="s">" "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span>
<span class="p">[</span>
<span class="s">"breakpoint command add --python-function strcompare.break_if_contains"</span><span class="p">,</span>
<span class="s">"-k haystack -v parent.child.data"</span><span class="p">,</span>
<span class="c1"># "-k haystack -v parent",
</span> <span class="s">"-k needle -v hello"</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="s">"run"</span><span class="p">)</span>
</code></pre></div></div>
<p>As you can see, this can simplify commenting out parts of the script (in this case I was ensuring that it works for simple variables like <code class="language-plaintext highlighter-rouge">String::from("foo")</code> as well as layered structs).
You may also notice that specifying the number of the breakpoint to modify is optional; if omitted, it defaults to the most recently added breakpoint.
I could have done this in the bash script, but the complexities of line continuation in bash made Python a little more comfortable for this task.</p>
<p>Finally, because it feels a little clumsy to have to <em>first</em> set a breakpoint and then <em>separately</em> modify it with the conditional script, here is an example of how to write an LLDB “custom command” that can accept all required arguments, set up the breakpoint, modify it, and <code class="language-plaintext highlighter-rouge">run</code>, all in one fell swoop:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">run_break</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">command</span><span class="p">,</span> <span class="n">exe_ctx</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="s">"""Helper to run `break_on_contains`
All arguments are required:
`-f`: file for breakpoint
`-l`: line for breakpoint
`-n`: needle
`-h`: haystack
example:
(lldb) run_break -f foo/src/main.rs -l 17 -n hello -h parent.child.data
"""</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">command</span><span class="p">.</span><span class="n">split</span><span class="p">()</span>
<span class="n">args</span><span class="p">.</span><span class="n">reverse</span><span class="p">()</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">arg</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="k">except</span> <span class="nb">IndexError</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">match</span> <span class="n">arg</span><span class="p">:</span>
<span class="n">case</span> <span class="s">"-f"</span><span class="p">:</span>
<span class="nb">file</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">case</span> <span class="s">"-l"</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">case</span> <span class="s">"-n"</span><span class="p">:</span>
<span class="n">needle</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">case</span> <span class="s">"-h"</span><span class="p">:</span>
<span class="n">haystack</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="sa">f</span><span class="s">"break set -f </span><span class="si">{</span><span class="nb">file</span><span class="si">}</span><span class="s"> -l </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span>
<span class="s">" "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span>
<span class="p">[</span>
<span class="s">"breakpoint command add --python-function strcompare.break_if_contains"</span><span class="p">,</span>
<span class="sa">f</span><span class="s">"-k haystack -v </span><span class="si">{</span><span class="n">haystack</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
<span class="sa">f</span><span class="s">"-k needle -v </span><span class="si">{</span><span class="n">needle</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="s">"run"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__lldb_init_module</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span>
<span class="s">"command script add -f strcompare.run_break run_break"</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Clearly this could be cleaned up with <code class="language-plaintext highlighter-rouge">argparse</code> and maybe <code class="language-plaintext highlighter-rouge">shlex</code>.
However, it accomplishes my goal of a conditional breakpoint based on <code class="language-plaintext highlighter-rouge">String</code> contents in Rust!
At this point, the process is as simple as this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rust-lldb target/debug/foo
(lldb) command script import /path/to/strcompare.py
(lldb) run_break -f foo/src/main.rs -l 17 -n hello -h parent.child.data
Breakpoint 1: 2 locations.
Process 64567 launched: '/path/to/foo/target/debug/foo' (arm64)
Process 64567 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000f74 foo`foo::main::hf5da6d1ecd5155fc at main.rs:18:5
15 },
16 };
17
-> 18 println!("{:?}", parent.child.data);
^
19 }
</code></pre></div></div>
<p>Finally, as one last convenience, if one wanted all of this to be automatically loaded at LLDB startup, one can create a file at <code class="language-plaintext highlighter-rouge">~/.lldbinit</code> with the following contents:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>command script import /path/to/strcompare.py
</code></pre></div></div>
<p>After that, one should be able to invoke <code class="language-plaintext highlighter-rouge">rust-lldb</code> and subsequently the <code class="language-plaintext highlighter-rouge">run_break</code> helper function is ready to rock!</p>
<p>Here is my final (proof of concept) code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"""strcompare.py
Adds Rust string comparison breakpoint to lldb
Usage:
```
$ rust-lldb target/debug/my_binary
(lldb) command script import /path/to/strcompare.py
(lldb) break set -f project_name/src/main.rs -l 42
(lldb) break command add -F strcompare.break_if_contains -k haystack -v self.description -k needle -v hello 1
(lldb) run
```
Alternatively, using the `run_break` helper:
```
$ rust-lldb target/debug/my_binary
(lldb) command script import /path/to/strcompare.py
(lldb) run_break -f project_name/src/main.rs -l 42 -n hello -h self.description
```
Further reading:
<https://n8henrie.com/2025/12/how-to-set-a-conditional-breakpoint-when-debugging-rust-with-lldb/>
<https://lldb.llvm.org/use/tutorials/writing-custom-commands.html>
<https://lldb.llvm.org/use/tutorials/breakpoint-triggered-scripts.html>
"""</span>
<span class="k">def</span> <span class="nf">break_if_contains</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">bp_loc</span><span class="p">,</span> <span class="n">extra_args</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="s">"""Break if a Rust string contains a substring
Usage:
(lldb) break command add -F filename.break_if_contains </span><span class="se">\
</span><span class="s"> -k haystack -v UNQUOTED_VAR_NAME </span><span class="se">\
</span><span class="s"> -k needle -v UNQUOTED_STRING </span><span class="se">\
</span><span class="s"> BREAKPOINT_NUMBER
Example:
(lldb) break command add -F strcompare.break_if_contains </span><span class="se">\
</span><span class="s"> -k haystack -v self.description </span><span class="se">\
</span><span class="s"> -k needle -v Hello </span><span class="se">\
</span><span class="s"> 1
"""</span>
<span class="n">needle</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">extra_args</span><span class="p">.</span><span class="n">GetValueForKey</span><span class="p">(</span><span class="s">"needle"</span><span class="p">))</span>
<span class="n">haystack</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">extra_args</span><span class="p">.</span><span class="n">GetValueForKey</span><span class="p">(</span><span class="s">"haystack"</span><span class="p">))</span>
<span class="n">parts</span> <span class="o">=</span> <span class="n">haystack</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"."</span><span class="p">)</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">frame</span><span class="p">.</span><span class="n">FindVariable</span><span class="p">(</span><span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">current</span><span class="p">.</span><span class="n">IsValid</span><span class="p">():</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">for</span> <span class="n">part</span> <span class="ow">in</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">:]:</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">current</span><span class="p">.</span><span class="n">GetChildMemberWithName</span><span class="p">(</span><span class="n">part</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">current</span><span class="p">.</span><span class="n">IsValid</span><span class="p">():</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="n">summary</span> <span class="o">=</span> <span class="n">current</span><span class="p">.</span><span class="n">summary</span><span class="p">.</span><span class="n">strip</span><span class="p">(</span><span class="s">'"'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">needle</span> <span class="ow">in</span> <span class="n">summary</span>
<span class="k">def</span> <span class="nf">run_break</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">command</span><span class="p">,</span> <span class="n">exe_ctx</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="s">"""Helper to run `break_on_contains`
All arguments are required:
`-f`: file for breakpoint
`-l`: line for breakpoint
`-n`: needle
`-h`: haystack
example:
(lldb) run_break -f foo/src/main.rs -l 17 -n hello -h parent.child.data
"""</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">command</span><span class="p">.</span><span class="n">split</span><span class="p">()</span>
<span class="n">args</span><span class="p">.</span><span class="n">reverse</span><span class="p">()</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">arg</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="k">except</span> <span class="nb">IndexError</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">match</span> <span class="n">arg</span><span class="p">:</span>
<span class="n">case</span> <span class="s">"-f"</span><span class="p">:</span>
<span class="nb">file</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">case</span> <span class="s">"-l"</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">case</span> <span class="s">"-n"</span><span class="p">:</span>
<span class="n">needle</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">case</span> <span class="s">"-h"</span><span class="p">:</span>
<span class="n">haystack</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="sa">f</span><span class="s">"break set -f </span><span class="si">{</span><span class="nb">file</span><span class="si">}</span><span class="s"> -l </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span>
<span class="s">" "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span>
<span class="p">[</span>
<span class="s">"breakpoint command add --python-function strcompare.break_if_contains"</span><span class="p">,</span>
<span class="sa">f</span><span class="s">"-k haystack -v </span><span class="si">{</span><span class="n">haystack</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
<span class="sa">f</span><span class="s">"-k needle -v </span><span class="si">{</span><span class="n">needle</span><span class="si">}</span><span class="s">"</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="s">"run"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__lldb_init_module</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
<span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span>
<span class="s">"command script add -f strcompare.run_break run_break"</span>
<span class="p">)</span>
</code></pre></div></div>
Thu, 04 Dec 2025 21:01:58 -0700
https://n8henrie.com/2025/12/how-to-set-a-conditional-breakpoint-when-debugging-rust-with-lldb/
https://n8henrie.com/2025/12/how-to-set-a-conditional-breakpoint-when-debugging-rust-with-lldb/
rust
debugging
tech
-
Nix: Why Does My System Depend on $PKG?
<p><strong>Bottom Line:</strong> <code class="language-plaintext highlighter-rouge">nix why-depends --derivation /run/current-system</code> and <code class="language-plaintext highlighter-rouge">nix derivation show --recursive /run/current-system</code> are handy.
<!--more--></p>
<p>NixOS and nix-darwin users – especially those following unstable – will be familiar with the situation of <code class="language-plaintext highlighter-rouge">nixos-rebuild</code> or <code class="language-plaintext highlighter-rouge">darwin-rebuild</code> failure due to breakage in a dependency.
(Said user will hopefully also be <em>grateful</em> that this failure prevents them from switching into a system with a broken dependency!)
Once this situation is encountered, there are a few reasonable approaches, including but not limited to:</p>
<ul>
<li>Do nothing.
Patiently wait with your existing system, grateful that it has been prevented from switching into a broken state.
Hope that the issue is eventually fixed, or fix it yourself.
<ul>
<li>In this case, I strongly recommend searching the <a href="https://github.com/NixOS/nixpkgs/issues/">nixpkgs issues</a> and either subscribing to the existing issue, or opening a new one if you’re sure it hasn’t been reported</li>
</ul>
</li>
<li>If the package isn’t particularly critical, remove the broken package from your system closure so your system upgrade can complete.</li>
<li>Use an overlay to pin the broken package to a working version so your system upgrade can complete while keeping a working (but possibly outdated) version of the package.</li>
</ul>
<p>To some degree, all of these options rely on knowing what package is broken, which can be surprisingly hard to determine.
Sometimes it is obvious based on the error message.
Other times, the broken package may be a transitive dependency of your system’s direct dependency; while this <em>is</em> spelled out in the same error message, it can be tough to pick out sometimes.</p>
<p>As an example, my aarch64-darwin system is currently unable to build.
Here is what I see:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ darwin-rebuild build --flake .
building the system configuration...
error: builder for '/nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv' failed with exit code 1;
last 25 log lines:
> current directory: /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/gems/nokogiri-1.16.0/ext/nokogiri
> make DESTDIR\= sitearchdir\=./.gem.20251015-88848-9l6020 sitelibdir\=./.gem.20251015-88848-9l6020
> compiling gumbo.c
> In file included from gumbo.c:30:
> In file included from ./nokogiri.h:77:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby.h:38:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/ruby.h:28:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic.h:24:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic/char.h:29:
> /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/core/rstring.h:398:24: warning: default initialization of an object of type 'struct RString' with const member leaves the object uninitialized [-Wdefault-const-init-field-unsafe]
> 398 | struct RString retval;
> | ^
> /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/core/rbasic.h:88:17: note: member 'klass' declared 'const' here
> 88 | const VALUE klass;
> | ^
> gumbo.c:32:10: fatal error: 'nokogiri_gumbo.h' file not found
> 32 | #include "nokogiri_gumbo.h"
> | ^~~~~~~~~~~~~~~~~~
> 1 warning and 1 error generated.
> make: *** [Makefile:248: gumbo.o] Error 1
>
> make failed, exit code 2
>
> Gem files will remain installed in /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/gems/nokogiri-1.16.0 for inspection.
> Results logged to /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/extensions/arm64-darwin-24/3.3.0/nokogiri-1.16.0/gem_make.out
For full logs, run:
nix log /nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv
error: 1 dependencies of derivation '/nix/store/a4539sgfjbl0xqz3jx4slch2ba9d6rwg-ronn-gems.drv' failed to build
error: 1 dependencies of derivation '/nix/store/97882fyjn32cc6biar60x75fpp371566-ronn-0.10.1.drv' failed to build
error: 1 dependencies of derivation '/nix/store/gsnwdgafz7czy1ca96b2sgr095k47iq2-actionlint-1.7.7.drv' failed to build
error: 1 dependencies of derivation '/nix/store/7nvsky84fws4lc1nhkr38c2b9yyr7s9d-home-manager-applications.drv' failed to build
error: 1 dependencies of derivation '/nix/store/vlzwa0j6ljj38457whfi9ll4ay8m4krf-home-manager-fonts.drv' failed to build
error: 1 dependencies of derivation '/nix/store/m60anx6pcl18gb6wdaqpd7nxms4yizjg-home-manager-path.drv' failed to build
error (ignored): error: cannot unlink "/private/tmp/nix-build-whisper-0.6.0.drv-4/source/target/aarch64-apple-darwin/release/deps": Directory not empty
error: 1 dependencies of derivation '/nix/store/60z2q88r583368c5lgbwgn2lp9y31pwf-system-applications.drv' failed to build
error: 1 dependencies of derivation '/nix/store/3l5p89klbglv23xk3mlnjj6lrmh3kakx-system-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/ps2pykvd8866hysx82dwgqb9pkjdh33z-darwin-system-25.11.9a9ab01.drv' failed to build
</code></pre></div></div>
<p>Based on this, I can tell that <code class="language-plaintext highlighter-rouge">nokogiri</code> seems to have failed:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>For full logs, run:
nix log /nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv
</code></pre></div></div>
<p>Based on the heirarchy of th error message, it looks like <code class="language-plaintext highlighter-rouge">ronn-gems</code>, <code class="language-plaintext highlighter-rouge">ronn</code>, <code class="language-plaintext highlighter-rouge">actionlint</code>, and some <code class="language-plaintext highlighter-rouge">home-manager</code> paths are also failing (likely as a result).
However, I’m not entirely sure exactly <em>how</em> I am depending on <code class="language-plaintext highlighter-rouge">nokogiri</code>.</p>
<p>Up until recently, my approach in these situations was to just <code class="language-plaintext highlighter-rouge">grep</code> (or <code class="language-plaintext highlighter-rouge">rg</code> rather) to see if any of these are explicitly included in my configuration:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rg <span class="nt">-t</span> nix nokogiri ~/git/nixos
<span class="gp">$</span><span class="w">
</span></code></pre></div></div>
<p>However, I have recently dug more into <code class="language-plaintext highlighter-rouge">nix why-depends</code>, and I’m pleased to report that it seems helpful here – but not necessarily by itself.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix why-depends <span class="nt">--help</span>
<span class="go">nix why-depends [option...] package dependency
Examples
· Show one path through the dependency graph leading from Hello to Glibc:
</span><span class="gp"> │ #</span><span class="w"> </span>nix why-depends nixpkgs#hello nixpkgs#glibc
</code></pre></div></div>
<p>My first thought was that I should use my system (<code class="language-plaintext highlighter-rouge">.#darwinConfigurations.natepro.config.system.build.toplevel</code>) as the “dependency” and <code class="language-plaintext highlighter-rouge">nokogiri</code> as the “package”, but it seems necessary for the “package” to be successfully built for <code class="language-plaintext highlighter-rouge">why-depends</code> to work; attempting to use it in this way just gives us back the same backtrace:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix why-depends ~/git/nixos#darwinConfigurations.natepro.config.system.build.toplevel nokogiri
error: builder for '/nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv' failed with exit code 1;
last 25 log lines:
> current directory: /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/gems/nokogiri-1.16.0/ext/nokogiri
> make DESTDIR\= sitearchdir\=./.gem.20251017-95545-55qfgi sitelibdir\=./.gem.20251017-95545-55qfgi
> compiling gumbo.c
> In file included from gumbo.c:30:
> In file included from ./nokogiri.h:77:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby.h:38:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/ruby.h:28:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic.h:24:
> In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic/char.h:29:
> /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/core/rstring.h:398:24: warning: default initialization of an object of type 'struct RString' with const member leaves the object uninitialized [-Wdefault-const-init-field-unsafe]
> 398 | struct RString retval;
> | ^
> /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/core/rbasic.h:88:17: note: member 'klass' declared 'const' here
> 88 | const VALUE klass;
> | ^
> gumbo.c:32:10: fatal error: 'nokogiri_gumbo.h' file not found
> 32 | #include "nokogiri_gumbo.h"
> | ^~~~~~~~~~~~~~~~~~
> 1 warning and 1 error generated.
> make: *** [Makefile:248: gumbo.o] Error 1
>
> make failed, exit code 2
>
> Gem files will remain installed in /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/gems/nokogiri-1.16.0 for inspection.
> Results logged to /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/extensions/arm64-darwin-24/3.3.0/nokogiri-1.16.0/gem_make.out
For full logs, run:
nix log /nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv
error: 1 dependencies of derivation '/nix/store/a4539sgfjbl0xqz3jx4slch2ba9d6rwg-ronn-gems.drv' failed to build
error: 1 dependencies of derivation '/nix/store/97882fyjn32cc6biar60x75fpp371566-ronn-0.10.1.drv' failed to build
error: 1 dependencies of derivation '/nix/store/gsnwdgafz7czy1ca96b2sgr095k47iq2-actionlint-1.7.7.drv' failed to build
error (ignored): error: cannot unlink "/private/tmp/nix-build-time-1.9.drv-3/time-1.9": Directory not empty
error: 1 dependencies of derivation '/nix/store/baczh3ilpnzwii15r3hd1vzhnmgscdcy-home-manager-applications.drv' failed to build
error (ignored): error: cannot unlink "/private/tmp/nix-build-ffmpeg-7.1.1.drv-10": Directory not empty
error: 1 dependencies of derivation '/nix/store/1n84x1s39rmp27c2m4wg7i5rih9xq1xs-home-manager-fonts.drv' failed to build
error: 1 dependencies of derivation '/nix/store/isnjghv45qr10hdwakv86m0993phhb55-home-manager-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/n2am0d06r4bbn637v64f566zkvyki1k9-system-applications.drv' failed to build
error: 1 dependencies of derivation '/nix/store/pkkf0bsgnxfg0x1vwcgpqr676cph79gc-system-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/9wv921vl3aacjxkzm6s9ip247nhwlww1-darwin-system-25.11.9a9ab01.drv' failed to build
</code></pre></div></div>
<p>It seems that a reasonable workaround might be to use the <em>current</em> system, to see how <em>it</em> depends on nokogiri.
Thankfully the symlink at <code class="language-plaintext highlighter-rouge">/run/current-system</code> provides a handy shortcut!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ readlink /run/current-system
/nix/store/735wnqlyhfx5h4l5p22cpg06463b84gq-darwin-system-25.11.9a9ab01
</code></pre></div></div>
<p>We then run into our next issue:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix why-depends /run/current-system nokogiri
error: cannot find flake 'flake:nokogiri' in the flake registries
</code></pre></div></div>
<p>The “dependency” is not a string of a package name, looks like it needs to be a flake.
We saw <code class="language-plaintext highlighter-rouge">ruby3.3-nokogiri</code> above, so the package is likely <code class="language-plaintext highlighter-rouge">rubyPackages.nokogiri</code> or <code class="language-plaintext highlighter-rouge">rubyPackages_3_3.nokogiri</code>, but neither of these seem to work either, nor does using the path of the failing derivation:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix why-depends /run/current-system nixpkgs#rubyPackages.nokogiri
these 9 paths will be fetched (0.72 MiB download, 4.95 MiB unpacked):
/nix/store/2h6w1ka2q5ksnwfgw064240r3vmir32p-find-xml-catalogs-hook
/nix/store/x06zagc5aasqd4nvjfzg6zv917v7zkvn-find-xml-catalogs-hook
/nix/store/jf18994msibcw4z59mh2lgw2hmfavlg4-libxml2-2.14.5-dev
/nix/store/3lq98gys6gf97z6cjpklnf9mc4s4wmyb-libxslt-1.1.43
/nix/store/p35v975i3kx3clh4afgi7lcjnb0b5wpy-libxslt-1.1.43-bin
/nix/store/xld92i9ps44vrdj7dcdzk4bhzaihcb6i-libxslt-1.1.43-dev
/nix/store/6ixfg46lnzmssfj71h5rf11aspvirmc0-ruby3.3-mini_portile2-2.8.8
/nix/store/znhn5z9z0c1i780jw54ag9rnpm92z1ln-ruby3.3-nokogiri-1.18.7
/nix/store/bgr5q188w30w1sbc58wglnf1f8hrrmls-ruby3.3-racc-1.8.1
'/nix/store/735wnqlyhfx5h4l5p22cpg06463b84gq-darwin-system-25.11.9a9ab01' does not depend on 'flake:nixpkgs#rubyPackages.nokogiri'
$ nix why-depends /run/current-system nixpkgs#rubyPackages_3_3.nokogiri
these 9 paths will be fetched (0.72 MiB download, 4.95 MiB unpacked):
/nix/store/2h6w1ka2q5ksnwfgw064240r3vmir32p-find-xml-catalogs-hook
/nix/store/x06zagc5aasqd4nvjfzg6zv917v7zkvn-find-xml-catalogs-hook
/nix/store/jf18994msibcw4z59mh2lgw2hmfavlg4-libxml2-2.14.5-dev
/nix/store/3lq98gys6gf97z6cjpklnf9mc4s4wmyb-libxslt-1.1.43
/nix/store/p35v975i3kx3clh4afgi7lcjnb0b5wpy-libxslt-1.1.43-bin
/nix/store/xld92i9ps44vrdj7dcdzk4bhzaihcb6i-libxslt-1.1.43-dev
/nix/store/6ixfg46lnzmssfj71h5rf11aspvirmc0-ruby3.3-mini_portile2-2.8.8
/nix/store/znhn5z9z0c1i780jw54ag9rnpm92z1ln-ruby3.3-nokogiri-1.18.7
/nix/store/bgr5q188w30w1sbc58wglnf1f8hrrmls-ruby3.3-racc-1.8.1
'/nix/store/735wnqlyhfx5h4l5p22cpg06463b84gq-darwin-system-25.11.9a9ab01' does not depend on 'flake:nixpkgs#rubyPackages_3_3.nokogiri'
$ nix why-depends /run/current-system /nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv
'/nix/store/735wnqlyhfx5h4l5p22cpg06463b84gq-darwin-system-25.11.9a9ab01' does not depend on '/nix/store/kyiywvx9mnq90f466nwwj876zbzqmc1m-ruby3.3-nokogiri-1.16.0.drv'
</code></pre></div></div>
<p>Rethinking, this makes <em>some</em> sense, as the <em>current</em> system obviously doesn’t depend on the <em>failing</em> package; its build succeeded!
So if we’re trying to trace the path from the <em>failing</em> dependency to the <em>failing</em> build, we would need to ask about how the <em>current</em>, successfully built system dependended on the <em>successful</em> build of its <code class="language-plaintext highlighter-rouge">nokogiri</code> (assuming that this relationship hasn’t changed – which is a load-bearing assumption).</p>
<p>So how do we figure out what <code class="language-plaintext highlighter-rouge">nokogiri</code> path the current system depended on?
Unfortunately just searching <code class="language-plaintext highlighter-rouge">/nix/store</code> for ‘.<em>nokogiri.</em>’ will give us dozens (or more) of results from prior system generations and other packages, so that’s out.
Luckily we can look a little deeper into our current system dependencies with <code class="language-plaintext highlighter-rouge">nix derivation show /run/current-system</code>.</p>
<p>Unfortunately, we are again stymied:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix derivation show /run/current-system | rg nokogiri
$
</code></pre></div></div>
<p>No results.
I think this occurs because the current system doesn’t have a <em>direct</em> dependency on nokogiri; as noted above, we haven’t <em>explicitly</em> listed it anywhere.
So perhaps it is a build dependency (i.e. a dependency of a dependency).
Thankfully there is a <code class="language-plaintext highlighter-rouge">--recursive</code> flag!
This will take longer to run, but should hopefully get us to our next step:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix derivation show --recursive /run/current-system | rg -c nokogiri
23
</code></pre></div></div>
<p>Based on its <code class="language-plaintext highlighter-rouge">help</code> page, we see that this tool outputs JSON that maps <code class="language-plaintext highlighter-rouge">store_path:output</code> where <code class="language-plaintext highlighter-rouge">output</code> is in the format described <a href="https://nix.dev/manual/nix/2.28/protocols/json/derivation.html">in the nix manual</a>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix derivation show --help | rg JSON
This command prints on standard output a JSON representation of the store
nix derivation show outputs a JSON map of store paths to derivations in the
</code></pre></div></div>
<p>Looking through this format, we could almost certainly “reimplement” <code class="language-plaintext highlighter-rouge">nix why-depends</code> by searching the <code class="language-plaintext highlighter-rouge">inputDrvs</code>, but this is slightly more effort than just finding the store paths:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix derivation show --recursive /run/current-system | jq -r 'keys | map(select(match("nokogiri")))[]'
/nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv
/nix/store/xa2icl9j5jxwx0v2fdxv3qazyv1h43m4-nokogiri-1.16.0.gem.drv
</code></pre></div></div>
<p>then putting these into <code class="language-plaintext highlighter-rouge">nix why-depends</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix why-depends /run/current-system /nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv
'/nix/store/h4s1jcgfygr4mraz3lvy41iwrgzjsimr-darwin-system-25.11.9a9ab01' does not depend on '/nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv'
</code></pre></div></div>
<p>Huh, another speedbump.
We’ve already verified that the current system <em>does</em> at least transitively depend on this derivation.
Reading through <code class="language-plaintext highlighter-rouge">nix why-depends --help</code>, we see that it accepts a <code class="language-plaintext highlighter-rouge">--derivation</code> flag, and this seems to finally get us our answer:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix why-depends --derivation /run/current-system /nix/store/xa2icl9j5jxwx0v2fdxv3qazyv1h43m4-nokogiri-1.16.0.gem.drv
/nix/store/2qhzzfbv4zdks9rh1j1nq405m4lifbr3-darwin-system-25.11.9a9ab01.drv
└───/nix/store/1067s8x77wnbp4x59aa9vgrdpjazjrpk-system-applications.drv
└───/nix/store/s5yav52c6b6d8ffl1pcla41y9njqzklf-actionlint-1.7.7.drv
└───/nix/store/iwnbim2kfpsx0njiczrjdbwkzppqgzpf-ronn-0.10.1.drv
└───/nix/store/bjkr33d0dsr4am83n6ypjfazkm58my8s-ronn-gems.drv
└───/nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv
└───/nix/store/xa2icl9j5jxwx0v2fdxv3qazyv1h43m4-nokogiri-1.16.0.gem.drv
</code></pre></div></div>
<p>Anticlimactically, careful inspect reveals this to be essentially a cleaned-up version of the output we got from <code class="language-plaintext highlighter-rouge">darwin-rebuild</code> in the first place.
(NB: some of the store paths will have changed during the writing of this article, as I rebuilt my system before finalizing the post.)</p>
<p>Thankfully, with some help from the cleaner output, we can see fairly easily that the system depends on system-applications, which depends on actionlint.
Sure enough, searching our flake, we find it is directly referenced:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rg <span class="nt">-t</span> nix <span class="nt">-C3</span> actionlint
<span class="go">modules/default-packages.nix
25- ]
26- ++ [
27- (pass.withExtensions (exts: [ exts.pass-otp ]))
28: actionlint
29- alacritty
30- alejandra
31- bacon
</span></code></pre></div></div>
<p>Unsurprisingly commenting out this line allows our system to build.</p>
<p>As a side-note, there is also an <code class="language-plaintext highlighter-rouge">--all</code> flag that shows other paths to the same dependency; its output looks more similar to the original output from <code class="language-plaintext highlighter-rouge">darwin-rebuild</code>:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix why-depends <span class="nt">--derivation</span> <span class="nt">--all</span> /run/current-system /nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv
<span class="go">/nix/store/2qhzzfbv4zdks9rh1j1nq405m4lifbr3-darwin-system-25.11.9a9ab01.drv
├───/nix/store/1067s8x77wnbp4x59aa9vgrdpjazjrpk-system-applications.drv
│ └───/nix/store/s5yav52c6b6d8ffl1pcla41y9njqzklf-actionlint-1.7.7.drv
│ ├───/nix/store/iwnbim2kfpsx0njiczrjdbwkzppqgzpf-ronn-0.10.1.drv
│ │ └───/nix/store/bjkr33d0dsr4am83n6ypjfazkm58my8s-ronn-gems.drv
│ │ ├───/nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv
│ │ └───/nix/store/77xqfn4qs4d4k9fabvjq72sj33asm26y-ruby3.3-ronn-ng-0.10.1.drv
│ │ └───/nix/store/fy1akqdymr80p6ypjwvdrpp6l9fm1j68-ruby3.3-nokogiri-1.16.0.drv
│ └───/nix/store/92525bicsii166yk56g3vm4grbwpjh5h-actionlint-1.7.7-go-modules.drv
│ └───/nix/store/iwnbim2kfpsx0njiczrjdbwkzppqgzpf-ronn-0.10.1.drv
├───/nix/store/dwh1c68h8xijmfbykyn9ggkpjshw17fl-system-path.drv
│ └───/nix/store/s5yav52c6b6d8ffl1pcla41y9njqzklf-actionlint-1.7.7.drv
├───/nix/store/qvw37qgfkpfldbv0vsihfijsrxb8xgda-etc.drv
│ ├───/nix/store/dwh1c68h8xijmfbykyn9ggkpjshw17fl-system-path.drv
│ └───/nix/store/440cn56dyhlc0ghf281gb4r6dbzcbfc8-user-environment.drv
│ └───/nix/store/v1b1lvcc9is3dpxjl2qr3dxks8f9kw24-home-manager-path.drv
│ └───/nix/store/s5yav52c6b6d8ffl1pcla41y9njqzklf-actionlint-1.7.7.drv
└───/nix/store/88fh7jn807sfhjjzk0f67s48f443yl25-activation-n8henrie.drv
└───/nix/store/r0adycland7ffzpz80l35y7x2iw9ald3-home-manager-generation.drv
├───/nix/store/v1b1lvcc9is3dpxjl2qr3dxks8f9kw24-home-manager-path.drv
├───/nix/store/c749wf5q4d9c6cd78wypd9am1kl92kby-home-manager-files.drv
│ ├───/nix/store/nkhwrgp52yhambmzafqsbrlwimcm6zsz-home-manager-applications.drv
│ │ └───/nix/store/s5yav52c6b6d8ffl1pcla41y9njqzklf-actionlint-1.7.7.drv
│ ├───/nix/store/d8p8kh2f9kz3ikirxggicf1iirdllp33-hm_fontconfigconf.d10hmfonts.conf.drv
│ │ └───/nix/store/v1b1lvcc9is3dpxjl2qr3dxks8f9kw24-home-manager-path.drv
│ └───/nix/store/xzp24ns6lk93vd3spdkfqb5dmgncvb45-hm_LibraryFonts.homemanagerfontsversion.drv
│ └───/nix/store/x42ccmdhrw0smyln136vcwkw1kr8qx42-home-manager-fonts.drv
│ └───/nix/store/s5yav52c6b6d8ffl1pcla41y9njqzklf-actionlint-1.7.7.drv
└───/nix/store/na4sxb3szzxhw89pm9j8rywmfj4fhdc9-activation-script.drv
├───/nix/store/x42ccmdhrw0smyln136vcwkw1kr8qx42-home-manager-fonts.drv
└───/nix/store/xzp24ns6lk93vd3spdkfqb5dmgncvb45-hm_LibraryFonts.homemanagerfontsversion.drv
</span></code></pre></div></div>
Sun, 12 Oct 2025 09:04:59 -0600
https://n8henrie.com/2025/10/nix-why-does-my-system-depend-on-pkg/
https://n8henrie.com/2025/10/nix-why-does-my-system-depend-on-pkg/
MacOS
nix
tech
-
Compiling Rust for the ESP32 with Nix
<p><strong>Bottom Line:</strong> Nix’s tooling for Rust compilation can target the ESP32.
<!--more--></p>
<h3 id="preface">Preface</h3>
<p>This is a fairly long, meandering post about using nix to compile a <code class="language-plaintext highlighter-rouge">no_std</code>
Rust project for the ESP32C3. I was able to get things working eventually, and
I try to recreate the process that took me there, including several mistakes
along the way. Many of the error messages that I encountered seemed quite
obscure and had non-obvious (to me, at least) fixes. Worst of all was that I
found relatively few directly relevant or helpful blog posts in spite of fairly
diligent searches through DuckDuckGo, Google, Stack Overflow, GitHub, and the
NixOS Discourse. I decided to write this post in this style – including the
error messages I encountered and what I did to resolve or work around them –
specifically hoping to provide future searchers with something more helpful
than what I found, should they run across similar errors. For anyone that just
wants to “flip to the solutions at the end of the textbook,” feel free to
scroll to the bottom, where I’ve included the final nix config; you’ll
obviously still need to clone the <code class="language-plaintext highlighter-rouge">esp-rs/no_std-training</code> repo to get the relevant
Rust code.</p>
<hr />
<p><br />
I occasionally like to tinker with electronics, like <a href="https://n8henrie.com/tags/#arduino-ref">toy projects on an
arduino</a>, or sometimes building for
even cheaper targets like the <a href="https://amzn.to/3Lw87C2">ESP01</a> or an
<a href="https://amzn.to/46kaorV">ATMEGA328P</a> directly.</p>
<p>I’ve traditionally used the Arduino IDE and/or
<a href="https://platformio.org/">PlatformIO</a> to get the job done, and since I hardly
know any C, I’ve also experimented with <a href="https://micropython.org">micropython</a>
(whose support for the ESP8266 is particularly welcome).</p>
<p>More recently, as I continue learning about Rust, one of the features that
particularly appeals to me is the support for compiling for “bare-metal”
<a href="https://docs.rust-embedded.org/book/intro/no-std.html"><code class="language-plaintext highlighter-rouge">no_std</code></a> targets,
including my beloved ATMEGA328p (<code class="language-plaintext highlighter-rouge">--target avr-unknown-gnu-atmega328</code>). Perhaps
an even more exciting target is the ESP32C3, for which an <a href="https://mabez.dev/blog/posts/">incredible amount of
(ongoing) work</a> is making this a wifi-enabled
<code class="language-plaintext highlighter-rouge">no_std</code> Rust-compatible chip: <a href="https://github.com/esp-rs/esp-wifi">https://github.com/esp-rs/esp-wifi</a></p>
<p>Because I’m only an <em>occasional</em> tinkerer with these types of projects, one
issue that has bitten me more than once is when updates to the tooling and
ecosystem make it so that once-working code no longer works when I come back to it
after a hiatus. While many of these projects can run for years or decades once
flashed to a device, I often find that if I return to update or modify a
project months or years later, that so much of the tooling has changed that I
can’t get the project to compile (even with no changes to my code) or perhaps
the tooling to flash the binary has changed or become outdated. While it’s
great that arduino, platformio, esptool, ampy, etc. are continuing to evolve
and improve, it is certainly frustrating when things have changed so much that
existing projects no longer work.</p>
<p>The Rust tooling is already pretty solid at protecting against this; for
example, one can include a <code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code> file along with a project and
pin a specific version of the Rust compiler (e.g. <code class="language-plaintext highlighter-rouge">nightly-2020-07-10</code>), and
even specify included components and targets:
<a href="https://rust-lang.github.io/rustup/overrides.html">https://rust-lang.github.io/rustup/overrides.html</a></p>
<p>I think this would <em>probably</em> suffice for making it highly likely that one
could return to a Rust-based microcontroller project years later and still be
able to produce a usable binary. However, this is the type of problem for which
nix <strong>really</strong> shines – it can help guarantee that all of the dependencies for
a project are reproducible down to first principles and even leverages a
binary cache that can help ensure that tools are available for use in nix
projects even if their original sources are taken offline. If one knows
beforehand that it may be many years before they return to a project, it’s even
possible to vendor archives of all of these dependencies, guarding against the
hypothetical possibility that the <code class="language-plaintext highlighter-rouge">nightly-2020-07-10</code> version of Rust is taken
down and no longer available for download (see also: <code class="language-plaintext highlighter-rouge">nix nar</code>, <code class="language-plaintext highlighter-rouge">nix bundle</code>,
<code class="language-plaintext highlighter-rouge">nix-copy-closure</code>).</p>
<p>For the purposes of this post, I found that – with some effort – I was able
to use the nix tooling to compile a <code class="language-plaintext highlighter-rouge">no_std</code> project for the ESP32C3 that
successfully connects to wifi. I think the best place to start is by putting
nix aside for a moment to focus on the Rust code.</p>
<p>I started by dusting off my <a href="https://amzn.to/48rT8mo">ESP32C3</a> and referring to
the <a href="https://github.com/esp-rs/esp-wifi">esp-rs/esp-wifi repo</a>. I had toyed
with it a year or two ago, but the esp-rs team has put a <em>lot</em> of work into it
since then, so I wanted to see how well the updates worked. I was able to get
the code in <code class="language-plaintext highlighter-rouge">examples-esp32c3/examples/dhcp.rs</code> to work, but as of the time of
writing <a href="https://github.com/esp-rs/esp-wifi/blob/b54310ece09141d116c94f64ff8559810c64b6be/examples.md?plain=1#L5">the instructions</a> are set up for this to be run as an example
(<code class="language-plaintext highlighter-rouge">cargo run --example dhcp --release --features "embedded-svc,wifi"</code>) from the
root of the repo, and I found it fairly difficult to make modifications to this
code for a standalone project, in part due to the inter-dependencies within the
workspace.</p>
<p>Luckily, while poking around, I found a fairly new repo at
<a href="https://github.com/esp-rs/no_std-training">github.com/esp-rs/no_std-training</a>
that seemed to be just the ticket – in <code class="language-plaintext highlighter-rouge">no_std-training/intro/http-client</code>, I
found an example project including a <code class="language-plaintext highlighter-rouge">Cargo.toml</code>, <code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code>, and
sample code in a subdirectory at <code class="language-plaintext highlighter-rouge">examples/http-client.rs</code> that seems like a
great start. At the time of writing <code class="language-plaintext highlighter-rouge">src/main.rs</code> seemed incomplete and was not
working – this project appears to be a work in progress.</p>
<p>On my M1 Mac, I found that I was able to compile this code with no difficulty:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git clone [email protected]:esp-rs/no_std-training.git
<span class="gp">$</span><span class="w"> </span><span class="nb">cd </span>no_std-training
<span class="gp">$</span><span class="w"> </span>git checkout 88bc692d81dfcf9491c80dc7c9e8601b702e465a
<span class="gp">$</span><span class="w"> </span><span class="nb">cd </span>intro/http-client
<span class="gp">$</span><span class="w"> </span><span class="nb">cat </span>examples/http-client.rs <span class="o">></span> src/main.rs
<span class="gp">$</span><span class="w"> </span>rustup target add riscv32imc-unknown-none-elf
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">SSID</span><span class="o">=</span>foo <span class="nv">PASSWORD</span><span class="o">=</span>bar
<span class="gp">$</span><span class="w"> </span>cargo build
<span class="gp">$</span><span class="w"> </span>file target/riscv32imc-unknown-none-elf/debug/http-client
<span class="go">target/riscv32imc-unknown-none-elf/debug/http-client: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
</span></code></pre></div></div>
<p><strong>NB:</strong> the esp-rs team strongly recommends building in <code class="language-plaintext highlighter-rouge">--release</code> mode, and
cautions that the code may fail to run if compiled in debug mode (the default)
like I’ve done above; I’m just using debug mode to check my work while writing
this post because it’s faster to compile.</p>
<p>With that working, I set about to putting dependencies into nix to hopefully
help <em>keep</em> it working. One of the first steps to help this process is to pin
any <code class="language-plaintext highlighter-rouge">git</code> dependencies in <code class="language-plaintext highlighter-rouge">Cargo.toml</code>, to make sure we’re always pulling down
the same version.</p>
<p>Thankfully, reviewing <code class="language-plaintext highlighter-rouge">Cargo.toml</code> shows only a single <code class="language-plaintext highlighter-rouge">git</code> dependency, on
<code class="language-plaintext highlighter-rouge">esp-wifi</code> itself, which we can pin to a recent and known working commit by
adding a <code class="language-plaintext highlighter-rouge">rev</code> to the <code class="language-plaintext highlighter-rouge">esp-wifi</code> line:</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">esp-wifi</span> <span class="o">=</span> <span class="p">{</span> <span class="py">git</span> <span class="p">=</span> <span class="s">"https://github.com/esp-rs/esp-wifi/"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"esp32c3"</span><span class="p">,</span> <span class="s">"wifi-logs"</span><span class="p">,</span> <span class="s">"wifi"</span><span class="p">],</span> <span class="py">rev</span> <span class="p">=</span> <span class="s">"e7140fd35852dadcd1df7592dc149e876256348f"</span> <span class="p">}</span>
</code></pre></div></div>
<p>I usually start adding nix to my projects using a flake template, which I’ve
made available at <a href="https://github.com/n8henrie/flake-templates/blob/274e5d613b0c4b9de4089b3db18126bcc29ea7a4/trivial/flake.nix">github.com/n8henrie/flake-templates</a> and can be used like
so:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">$</span> <span class="nv">nix</span> <span class="nv">flake</span> <span class="nv">init</span> <span class="o">-</span><span class="nv">t</span> <span class="nv">github</span><span class="p">:</span><span class="sx">n8henrie/flake-templates</span><span class="c">#trivial</span>
</code></pre></div></div>
<p>This includes a function named <code class="language-plaintext highlighter-rouge">systemClosure</code> that helps reduce some
boilerplate to expose outputs for multiple systems. (Most people use
<code class="language-plaintext highlighter-rouge">flake-utils</code> for this, no specific reason that I don’t.)</p>
<p>Next, I add an input for <a href="https://github.com/oxalica/rust-overlay">oxalica/rust-overlay</a>, which is an overlay that –
among other things – makes it easier to leverage an existing
<code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code> file in order to specify the desired versions of the Rust
tools. I pinned its input to match my nixpkgs version:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:nixos/nixpkgs/release-23.05"</span><span class="p">;</span>
<span class="nv">rust-overlay</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:oxalica/rust-overlay"</span><span class="p">;</span>
<span class="nv">inputs</span><span class="o">.</span><span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">follows</span> <span class="o">=</span> <span class="s2">"nixpkgs"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Next, I did the <em>easy</em> part, by making a dev shell that includes the version of
cargo specified by <code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code>, by adding the following (I have
ommitted some context for the sake of brevity; the full final file is at the
bottom of the post):</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span>
<span class="nv">pkgs</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">overlays</span> <span class="o">=</span> <span class="p">[(</span><span class="kr">import</span> <span class="nv">rust-overlay</span><span class="p">)];</span>
<span class="p">};</span>
<span class="nv">toolchain</span> <span class="o">=</span> <span class="p">(</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">rust-bin</span><span class="o">.</span><span class="nv">fromRustupToolchainFile</span> <span class="sx">./rust-toolchain.toml</span>
<span class="p">);</span>
<span class="kn">in</span>
<span class="nv">devShells</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">mkShell</span> <span class="p">{</span>
<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">cargo-espflash</span>
<span class="nv">toolchain</span>
<span class="p">];</span>
<span class="p">};</span>
</code></pre></div></div>
<p>This allows me to:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git add flake.nix
<span class="gp">$</span><span class="w"> </span>nix develop
<span class="gp">$</span><span class="w"> </span><span class="c"># show that cargo is being provided by nix:</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">type</span> <span class="nt">-p</span> cargo
<span class="go">/nix/store/5sdglskvfpv67kw2hcp8pnkvk7w5d4rl-rust-default-1.72.0-nightly-2023-06-25/bin/cargo
</span><span class="gp">$</span><span class="w"> </span><span class="c"># cargo has the expected version:</span>
<span class="gp">$</span><span class="w"> </span>cargo <span class="nt">--version</span>
<span class="go">cargo 1.72.0-nightly (03bc66b55 2023-06-23)
</span><span class="gp">$</span><span class="w"> </span>cargo build
<span class="gp">$</span><span class="w"> </span><span class="c"># nix's cargo compiles the project without errors:</span>
<span class="gp">$</span><span class="w"> </span>file target/riscv32imc-unknown-none-elf/debug/http-client
<span class="go">target/riscv32imc-unknown-none-elf/debug/http-client: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
</span><span class="gp">$</span><span class="w"> </span><span class="c"># show that the espflash utility is also available</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">type</span> <span class="nt">-p</span> cargo-espflash
<span class="go">/nix/store/yf5d1k5mdqxghpb89qfqglcxqs4ksx0n-cargo-espflash-1.7.0/bin/cargo-espflash
</span></code></pre></div></div>
<p><strong>Hint</strong>: if nix gives you <code class="language-plaintext highlighter-rouge">error: getting status of... default.nix': No such
file or directory</code>, when there <em>clearly</em> is a <code class="language-plaintext highlighter-rouge">default.nix</code>, it probably means
that you’re working in a git repo (which we are) but haven’t added that file;
try <code class="language-plaintext highlighter-rouge">git add default.nix</code> (or whatever the file is) and run the nix command
again.</p>
<p>Cool, it worked!</p>
<p>This is <em>probably</em> good enough for most intents and purposes, at it should
provide a reproducible Rust / cargo toolchain (and the espflash utility used to
flash the code onto the esp32). One simply has to <code class="language-plaintext highlighter-rouge">nix develop</code> and they should
be dropped into a shell environment with all of the required tools, and that
environment should be reproducible in the future.</p>
<p>However, I’ve seen that nix also includes tooling for building a Rust package
directly with the likes of <code class="language-plaintext highlighter-rouge">buildRustPackage</code>. Recommended reading:</p>
<ul>
<li><a href="https://nixos.wiki/wiki/Rust">https://nixos.wiki/wiki/Rust</a></li>
<li><a href="https://github.com/NixOS/nixpkgs/blob/05a907557826942cdf601c96d3dea0e8d4924491/doc/languages-frameworks/rust.section.md">https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/rust.section.md</a></li>
</ul>
<p>I wanted to explore this approach as well, and this is where things got a
little hairy.</p>
<p>To start, I added a <code class="language-plaintext highlighter-rouge">default.nix</code> with the following contents:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">lib</span><span class="p">,</span>
<span class="nv">rustPlatform</span><span class="p">,</span>
<span class="nv">name</span><span class="p">,</span>
<span class="p">}:</span> <span class="p">(</span><span class="nv">rustPlatform</span><span class="o">.</span><span class="nv">buildRustPackage</span>
<span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">name</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="nv">lib</span><span class="o">.</span><span class="nv">cleanSource</span> <span class="sx">./.</span><span class="p">;</span>
<span class="p">})</span>
</code></pre></div></div>
<p>and I added the following to my <code class="language-plaintext highlighter-rouge">flake.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="p">((</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">fromTOML</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span> <span class="sx">./Cargo.toml</span><span class="p">))</span><span class="o">.</span><span class="nv">package</span><span class="p">)</span> <span class="nv">name</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>For anyone less familiar with nix, this pulls the <code class="language-plaintext highlighter-rouge">name</code> attribute from
<code class="language-plaintext highlighter-rouge">Cargo.toml</code> and passes it to <code class="language-plaintext highlighter-rouge">default.nix</code> using the <code class="language-plaintext highlighter-rouge">callPackage</code> pattern.
<code class="language-plaintext highlighter-rouge">pkgs.callPackage</code> is not required in this case but is a handy pattern in
general because nix automatically resolves input dependencies that are
available attributes of <code class="language-plaintext highlighter-rouge">pkgs</code> (in this case <code class="language-plaintext highlighter-rouge">rustPlatform</code>) but
also allows for passing in dependencies manually. This allows me to pass in
<code class="language-plaintext highlighter-rouge">name</code> (which is not an attribute of <code class="language-plaintext highlighter-rouge">pkgs</code>), or I could also override
<code class="language-plaintext highlighter-rouge">rustPlatform</code> if desired. When one has <em>dozens</em> of inputs it can be
particularly handy, as one can override a single one of them while letting the
remainder be resolved automatically to their defaults. Also, <code class="language-plaintext highlighter-rouge">default.nix</code> –
as its name suggests – is picked up automatically by <code class="language-plaintext highlighter-rouge">callPackage ./.</code>, but I
could have named it <code class="language-plaintext highlighter-rouge">foo.nix</code> and used <code class="language-plaintext highlighter-rouge">callPackage ./foo.nix</code>.</p>
<p>Let’s see where this gets us:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="go">error: getting status of '/nix/store/s9af3f3j2lz0sa9l3n6d2lsxhngyqq96-source/intro/http-client/default.nix': No such file or directory
</span><span class="gp">$</span><span class="w"> </span><span class="c"># whups, see my hint above</span>
<span class="gp">$</span><span class="w"> </span>git add default.nix
<span class="gp">$</span><span class="w"> </span>nix build
<span class="go">error: cargoSha256, cargoHash, cargoVendorDir, or cargoLock must be set
</span></code></pre></div></div>
<p>Ok, so nix wants me to point it to a <code class="language-plaintext highlighter-rouge">Cargo.lock</code> file so it can ensure that
all of the Rust dependencies are reproducible. Thankfully we should still have
one hanging around from the <code class="language-plaintext highlighter-rouge">cargo build --target=...</code> step above. (If not
you’ll need to re-run that step.) Add the following to <code class="language-plaintext highlighter-rouge">default.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">cargoLock</span><span class="o">.</span><span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
</code></pre></div></div>
<p>One <em>might</em> also need to add <code class="language-plaintext highlighter-rouge">Cargo.lock</code> to git at this point, but in this
case it’s already being tracked. Sometimes it is <code class="language-plaintext highlighter-rouge">.gitignore</code>d in which case
one might choose to <code class="language-plaintext highlighter-rouge">git add -f Cargo.lock</code>.</p>
<p>Next error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="go">error: No hash was found while vendoring the git dependency esp-wifi-0.1.0. You can add
a hash through the `outputHashes` argument of `importCargoLock`:
outputHashes = {
</span><span class="gp"> "esp-wifi-0.1.0" = "<hash></span><span class="s2">";
</span><span class="gp"> };</span><span class="w">
</span><span class="go">
If you use `buildRustPackage`, you can add this attribute to the `cargoLock`
attribute set.
</span></code></pre></div></div>
<p>Ok, so let’s change the <code class="language-plaintext highlighter-rouge">cargoLock</code> part to the following, knowing that we’ll
get an error about an invalid hash (the error message will tell us the correct
value to fill in):</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">cargoLock</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="nv">outputHashes</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"esp-wifi-0.1.0"</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="go">error: hash mismatch in fixed-output derivation '/nix/store/c0icjxbnwfhbw2w0pk5vd4dcw9p6irpr-esp-wifi-b54310e.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-IUkX3inbeeRZk9q/mdg56h+qft+0/TVpOM4rCKNOwz8=
</span></code></pre></div></div>
<p>Ok, let’s fill that in:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">cargoLock</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="nv">outputHashes</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"esp-wifi-0.1.0"</span> <span class="o">=</span> <span class="s2">"sha256-IUkX3inbeeRZk9q/mdg56h+qft+0/TVpOM4rCKNOwz8="</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>
<p>This time we get a different error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="gp"> ></span><span class="w"> </span>error: <span class="s2">"/nix/store/8sindl6wnv2s5z1zwvq0rkffacicx80d-rustc-1.69.0/lib/rustlib/src/rust/Cargo.lock"</span> does not exist, unable to build with the standard library, try:
<span class="gp"> ></span><span class="w"> </span>rustup component add rust-src
</code></pre></div></div>
<p>Now this one left me scratching my head for a little while, because I knew that
the esp-rs team had conveniently put the <code class="language-plaintext highlighter-rouge">rust-src</code> dependency in our
<code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code> for us:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cat </span>rust-toolchain.toml
<span class="go">[toolchain]
channel = "nightly-2023-06-25"
components = ["rust-src"]
targets = ["riscv32imc-unknown-none-elf"]
</span></code></pre></div></div>
<p>Eventually I realized that the version numbers didn’t add up: note the
<code class="language-plaintext highlighter-rouge">rustc-1.69.0</code> here as opposed to <code class="language-plaintext highlighter-rouge">rust-default-1.72.0-nightly</code> above. So
clearly one issue is that the toolchain from the <code class="language-plaintext highlighter-rouge">oxalica</code> override is <em>not</em>
being used. Which makes sense, because we’re using nix’s default
<code class="language-plaintext highlighter-rouge">rustPlatform</code>.</p>
<p>After reading the nix + Rust links above a few more times, I noticed <a href="https://github.com/NixOS/nixpkgs/blob/05a907557826942cdf601c96d3dea0e8d4924491/doc/languages-frameworks/rust.section.md#using-rust-nightly-in-a-derivation-with-buildrustpackage-using-rust-nightly-in-a-derivation-with-buildrustpackage">this
section</a> on building Rust nightly with <code class="language-plaintext highlighter-rouge">buildRustPackage</code>, which refers to
the <code class="language-plaintext highlighter-rouge">makeRustPlatform</code> function and thankfully uses the <code class="language-plaintext highlighter-rouge">oxalica</code> overlay in
its example! Taking from there, I added an additional variable to <code class="language-plaintext highlighter-rouge">flake.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">rustPlatform</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">makeRustPlatform</span> <span class="p">{</span>
<span class="nv">rustc</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="nv">cargo</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>and, lower in the same file, I used this to pass it as the <code class="language-plaintext highlighter-rouge">rustPlatform</code> input
to <code class="language-plaintext highlighter-rouge">default.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="p">((</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">fromTOML</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span> <span class="sx">./Cargo.toml</span><span class="p">))</span><span class="o">.</span><span class="nv">package</span><span class="p">)</span> <span class="nv">name</span><span class="p">;</span>
<span class="kn">inherit</span> <span class="nv">rustPlatform</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Now, I got a new error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="go">error: no matching package named `addr2line` found
</span></code></pre></div></div>
<p>Here, I eventually came across <a href="https://discourse.nixos.org/t/build-rust-app-using-cargos-build-std-feature-with-naersk-fails-because-rust-src-is-missing/13161/4">this related post</a> in the NixOS Discourse
that has a suggested workaround. Essentially, certain packages that are
required by the <code class="language-plaintext highlighter-rouge">rust-std</code> feature need to be downloaded (at build time), which
<code class="language-plaintext highlighter-rouge">cargo</code> usually takes care of. However, the “purity” of nix builds disallows
network access*, so this step fails. Instead, one needs to manually specify
these dependencies in <code class="language-plaintext highlighter-rouge">Cargo.toml</code>, and apparently the <code class="language-plaintext highlighter-rouge">dev-dependencies</code> is
the proper section for this (perhaps because they are required to build the
build tooling, not to build the crate itself – let me know if this is way off
base).</p>
<p>* At least outside of explicit downloads with tools like <code class="language-plaintext highlighter-rouge">pkgs.fetchurl</code>,
which also require a <code class="language-plaintext highlighter-rouge">hash</code> to verify that the resulting download’s contents
are exactly correct.</p>
<p>One way to add these to <code class="language-plaintext highlighter-rouge">Cargo.toml</code> is via <code class="language-plaintext highlighter-rouge">cargo add</code>, which should result in
two new lines at the bottom:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo add <span class="nt">--dev</span> addr2line
<span class="gp">$</span><span class="w"> </span><span class="nb">tail</span> <span class="nt">-2</span> Cargo.toml
<span class="go">[dev-dependencies]
addr2line = "0.21.0"
</span></code></pre></div></div>
<p>Re-running <code class="language-plaintext highlighter-rouge">nix build</code> at this point gave me a slightly different error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">></span><span class="w"> </span>error: failed to <span class="k">select </span>a version <span class="k">for </span>the requirement <span class="sb">`</span>addr2line <span class="o">=</span> <span class="s2">"^0.19.0"</span><span class="sb">`</span> <span class="o">(</span>locked to 0.19.0<span class="o">)</span>
<span class="gp">></span><span class="w"> </span>candidate versions found which didn<span class="s1">'t match: 0.21.0
</span></code></pre></div></div>
<p>I eventually sorted out that I needed to pin that exact version by editing
<code class="language-plaintext highlighter-rouge">Cargo.toml</code> adding an <code class="language-plaintext highlighter-rouge">=</code> just before the version number:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">[dev-dependencies]</span>
addr2line = "=0.19.0"
</code></pre></div></div>
<p>Interestingly, upon re-running <code class="language-plaintext highlighter-rouge">nix build</code>, I got the <em>exact same error</em>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>> error: failed to select a version for the requirement `addr2line = "^0.19.0"` (locked to 0.19.0)
> candidate versions found which didn't match: 0.21.0
</code></pre></div></div>
<p>I eventually realized that the change I made to <code class="language-plaintext highlighter-rouge">Cargo.toml</code> wasn’t reflected
in <code class="language-plaintext highlighter-rouge">Cargo.lock</code>; for that, I needed to run <code class="language-plaintext highlighter-rouge">cargo update</code>. After a <code class="language-plaintext highlighter-rouge">cargo
update</code> and another attempt at building, I see an error also discussed in that
thread:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cargo update && nix build
...
> error: no matching package named `compiler_builtins` found
</code></pre></div></div>
<p>Here we’ll repeat the same procedure:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">cargo add --dev compiler_builtins</code></li>
<li><code class="language-plaintext highlighter-rouge">cargo update && nix build</code></li>
<li>If there is an error about the version, pin it by modifying the respective
line in <code class="language-plaintext highlighter-rouge">Cargo.toml</code> from <code class="language-plaintext highlighter-rouge">compiler_builtins = "some_version_number"</code> to
<code class="language-plaintext highlighter-rouge">compiler_builtins = "=other_version_number"</code> (don’t forget the extra <code class="language-plaintext highlighter-rouge">=</code>),
where <code class="language-plaintext highlighter-rouge">other_version_number</code> is taken from <code class="language-plaintext highlighter-rouge">(locked to ...)</code> in the error
message.</li>
<li><code class="language-plaintext highlighter-rouge">cargo update && nix build</code> again, evaluate for new error message</li>
</ol>
<p>I then repeated this process a fair number of times and eventually made it to a
dependency that <em>wouldn’t</em> work:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update <span class="o">&&</span> nix build
<span class="go"> Updating crates.io index
Updating git repository `https://github.com/esp-rs/esp-wifi/`
error: failed to select a version for the requirement `hermit-abi = "=0.3.0"`
candidate versions found which didn't match: 0.3.3, 0.3.2, 0.2.6, ...
location searched: crates.io index
required by package `http-client v0.1.0 (/Users/n8henrie/git/no_std-training/intro/http-client)`
perhaps a crate was updated and forgotten to be re-vendored?
</span></code></pre></div></div>
<p>I eventually navigated to <a href="https://crates.io/crates/hermit-abi/versions">https://crates.io/crates/hermit-abi/versions</a> and
found that the <code class="language-plaintext highlighter-rouge">0.3.0</code> version we need has been <strong>yanked</strong>. <em>Ugh</em>.</p>
<p>I tried looking at the documentation for <a href="https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html"><code class="language-plaintext highlighter-rouge">patch</code>ing dependencies</a>, but I
couldn’t find an obvious way to override the version of an intermediate
dependency. Eventually I gave up and changed the version of the toolchain
in <code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code> (I found that <code class="language-plaintext highlighter-rouge">nightly-2023-08-23</code> worked).
Unfortunately, this also means that I had to delete all those
<code class="language-plaintext highlighter-rouge">dev-dependencies</code> and start again, since these are additional dependencies
required to build Rust’s build tools (I think).</p>
<p>Many rounds of <code class="language-plaintext highlighter-rouge">cargo update && nix build</code> later, I came across a new error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp"> ></span><span class="w"> </span>LLVM ERROR: Global variable <span class="s1">'_start_rust'</span> has an invalid section specifier <span class="s1">'.init.rust'</span>: mach-o section specifier requires a segment and section separated by a comma.
<span class="gp"> ></span><span class="w"> </span>error: could not compile <span class="sb">`</span>esp-riscv-rt<span class="sb">`</span> <span class="o">(</span>lib<span class="o">)</span>
<span class="gp"> ></span><span class="w"> </span>warning: build failed, waiting <span class="k">for </span>other <span class="nb">jobs </span>to finish...
<span class="gp"> ></span><span class="w"> </span>LLVM ERROR: Global variable <span class="s1">'__EXTERNAL_INTERRUPTS'</span> has an invalid section specifier <span class="s1">'.trap.rodata'</span>: mach-o section specifier requires a segment and section separated by a comma.
</code></pre></div></div>
<p>At this point, I figured that the error was related to the fact that I wasn’t
cross-compiling at all, something I had noticed in the build logs earlier in
the process:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>++ env CC_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/cc CXX_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/c++ CC_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/cc CXX_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/c++ cargo build -j 8 --target aarch64-apple-darwin --frozen --release
</code></pre></div></div>
<p>Here’s the same command split into separate lines for readability:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>++ env \
CC_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/cc \
CXX_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/c++ \
CC_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/cc \
CXX_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/c++ \
cargo build \
-j 8 \
--target aarch64-apple-darwin \
--frozen \
--release
</code></pre></div></div>
<p>If you’ll look carefull at that long incantation, you’ll see <code class="language-plaintext highlighter-rouge">--target
aarch64-apple-darwin</code>. When building with <code class="language-plaintext highlighter-rouge">cargo</code>, we were able to lean on
<code class="language-plaintext highlighter-rouge">./.cargo/config.toml</code>, conveniently provided by the esp-rs team, which sets a
default build target. Nix apparently doesn’t take that into account and is
building for the host system architecture.</p>
<p>It seems that the nix way to cross-compile Rust for other architectures is
<strong>not</strong> by setting cargo’s <code class="language-plaintext highlighter-rouge">--target</code> directly (although it seems like
previously this was the case, but no longer). Instead, one is expected to use
the usual nix cross-compilation strategy of <a href="https://github.com/NixOS/nixpkgs/blob/15047c5ec025a693bea60a17b5305a68424d9e93/doc/languages-frameworks/rust.section.md#cross-compilation-cross-compilation">setting a <code class="language-plaintext highlighter-rouge">crossSystem</code> with the
desired config</a>. Here is the example from that link:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="o"><</span><span class="nv">nixpkgs</span><span class="o">></span> <span class="p">{</span>
<span class="nv">crossSystem</span> <span class="o">=</span> <span class="p">(</span><span class="kr">import</span> <span class="o"><</span><span class="sx">nixpkgs/lib</span><span class="o">></span><span class="p">)</span><span class="o">.</span><span class="nv">systems</span><span class="o">.</span><span class="nv">examples</span><span class="o">.</span><span class="nv">armhf-embedded</span> <span class="o">//</span> <span class="p">{</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"thumbv7em-none-eabi"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I thought this seemed easy enough and set about trying to figure out the right
combination. Cargo specifies the target as <code class="language-plaintext highlighter-rouge">riscv32imc-unknown-none-elf</code>, so
one can search the available nix-provided examples by looking at
<a href="https://github.com/NixOS/nixpkgs/blob/cdb6bfeab9e1e24091e289c2fea3cf40a007cc78/lib/systems/examples.nix">lib/systems/examples.nix</a>,
or by using the following command to search for examples containing <code class="language-plaintext highlighter-rouge">riscv</code>:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix <span class="nb">eval</span> <span class="nt">--json</span> <span class="se">\</span>
<span class="go"> --apply builtins.attrNames \
</span><span class="gp"> nixpkgs#</span>lib.systems.examples |
<span class="go"> jq -r .[] |
grep -i riscv
riscv32
riscv32-embedded
riscv64
riscv64-embedded
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">riscv32-embedded</code> sounds pretty promising, right? Let’s change <code class="language-plaintext highlighter-rouge">flake.nix</code> to
use this cross system for <code class="language-plaintext highlighter-rouge">rustPlatform</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">rustPlatform</span> <span class="o">=</span> <span class="kd">let</span>
<span class="nv">pkgsCross</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">crossSystem</span> <span class="o">=</span>
<span class="nv">lib</span><span class="o">.</span><span class="nv">systems</span><span class="o">.</span><span class="nv">examples</span><span class="o">.</span><span class="nv">riscv32-embedded</span>
<span class="o">//</span> <span class="p">{</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"riscv32imc-unknown-none-elf"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kn">in</span>
<span class="nv">pkgsCross</span><span class="o">.</span><span class="nv">makeRustPlatform</span>
<span class="p">{</span>
<span class="nv">rustc</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="nv">cargo</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>This gets us a new error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update <span class="o">&&</span> nix build
<span class="go"> Updating crates.io index
Updating git repository `https://github.com/esp-rs/esp-wifi/`
</span><span class="gp">error: builder for '/nix/store/szli9axz1hgswa0b9k3327pl506hmhi6-http-client-riscv32-none-elf.drv' failed with exit code 101;</span><span class="w">
</span><span class="go"> last 10 log lines:
</span><span class="gp"> ></span><span class="w"> </span>error[E0432]: unresolved import <span class="sb">`</span>core::sync::atomic::AtomicUsize<span class="sb">`</span>
<span class="gp"> ></span><span class="w"> </span><span class="nt">--</span><span class="o">></span> /private/tmp/nix-build-http-client-riscv32-none-elf.drv-0/cargo-vendor-dir/atomic-waker-1.1.2/src/lib.rs:27:5
<span class="gp"> ></span><span class="w"> </span>|
<span class="gp"> ></span><span class="w"> </span>27 | use core::sync::atomic::AtomicUsize<span class="p">;</span>
<span class="gp"> ></span><span class="w"> </span>| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no <span class="sb">`</span>AtomicUsize<span class="sb">`</span> <span class="k">in</span> <span class="sb">`</span><span class="nb">sync</span>::atomic<span class="sb">`</span>
<span class="gp"> ></span><span class="w">
</span><span class="gp"> ></span><span class="w"> </span>Compiling managed v0.8.0
<span class="gp"> ></span><span class="w"> </span>For more information about this error, try <span class="sb">`</span>rustc <span class="nt">--explain</span> E0432<span class="sb">`</span><span class="nb">.</span>
<span class="gp"> ></span><span class="w"> </span>error: could not compile <span class="sb">`</span>atomic-waker<span class="sb">`</span> <span class="o">(</span>lib<span class="o">)</span> due to previous error
<span class="gp"> ></span><span class="w"> </span>warning: build failed, waiting <span class="k">for </span>other <span class="nb">jobs </span>to finish...
<span class="go"> For full logs, run 'nix log /nix/store/szli9axz1hgswa0b9k3327pl506hmhi6-http-client-riscv32-none-elf.drv'.
</span></code></pre></div></div>
<p>At this point I did a lot of reading about nix cross-compiling, including some
<a href="https://github.com/oxalica/rust-overlay/pull/58#issuecomment-970100892">excellent
comments</a>
and <a href="https://github.com/oxalica/rust-overlay/blob/master/examples/cross-aarch64/shell.nix">a few
examples</a>
by Oxalica, but there were few results for this <em>exact</em> error. <a href="https://github.com/espressif/rust-esp32-example/issues/3">This
thread</a> is relevant
and has some notes from one of the main esp-rs developers (@MabezDev on
GitHub), but seemed to be about compiling <code class="language-plaintext highlighter-rouge">std</code>, and this is a <code class="language-plaintext highlighter-rouge">no_std</code>
project. Taking a second look at the log output (again split into separate
lines for readability):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>++ env \
CC_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/cc \
CXX_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/c++ \
CC_riscv32imc-unknown-none-elf=/nix/store/lasmnmwpszbyv8xambkxyhyvwi3164w2-riscv32-none-elf-stage-final-gcc-wrapper-12.2.0/bin/riscv32-none-elf-cc \
CXX_riscv32imc-unknown-none-elf=/nix/store/lasmnmwpszbyv8xambkxyhyvwi3164w2-riscv32-none-elf-stage-final-gcc-wrapper-12.2.0/bin/riscv32-none-elf-c++ \
cargo build \
-j 8 \
--target riscv32imc-unknown-none-elf \
--frozen \
--release
</code></pre></div></div>
<p>It looks like the <code class="language-plaintext highlighter-rouge">target</code> is being set correctly, but in true <code class="language-plaintext highlighter-rouge">nix</code> cross
compilation fashion it looked like it might also be using a cross-compiled
version of the compiler (based on the <code class="language-plaintext highlighter-rouge">CC_*</code> variables). That seems
unnecessary, since we’ve already proven that an <code class="language-plaintext highlighter-rouge">aarch64-darwin</code> compiled
toolchain can do the heavy lifting of cross compilation, we’re just trying to
set the desired <code class="language-plaintext highlighter-rouge">--target</code>.</p>
<p>I browsed <code class="language-plaintext highlighter-rouge">nixpkgs</code> until I found <a href="https://github.com/NixOS/nixpkgs/blob/ef8f77d411558688ffa2ec7a11671139f50a7913/pkgs/build-support/rust/hooks/cargo-build-hook.sh#L39">where it seems to be setting <code class="language-plaintext highlighter-rouge">--target</code> in
the <code class="language-plaintext highlighter-rouge">cargo</code> call</a>, which sets it to <code class="language-plaintext highlighter-rouge">rustTargetPlatformSpec</code>. This, in
turn, is being set to <code class="language-plaintext highlighter-rouge">rust.toRustTargetSpec stdenv.hostPlatform</code> <a href="https://github.com/NixOS/nixpkgs/blob/5e05bf13858a4240d99190b9fd651a25b696c651/pkgs/build-support/rust/hooks/default.nix#L29">here</a>.
<code class="language-plaintext highlighter-rouge">toRustTargetSpec</code> is defined <a href="https://github.com/NixOS/nixpkgs/blob/5e05bf13858a4240d99190b9fd651a25b696c651/pkgs/build-support/rust/lib/default.nix#L57">here</a> as the following:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">toRustTarget</span> <span class="o">=</span> <span class="nv">platform</span><span class="p">:</span> <span class="kd">let</span>
<span class="kn">inherit</span> <span class="p">(</span><span class="nv">platform</span><span class="o">.</span><span class="nv">parsed</span><span class="p">)</span> <span class="nv">cpu</span> <span class="nv">kernel</span> <span class="nv">abi</span><span class="p">;</span>
<span class="nv">cpu_</span> <span class="o">=</span> <span class="nv">platform</span><span class="o">.</span><span class="nv">rustc</span><span class="o">.</span><span class="nv">platform</span><span class="o">.</span><span class="nv">arch</span> <span class="nv">or</span> <span class="p">{</span>
<span class="s2">"armv7a"</span> <span class="o">=</span> <span class="s2">"armv7"</span><span class="p">;</span>
<span class="s2">"armv7l"</span> <span class="o">=</span> <span class="s2">"armv7"</span><span class="p">;</span>
<span class="s2">"armv6l"</span> <span class="o">=</span> <span class="s2">"arm"</span><span class="p">;</span>
<span class="s2">"armv5tel"</span> <span class="o">=</span> <span class="s2">"armv5te"</span><span class="p">;</span>
<span class="s2">"riscv64"</span> <span class="o">=</span> <span class="s2">"riscv64gc"</span><span class="p">;</span>
<span class="p">}</span><span class="o">.</span><span class="p">${</span><span class="nv">cpu</span><span class="o">.</span><span class="nv">name</span><span class="p">}</span> <span class="nv">or</span> <span class="nv">cpu</span><span class="o">.</span><span class="nv">name</span><span class="p">;</span>
<span class="nv">vendor_</span> <span class="o">=</span> <span class="nv">toTargetVendor</span> <span class="nv">platform</span><span class="p">;</span>
<span class="kn">in</span> <span class="nv">platform</span><span class="o">.</span><span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span>
<span class="nv">or</span> <span class="s2">"</span><span class="si">${</span><span class="nv">cpu_</span><span class="si">}</span><span class="s2">-</span><span class="si">${</span><span class="nv">vendor_</span><span class="si">}</span><span class="s2">-</span><span class="si">${</span><span class="nv">kernel</span><span class="o">.</span><span class="nv">name</span><span class="si">}${</span><span class="nv">lib</span><span class="o">.</span><span class="nv">optionalString</span> <span class="p">(</span><span class="nv">abi</span><span class="o">.</span><span class="nv">name</span> <span class="o">!=</span> <span class="s2">"unknown"</span><span class="p">)</span> <span class="s2">"-</span><span class="si">${</span><span class="nv">abi</span><span class="o">.</span><span class="nv">name</span><span class="si">}</span><span class="s2">"</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
<span class="nv">toRustTargetSpec</span> <span class="o">=</span> <span class="nv">platform</span><span class="p">:</span>
<span class="k">if</span> <span class="nv">platform</span> <span class="o">?</span> <span class="nv">rustc</span><span class="o">.</span><span class="nv">platform</span>
<span class="k">then</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">toFile</span> <span class="p">(</span><span class="nv">toRustTarget</span> <span class="nv">platform</span> <span class="o">+</span> <span class="s2">".json"</span><span class="p">)</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">toJSON</span> <span class="nv">platform</span><span class="o">.</span><span class="nv">rustc</span><span class="o">.</span><span class="nv">platform</span><span class="p">)</span>
<span class="k">else</span> <span class="nv">toRustTarget</span> <span class="nv">platform</span><span class="p">;</span>
</code></pre></div></div>
<p>So for the case at hand, I read this as:</p>
<ol>
<li>Does <code class="language-plaintext highlighter-rouge">pkgs.stdenv.hostPlatform</code> have a <code class="language-plaintext highlighter-rouge">rustc.platform</code> attribute? No
(otherwise would make a <code class="language-plaintext highlighter-rouge">.json</code> target from the platform).</li>
<li>Therefore, use <code class="language-plaintext highlighter-rouge">toRustTarget pkgs.stdenv.hostPlatform</code>.</li>
<li>Continuing in <code class="language-plaintext highlighter-rouge">toRustTarget</code>, does <code class="language-plaintext highlighter-rouge">pkgs.stdenv.hostPlatform</code> have a
<code class="language-plaintext highlighter-rouge">rustc.config</code> attribute? Yes.</li>
<li>Therefore, use <code class="language-plaintext highlighter-rouge">rustc.config</code> (otherwise would construct a string from
<code class="language-plaintext highlighter-rouge">cpu</code>, <code class="language-plaintext highlighter-rouge">vendor</code>, <code class="language-plaintext highlighter-rouge">abi</code>, etc.).</li>
</ol>
<p>So it looks like <code class="language-plaintext highlighter-rouge">rust.config</code> may be all that’s required to set the
<code class="language-plaintext highlighter-rouge">--target</code>. Let’s try the following:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">rustPlatform</span> <span class="o">=</span> <span class="kd">let</span>
<span class="nv">pkgsCross</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"riscv32imc-unknown-none-elf"</span><span class="p">;</span>
<span class="p">};</span>
<span class="kn">in</span>
<span class="nv">pkgsCross</span><span class="o">.</span><span class="nv">makeRustPlatform</span>
<span class="p">{</span>
<span class="nv">rustc</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="nv">cargo</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update <span class="o">&&</span> nix build
<span class="go"> Updating crates.io index
Updating git repository `https://github.com/esp-rs/esp-wifi/`
</span><span class="gp">error: builder for '/nix/store/9w5m6wb7di7br2ar3wy5a9kcrc6dizj3-http-client.drv' failed with exit code 101;</span><span class="w">
</span><span class="go"> last 10 log lines:
</span><span class="gp"> ></span><span class="w"> </span>Compiling enumset v1.1.2
<span class="gp"> ></span><span class="w"> </span>Compiling managed v0.8.0
<span class="gp"> ></span><span class="w"> </span>Compiling atomic-waker v1.1.2
<span class="gp"> ></span><span class="w"> </span>Compiling bitflags v1.3.2
<span class="gp"> ></span><span class="w"> </span>Compiling no-std-net v0.5.0
<span class="gp"> ></span><span class="w"> </span>LLVM ERROR: Global variable <span class="s1">'_start_rust'</span> has an invalid section specifier <span class="s1">'.init.rust'</span>: mach-o section specifier requires a segment and section separated by a comma.
<span class="gp"> ></span><span class="w"> </span>error: could not compile <span class="sb">`</span>esp-riscv-rt<span class="sb">`</span> <span class="o">(</span>lib<span class="o">)</span>
<span class="gp"> ></span><span class="w"> </span>warning: build failed, waiting <span class="k">for </span>other <span class="nb">jobs </span>to finish...
<span class="gp"> ></span><span class="w"> </span>LLVM ERROR: Global variable <span class="s1">'__EXTERNAL_INTERRUPTS'</span> has an invalid section specifier <span class="s1">'.trap.rodata'</span>: mach-o section specifier requires a segment and section separated by a comma.
<span class="gp"> ></span><span class="w"> </span>error: could not compile <span class="sb">`</span>esp32c3<span class="sb">`</span> <span class="o">(</span>lib<span class="o">)</span>
<span class="go"> For full logs, run 'nix log /nix/store/9w5m6wb7di7br2ar3wy5a9kcrc6dizj3-http-client.drv'.
</span></code></pre></div></div>
<p>Well, now we’re back to an error we’ve seen before, when we were compiling for
the wrong architecture. Sure enough, glancing through the log, we’re back to
<code class="language-plaintext highlighter-rouge">--target aarch64-apple-darwin</code> – a step in the wrong direction. Let’s put the
<code class="language-plaintext highlighter-rouge">crossSystem</code> back:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">rustPlatform</span> <span class="o">=</span> <span class="kd">let</span>
<span class="nv">pkgsCross</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">crossSystem</span> <span class="o">=</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"riscv32imc-unknown-none-elf"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kn">in</span>
<span class="nv">pkgsCross</span><span class="o">.</span><span class="nv">makeRustPlatform</span>
<span class="p">{</span>
<span class="nv">rustc</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="nv">cargo</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>This gets us to our next error. Progress!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update <span class="o">&&</span> nix build
<span class="go"> Updating crates.io index
Updating git repository `https://github.com/esp-rs/esp-wifi/`
</span><span class="gp">error: builder for '/nix/store/2fp9fkha1qjnand2xwrrair8jg86ml65-http-client-aarch64-apple-darwin.drv' failed with exit code 101;</span><span class="w">
</span><span class="go"> last 10 log lines:
</span><span class="gp"> ></span><span class="w"> </span>error: environment variable <span class="sb">`</span>PASSWORD<span class="sb">`</span> not defined at compile <span class="nb">time</span>
<span class="gp"> ></span><span class="w"> </span><span class="nt">--</span><span class="o">></span> src/main.rs:26:24
<span class="gp"> ></span><span class="w"> </span>|
<span class="gp"> ></span><span class="w"> </span>26 | const PASSWORD: &str <span class="o">=</span> <span class="nb">env</span><span class="o">!(</span><span class="s2">"PASSWORD"</span><span class="o">)</span><span class="p">;</span>
<span class="gp"> ></span><span class="w"> </span>| ^^^^^^^^^^^^^^^^
<span class="gp"> ></span><span class="w"> </span>|
<span class="gp"> ></span><span class="w"> </span><span class="o">=</span> <span class="nb">help</span>: use <span class="sb">`</span>std::env::var<span class="o">(</span><span class="s2">"PASSWORD"</span><span class="o">)</span><span class="sb">`</span> to <span class="nb">read </span>the variable at run <span class="nb">time</span>
<span class="gp"> ></span><span class="w"> </span><span class="o">=</span> note: this error originates <span class="k">in </span>the macro <span class="sb">`</span><span class="nb">env</span><span class="sb">`</span> <span class="o">(</span><span class="k">in </span>Nightly builds, run with <span class="nt">-Z</span> macro-backtrace <span class="k">for </span>more info<span class="o">)</span>
<span class="gp"> ></span><span class="w">
</span><span class="gp"> ></span><span class="w"> </span>error: could not compile <span class="sb">`</span>http-client<span class="sb">`</span> <span class="o">(</span>bin <span class="s2">"http-client"</span><span class="o">)</span> due to 2 previous errors
<span class="go"> For full logs, run 'nix log /nix/store/2fp9fkha1qjnand2xwrrair8jg86ml65-http-client-aarch64-apple-darwin.drv'.
</span></code></pre></div></div>
<p>Looking through the build logs, the <code class="language-plaintext highlighter-rouge">cargo build</code> seems to be doing what we had
hoped; I see an <code class="language-plaintext highlighter-rouge">aarch64-darwin</code> toolchain and a <code class="language-plaintext highlighter-rouge">riscv32</code> target:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>++ env \
CC_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/cc \
CXX_aarch64-apple-darwin=/nix/store/p72lcp92djj8xpdjm27rjrrxznjjgvyi-clang-wrapper-11.1.0/bin/c++ \
CC_riscv32imc-unknown-none-elf=/nix/store/py4adxsy9vzdgb7qlqv570wdc9rsayhf-aarch64-apple-darwin-clang-wrapper-11.1.0/bin/aarch64-apple-darwin-cc \
CXX_riscv32imc-unknown-none-elf=/nix/store/py4adxsy9vzdgb7qlqv570wdc9rsayhf-aarch64-apple-darwin-clang-wrapper-11.1.0/bin/aarch64-apple-darwin-c++ \
cargo build \
-j 8 \
--target riscv32imc-unknown-none-elf \
--frozen \
--release
</code></pre></div></div>
<p>The new error is one I actually understand (for once): the <code class="language-plaintext highlighter-rouge">esp-rs</code> authors
have the project configured to read the wifi credentials from the build
environment at compile time with the <code class="language-plaintext highlighter-rouge">env!</code> macro. When comiling with <code class="language-plaintext highlighter-rouge">cargo</code>,
we can just export these in the build environment, but <code class="language-plaintext highlighter-rouge">nix build</code>
intentionally cleans impurities (like the build environment), so it won’t be
able to see these by default. I don’t know of any way to configure the runtime
environment on the esp32, so I don’t think we can use the compiler’s suggestion
(using <code class="language-plaintext highlighter-rouge">std::env::var</code>). Instead, we know that <code class="language-plaintext highlighter-rouge">nix</code> will generally pass along
values that are set in a <code class="language-plaintext highlighter-rouge">mkDerivation</code> call as environment variables, so we’ll
just try setting some dummy values in <code class="language-plaintext highlighter-rouge">default.nix</code>, to see if that allows the
build to proceed:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSID = "foo";
PASSWORD = "bar";
</code></pre></div></div>
<p><strong>NB</strong>: Like basically everything else in <code class="language-plaintext highlighter-rouge">nix</code>, these will get built into a
derivation in <code class="language-plaintext highlighter-rouge">/nix/store</code> that is <strong>world readable</strong>. Passwords and other
secrets in nix are <a href="https://nixos.wiki/wiki/Comparison_of_secret_managing_schemes">an entire
topic</a> on its
own. For the moment, just know that this route of setting the wifi credentails
will make them discoverable by anyone with read access to your device. I
believe this would still be the case if using <code class="language-plaintext highlighter-rouge">builtins.getEnv</code> + <code class="language-plaintext highlighter-rouge">--impure</code>
instead of building it into the derivation.</p>
<p>That was a pretty easy fix, and successfully leads us to our next error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update <span class="o">&&</span> nix build
<span class="go"> Updating crates.io index
Updating git repository `https://github.com/esp-rs/esp-wifi/`
</span><span class="gp">error: builder for '/nix/store/p2gp7hl5xnddn3w8snn6dfpbzrj9dyfd-http-client-aarch64-apple-darwin.drv' failed with exit code 101;</span><span class="w">
</span><span class="go"> last 10 log lines:
</span><span class="gp"> ></span><span class="w"> </span><span class="o">=</span> note: second definition <span class="k">in</span> <span class="sb">`</span>core<span class="sb">`</span> loaded from /nix/store/cjc6j5r11wqmdkp6f5mcbrzb938rg9dw-rust-std-1.74.0-nightly-2023-08-23-riscv32imc-unknown-none-elf/lib/rustlib/riscv32imc-unknown-none-elf/lib/libcore-68e03c5be2ffebdc.rlib
<span class="gp"> ></span><span class="w">
</span><span class="gp"> ></span><span class="w"> </span>error[E0152]: duplicate lang item <span class="k">in </span>crate <span class="sb">`</span>core<span class="sb">`</span> <span class="o">(</span>which <span class="sb">`</span>alloc<span class="sb">`</span> depends on<span class="o">)</span>: <span class="sb">`</span>CStr<span class="sb">`</span><span class="nb">.</span>
<span class="gp"> ></span><span class="w"> </span>|
<span class="gp"> ></span><span class="w"> </span><span class="o">=</span> note: the lang item is first defined <span class="k">in </span>crate <span class="sb">`</span>core<span class="sb">`</span> <span class="o">(</span>which <span class="sb">`</span>twox_hash<span class="sb">`</span> depends on<span class="o">)</span>
<span class="gp"> ></span><span class="w"> </span><span class="o">=</span> note: first definition <span class="k">in</span> <span class="sb">`</span>core<span class="sb">`</span> loaded from /private/tmp/nix-build-http-client-aarch64-apple-darwin.drv-0/source/target/riscv32imc-unknown-none-elf/release/deps/libcore-dc12a78182d2c0a4.rmeta
<span class="gp"> ></span><span class="w"> </span><span class="o">=</span> note: second definition <span class="k">in</span> <span class="sb">`</span>core<span class="sb">`</span> loaded from /nix/store/cjc6j5r11wqmdkp6f5mcbrzb938rg9dw-rust-std-1.74.0-nightly-2023-08-23-riscv32imc-unknown-none-elf/lib/rustlib/riscv32imc-unknown-none-elf/lib/libcore-68e03c5be2ffebdc.rlib
<span class="gp"> ></span><span class="w">
</span><span class="gp"> ></span><span class="w"> </span>For more information about this error, try <span class="sb">`</span>rustc <span class="nt">--explain</span> E0152<span class="sb">`</span><span class="nb">.</span>
<span class="gp"> ></span><span class="w"> </span>error: could not compile <span class="sb">`</span>twox-hash<span class="sb">`</span> <span class="o">(</span>lib<span class="o">)</span> due to 121 previous errors
<span class="go"> For full logs, run 'nix log /nix/store/p2gp7hl5xnddn3w8snn6dfpbzrj9dyfd-http-client-aarch64-apple-darwin.drv'.
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">duplicate lang item in crate `core`</code> – what’s that all about? I found a few
GitHub issues and SO posts that didn’t give me much insight (or hope), but
you’re welcome to peruse:</p>
<ul>
<li><a href="https://github.com/rust-lang/wg-cargo-std-aware/issues/56">https://github.com/rust-lang/wg-cargo-std-aware/issues/56</a></li>
<li><a href="https://github.com/rust-lang/rust/issues/115963">https://github.com/rust-lang/rust/issues/115963</a></li>
<li><a href="https://stackoverflow.com/questions/59388952/how-to-solve-substrate-duplicate-lang-item-in-crate-std-which-myexternalcra">https://stackoverflow.com/questions/59388952/how-to-solve-substrate-duplicate-lang-item-in-crate-std-which-myexternalcra</a></li>
</ul>
<p>Thankfully, I eventually found <a href="https://stackoverflow.com/questions/63961435/cargo-test-fails-for-arduino-targets-with-duplicate-lang-item-in-crate">this SO
post</a>
which linked to <a href="https://github.com/Rahix/avr-hal/issues/71#issuecomment-695108184">this
comment</a>,
talking about how <code class="language-plaintext highlighter-rouge">cargo test</code> for embedded targets perhaps didn’t make much
sense (yet). By default, <code class="language-plaintext highlighter-rouge">nix</code> generally tries to test everything it can prior
to saying that “everything compiled fine”, so it would make sense that perhaps
it was running <code class="language-plaintext highlighter-rouge">cargo test</code> and having trouble there. Sure enough, digging
deeper through the log:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>++ cargo test -j 8 --release --target riscv32imc-unknown-none-elf --frozen -- --test-threads=8
Compiling stable_deref_trait v1.2.0
Compiling thiserror-core v1.0.38
Compiling crc32fast v1.3.2
Compiling thiserror-core-impl v1.0.38
Compiling static_assertions v1.1.0
Compiling adler v1.0.2
Compiling memchr v2.5.0
Compiling cpp_demangle v0.4.3
error[E0463]: can't find crate for `std`
|
= note: the `riscv32imc-unknown-none-elf` target may not support the standard library
= note: `std` is required by `stable_deref_trait` because it does not declare `#![no_std]`
= help: consider building the standard library from source with `cargo build -Zbuild-std`
</code></pre></div></div>
<p>What happens if we just disable the tests, by adding <code class="language-plaintext highlighter-rouge">doCheck = false;</code> to
<code class="language-plaintext highlighter-rouge">default.nix</code>?</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update <span class="o">&&</span> nix build
<span class="go"> Updating crates.io index
Updating git repository `https://github.com/esp-rs/esp-wifi/`
</span><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$?</span>
<span class="go">0
</span><span class="gp">$</span><span class="w"> </span>file result/bin/http-client
<span class="go">result/bin/http-client: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
</span></code></pre></div></div>
<p>Holy cow, a successful build. But does it work?</p>
<p>Running <code class="language-plaintext highlighter-rouge">espflash flash</code> seems to connect and tell us which serial port to use,
but needs us to specify the firmware file:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">--command</span> espflash flash
<span class="go">New version of espflash is available: v2.0.1
Serial port: /dev/tty.usbserial-1110
Connecting...
Chip type: ESP32-C3 (revision 3)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi
MAC address: 84:f7:03:39:f1:cc
Error:
× No such file or directory (os error 2)
</span></code></pre></div></div>
<p>Adding the file and specifying <code class="language-plaintext highlighter-rouge">--monitor</code> seems to work, and gives us some
output that confirms it’s running!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">--command</span> espflash <span class="nt">--monitor</span> ./result/bin/http-client
<span class="go">New version of espflash is available: v2.0.1
Serial port: /dev/tty.usbserial-1110
Connecting...
Chip type: ESP32-C3 (revision 3)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi
MAC address: 84:f7:03:39:f1:cc
App/part. size: 516368/4128768 bytes, 12.51%
</span><span class="gp">[00:00:01] #</span><span class="c">####################################### 12/12 segment 0x0</span>
<span class="gp">[00:00:00] #</span><span class="c">####################################### 1/1 segment 0x8000</span>
<span class="gp">[00:00:31] #</span><span class="c">####################################### 269/269 segment 0x10000</span>
<span class="go">Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd6100,len:0x172c
load:0x403ce000,len:0x928
0x403ce000 - .L17
at ??:??
load:0x403d0000,len:0x2ce0
0x403d0000 - .L17
at ??:??
entry 0x403ce000
0x403ce000 - .L17
at ??:??
I (30) boot: ESP-IDF v4.4-dev-2825-gb63ec47238 2nd stage bootloader
I (30) boot: compile time 12:10:40
I (30) boot: chip revision: 3
I (33) boot_comm: chip revision: 3, min. bootloader chip revision: 0
I (41) boot.esp32c3: SPI Speed : 80MHz
I (45) boot.esp32c3: SPI Mode : DIO
I (50) boot.esp32c3: SPI Flash Size : 4MB
I (55) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
</span><span class="gp">I (64) boot: #</span><span class="c"># Label Usage Type ST Offset Length</span>
<span class="go">I (71) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (78) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (86) boot: 2 factory factory app 00 00 00010000 003f0000
I (93) boot: End of partition table
I (98) boot_comm: chip revision: 3, min. application chip revision: 0
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c060020 size=125f8h ( 75256) map
I (125) esp_image: segment 1: paddr=00022620 vaddr=3fc84588 size=01214h ( 4628) load
I (126) esp_image: segment 2: paddr=0002383c vaddr=3fc9d958 size=00168h ( 360) load
I (130) esp_image: segment 3: paddr=000239ac vaddr=40380000 size=04584h ( 17796) load
I (142) esp_image: segment 4: paddr=00027f38 vaddr=00000000 size=080e0h ( 32992)
I (152) esp_image: segment 5: paddr=00030020 vaddr=42000020 size=5e0c0h (385216) map
I (215) boot: Loaded app from partition at offset 0x10000
I (215) boot: Disabling RNG early entropy source...
Wi-Fi set_configuration returned Ok(())
Is wifi started: Ok(true)
Start Wifi Scan
AccessPointInfo { ssid: "REDACTED", bssid: [...], channel: 6, secondary_channel: None, signal_strength: -43, protocols: EnumSet(), auth_method: WPAWPA2Personal }
AccessPointInfo { ssid: "REDACTED2", bssid: [...], channel: 6, secondary_channel: None, signal_strength: -85, protocols: EnumSet(), auth_method: None }
Ok(EnumSet(Client | AccessPoint))
Wi-Fi connect: Ok(())
Wait to get connected
Disconnected
</span></code></pre></div></div>
<p>Finally, we can add one more convenience to our <code class="language-plaintext highlighter-rouge">flake.nix</code> by moving our
definition of <code class="language-plaintext highlighter-rouge">name</code> up a layer and defining a default <code class="language-plaintext highlighter-rouge">app</code> that does the
flashing:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">apps</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="kd">let</span>
<span class="nv">flash</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">writeShellApplication</span> <span class="p">{</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"flash-</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
<span class="nv">runtimeInputs</span> <span class="o">=</span> <span class="p">[</span><span class="nv">pkgs</span><span class="o">.</span><span class="nv">cargo-espflash</span><span class="p">];</span>
<span class="nv">text</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2"> espflash --monitor </span><span class="si">${</span><span class="nv">self</span><span class="o">.</span><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span><span class="si">}</span><span class="s2">/bin/</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="err">
</span><span class="s2"> ''</span><span class="p">;</span>
<span class="p">};</span>
<span class="kn">in</span> <span class="p">{</span>
<span class="nv">type</span> <span class="o">=</span> <span class="s2">"app"</span><span class="p">;</span>
<span class="nv">program</span> <span class="o">=</span> <span class="s2">"</span><span class="si">${</span><span class="nv">flash</span><span class="si">}</span><span class="s2">/bin/flash-</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>With this in place, a simple <code class="language-plaintext highlighter-rouge">nix run</code> builds and flashes! (For the below, I’ve
put proper values into the <code class="language-plaintext highlighter-rouge">SSID</code> and <code class="language-plaintext highlighter-rouge">PASSWORD</code>.)</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix run
<span class="go">New version of espflash is available: v2.0.1
Serial port: /dev/tty.usbserial-1110
Connecting...
Chip type: ESP32-C3 (revision 3)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi
MAC address: 84:f7:03:39:f1:cc
App/part. size: 516448/4128768 bytes, 12.51%
</span><span class="gp">[00:00:01] #</span><span class="c">####################################### 12/12 segment 0x0</span>
<span class="gp">[00:00:00] #</span><span class="c">####################################### 1/1 segment 0x8000</span>
<span class="gp">[00:00:32] #</span><span class="c">####################################### 269/269 segment 0x10000</span>
<span class="go">Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd6100,len:0x172c
load:0x403ce000,len:0x928
0x403ce000 - .L17
at ??:??
load:0x403d0000,len:0x2ce0
0x403d0000 - .L17
at ??:??
entry 0x403ce000
0x403ce000 - .L17
at ??:??
I (30) boot: ESP-IDF v4.4-dev-2825-gb63ec47238 2nd stage bootloader
I (30) boot: compile time 12:10:40
I (30) boot: chip revision: 3
I (33) boot_comm: chip revision: 3, min. bootloader chip revision: 0
I (41) boot.esp32c3: SPI Speed : 80MHz
I (45) boot.esp32c3: SPI Mode : DIO
I (50) boot.esp32c3: SPI Flash Size : 4MB
I (55) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
</span><span class="gp">I (64) boot: #</span><span class="c"># Label Usage Type ST Offset Length</span>
<span class="go">I (71) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (78) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (86) boot: 2 factory factory app 00 00 00010000 003f0000
I (93) boot: End of partition table
I (98) boot_comm: chip revision: 3, min. application chip revision: 0
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c060020 size=125f8h ( 75256) map
I (125) esp_image: segment 1: paddr=00022620 vaddr=3fc84588 size=01214h ( 4628) load
I (126) esp_image: segment 2: paddr=0002383c vaddr=3fc9d958 size=00168h ( 360) load
I (130) esp_image: segment 3: paddr=000239ac vaddr=40380000 size=04584h ( 17796) load
I (142) esp_image: segment 4: paddr=00027f38 vaddr=00000000 size=080e0h ( 32992)
I (152) esp_image: segment 5: paddr=00030020 vaddr=42000020 size=5e11ch (385308) map
I (215) boot: Loaded app from partition at offset 0x10000
I (215) boot: Disabling RNG early entropy source...
Wi-Fi set_configuration returned Ok(())
Is wifi started: Ok(true)
Start Wifi Scan
AccessPointInfo { ssid: "REDACTED1", bssid: [...], channel: 6, secondary_channel: None, signal_strength: -39, protocols: EnumSet(), auth_method: WPA2Personal }
AccessPointInfo { ssid: "REDACTED2", bssid: [...], channel: 6, secondary_channel: None, signal_strength: -39, protocols: EnumSet(), auth_method: WPAWPA2Personal }
AccessPointInfo { ssid: "REDACTED3", bssid: [...], channel: 11, secondary_channel: None, signal_strength: -81, protocols: EnumSet(), auth_method: WPA2Personal }
Ok(EnumSet(Client | AccessPoint))
Wi-Fi connect: Ok(())
Wait to get connected
Ok(true)
Wait to get an ip address
got ip Ok(IpInfo { ip: 192.168.1.123, subnet: Subnet { gateway: 192.168.1.4, mask: Mask(24) }, dns: Some(192.168.1.4), secondary_dns: None })
Start busy loop on main
Making HTTP request
HTTP/1.0 200 OK
X-Cloud-Trace-Context: b3a2f08c40d782146364b65262968b33
Server: Google Frontend
Content-Length: 335
Date: Tue, 26 Sep 2023 16:49:18 GMT
Expires: Tue, 26 Sep 2023 16:59:18 GMT
Cache-Control: public, max-age=600
ETag: "uJJDjQ"
Content-Type: text/html
Age: 0
</span><span class="gp"><!DOCTYPE html></span><span class="w">
</span><span class="gp"><html></span><span class="w">
</span><span class="gp"><head></span><span class="w">
</span><span class="gp"> <title></span>Nothing here</title>
<span class="gp"></head></span><span class="w">
</span><span class="gp"><body></span><span class="w">
</span><span class="gp"><pre></span><span class="w">
</span><span class="go"> __________________________
</span><span class="gp"> < Hello fellow Rustaceans! ></span><span class="w">
</span><span class="go"> --------------------------
\
\
_~^~^~_
\) / o o \ (/
'_ - _'
/ '-----' \
</span><span class="gp"></pre></span><span class="w">
</span><span class="gp"></body></span><span class="w">
</span><span class="gp"></html></span><span class="w">
</span></code></pre></div></div>
<p>Phew, well that was a lot of work, but with any luck it’s work we should only
have to do <em>once</em>, and going forward the same project should – theoretically,
if done from the same archtecture – continue to compile and continue to flash,
no matter how much time passes before returning to tinker.</p>
<p>As I’m sure is obvious, I’m no expert in Rust, embedded systems, electronics,
or nix, so if you have suggestions for improvement, I’d love to hear about it
in the comments section.</p>
<p>I’m not going to bother making a GitHub repo for these, since they require
pinning specific versions of so many dependencies (which will likely be
outdated or unrelated to your specific project), but below you can reference
the final version of the relevant files. That’s all for now!</p>
<p><code class="language-plaintext highlighter-rouge">rust-toolchain.toml</code>:</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[toolchain]</span>
<span class="py">channel</span> <span class="p">=</span> <span class="s">"nightly-2023-08-23"</span>
<span class="py">components</span> <span class="p">=</span> <span class="nn">["rust-src"]</span>
<span class="py">targets</span> <span class="p">=</span> <span class="nn">["riscv32imc-unknown-none-elf"]</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Cargo.toml</code>:</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"http-client"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">authors</span> <span class="p">=</span> <span class="p">[</span><span class="s">"Sergio Gasquez <[email protected]>"</span><span class="p">]</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2021"</span>
<span class="py">license</span> <span class="p">=</span> <span class="s">"MIT OR Apache-2.0"</span>
<span class="c"># TODO: Explain</span>
<span class="py">resolver</span> <span class="p">=</span> <span class="s">"2"</span>
<span class="c"># TODO: Explain</span>
<span class="nn">[profile.release]</span>
<span class="c"># Explicitly disable LTO which the Xtensa codegen backend has issues</span>
<span class="py">lto</span> <span class="p">=</span> <span class="s">"off"</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="mi">3</span>
<span class="nn">[profile.dev]</span>
<span class="py">lto</span> <span class="p">=</span> <span class="s">"off"</span>
<span class="nn">[dependencies]</span>
<span class="nn">hal</span> <span class="o">=</span> <span class="p">{</span> <span class="py">package</span> <span class="p">=</span> <span class="s">"esp32c3-hal"</span><span class="p">,</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.12.0"</span> <span class="p">}</span>
<span class="nn">esp-backtrace</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.8.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"esp32c3"</span><span class="p">,</span> <span class="s">"panic-handler"</span><span class="p">,</span> <span class="s">"exception-handler"</span><span class="p">,</span> <span class="s">"print-uart"</span><span class="p">]</span> <span class="p">}</span>
<span class="nn">esp-println</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.6.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"esp32c3"</span><span class="p">,</span> <span class="s">"log"</span><span class="p">]</span> <span class="p">}</span>
<span class="nn">esp-wifi</span> <span class="o">=</span> <span class="p">{</span> <span class="py">git</span> <span class="p">=</span> <span class="s">"https://github.com/esp-rs/esp-wifi/"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"esp32c3"</span><span class="p">,</span> <span class="s">"wifi-logs"</span><span class="p">,</span> <span class="s">"wifi"</span><span class="p">],</span> <span class="py">rev</span> <span class="p">=</span> <span class="s">"e7140fd35852dadcd1df7592dc149e876256348f"</span> <span class="p">}</span>
<span class="nn">smoltcp</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.10.0"</span><span class="p">,</span> <span class="py">default-features</span><span class="p">=</span><span class="kc">false</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"proto-igmp"</span><span class="p">,</span> <span class="s">"proto-ipv4"</span><span class="p">,</span> <span class="s">"socket-tcp"</span><span class="p">,</span> <span class="s">"socket-icmp"</span><span class="p">,</span> <span class="s">"socket-udp"</span><span class="p">,</span> <span class="s">"medium-ethernet"</span><span class="p">,</span> <span class="s">"proto-dhcpv4"</span><span class="p">,</span> <span class="s">"socket-raw"</span><span class="p">,</span> <span class="s">"socket-dhcpv4"</span><span class="p">]</span> <span class="p">}</span>
<span class="nn">embedded-svc</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.25.0"</span><span class="p">,</span> <span class="py">default-features</span> <span class="p">=</span> <span class="kc">false</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[]</span> <span class="p">}</span>
<span class="py">embedded-io</span> <span class="p">=</span> <span class="s">"0.4.0"</span>
<span class="nn">heapless</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.7.14"</span><span class="p">,</span> <span class="py">default-features</span> <span class="p">=</span> <span class="kc">false</span> <span class="p">}</span>
<span class="nn">[dev-dependencies]</span>
<span class="py">compiler_builtins</span> <span class="p">=</span> <span class="py">"</span><span class="p">=</span><span class="mf">0.1</span><span class="err">.</span><span class="mi">100</span><span class="s">"</span><span class="err">
</span><span class="py">addr2line</span> <span class="p">=</span> <span class="s">"0.21.0"</span>
<span class="py">allocator-api2</span> <span class="p">=</span> <span class="py">"</span><span class="p">=</span><span class="mf">0.2</span><span class="err">.</span><span class="mi">15</span><span class="s">"</span><span class="err">
</span><span class="py">dlmalloc</span> <span class="p">=</span> <span class="s">"0.2.4"</span>
<span class="py">fortanix-sgx-abi</span> <span class="p">=</span> <span class="s">"0.5.0"</span>
<span class="py">getopts</span> <span class="p">=</span> <span class="s">"0.2.21"</span>
<span class="py">hermit-abi</span> <span class="p">=</span> <span class="py">"</span><span class="p">=</span><span class="mf">0.3</span><span class="err">.</span><span class="mi">2</span><span class="py">"
libc = "</span><span class="p">=</span><span class="mf">0.2</span><span class="err">.</span><span class="mi">147</span><span class="s">"</span><span class="err">
</span><span class="py">miniz_oxide</span> <span class="p">=</span> <span class="s">"0.7.1"</span>
<span class="py">object</span> <span class="p">=</span> <span class="py">"</span><span class="p">=</span><span class="mf">0.32</span><span class="err">.</span><span class="mi">0</span><span class="s">"</span><span class="err">
</span><span class="py">rustc-demangle</span> <span class="p">=</span> <span class="s">"0.1.23"</span>
<span class="py">wasi</span> <span class="p">=</span> <span class="s">"0.11.0"</span>
<span class="py">cc</span> <span class="p">=</span> <span class="py">"</span><span class="p">=</span><span class="mf">1.0</span><span class="err">.</span><span class="mi">79</span><span class="py">"
memchr = "</span><span class="p">=</span><span class="mf">2.5</span><span class="err">.</span><span class="mi">0</span><span class="py">"
unicode-width = "</span><span class="p">=</span><span class="mf">0.1</span><span class="err">.</span><span class="mi">10</span><span class="s">"</span><span class="err">
</span></code></pre></div></div>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">description</span> <span class="o">=</span> <span class="s2">"Flake to accompany https://n8henrie.com/2023/09/compiling-rust-for-the-esp32-with-nix/"</span><span class="p">;</span>
<span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:nixos/nixpkgs/release-23.05"</span><span class="p">;</span>
<span class="nv">rust-overlay</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:oxalica/rust-overlay"</span><span class="p">;</span>
<span class="nv">inputs</span><span class="o">.</span><span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">follows</span> <span class="o">=</span> <span class="s2">"nixpkgs"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="nv">outputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">self</span><span class="p">,</span>
<span class="nv">nixpkgs</span><span class="p">,</span>
<span class="nv">rust-overlay</span><span class="p">,</span>
<span class="p">}:</span> <span class="kd">let</span>
<span class="kn">inherit</span> <span class="p">(</span><span class="nv">nixpkgs</span><span class="p">)</span> <span class="nv">lib</span><span class="p">;</span>
<span class="nv">systems</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"aarch64-darwin"</span> <span class="s2">"x86_64-linux"</span> <span class="s2">"aarch64-linux"</span><span class="p">];</span>
<span class="nv">systemClosure</span> <span class="o">=</span> <span class="nv">attrs</span><span class="p">:</span>
<span class="kr">builtins</span><span class="o">.</span><span class="nv">foldl</span><span class="err">'</span> <span class="p">(</span><span class="nv">acc</span><span class="p">:</span> <span class="nv">system</span><span class="p">:</span>
<span class="nv">lib</span><span class="o">.</span><span class="nv">recursiveUpdate</span> <span class="nv">acc</span> <span class="p">(</span><span class="nv">attrs</span> <span class="nv">system</span><span class="p">))</span> <span class="p">{}</span>
<span class="nv">systems</span><span class="p">;</span>
<span class="kn">in</span>
<span class="nv">systemClosure</span> <span class="p">(</span>
<span class="nv">system</span><span class="p">:</span> <span class="kd">let</span>
<span class="kn">inherit</span> <span class="p">((</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">fromTOML</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span> <span class="sx">./Cargo.toml</span><span class="p">))</span><span class="o">.</span><span class="nv">package</span><span class="p">)</span> <span class="nv">name</span><span class="p">;</span>
<span class="nv">pkgs</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">overlays</span> <span class="o">=</span> <span class="p">[(</span><span class="kr">import</span> <span class="nv">rust-overlay</span><span class="p">)];</span>
<span class="p">};</span>
<span class="nv">toolchain</span> <span class="o">=</span> <span class="p">(</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">rust-bin</span><span class="o">.</span><span class="nv">fromRustupToolchainFile</span> <span class="sx">./rust-toolchain.toml</span>
<span class="p">);</span>
<span class="nv">rustPlatform</span> <span class="o">=</span> <span class="kd">let</span>
<span class="nv">pkgsCross</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">crossSystem</span> <span class="o">=</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"riscv32imc-unknown-none-elf"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kn">in</span>
<span class="nv">pkgsCross</span><span class="o">.</span><span class="nv">makeRustPlatform</span>
<span class="p">{</span>
<span class="nv">rustc</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="nv">cargo</span> <span class="o">=</span> <span class="nv">toolchain</span><span class="p">;</span>
<span class="p">};</span>
<span class="kn">in</span> <span class="p">{</span>
<span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">name</span> <span class="nv">rustPlatform</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">devShells</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">mkShell</span> <span class="p">{</span>
<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">cargo-espflash</span>
<span class="nv">toolchain</span>
<span class="p">];</span>
<span class="p">};</span>
<span class="nv">apps</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span> <span class="kd">let</span>
<span class="nv">flash</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">writeShellApplication</span> <span class="p">{</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"flash-</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
<span class="nv">runtimeInputs</span> <span class="o">=</span> <span class="p">[</span><span class="nv">pkgs</span><span class="o">.</span><span class="nv">cargo-espflash</span><span class="p">];</span>
<span class="nv">text</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2"> espflash --monitor </span><span class="si">${</span><span class="nv">self</span><span class="o">.</span><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">default</span><span class="si">}</span><span class="s2">/bin/</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="err">
</span><span class="s2"> ''</span><span class="p">;</span>
<span class="p">};</span>
<span class="kn">in</span> <span class="p">{</span>
<span class="nv">type</span> <span class="o">=</span> <span class="s2">"app"</span><span class="p">;</span>
<span class="nv">program</span> <span class="o">=</span> <span class="s2">"</span><span class="si">${</span><span class="nv">flash</span><span class="si">}</span><span class="s2">/bin/flash-</span><span class="si">${</span><span class="nv">name</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">default.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">lib</span><span class="p">,</span>
<span class="nv">rustPlatform</span><span class="p">,</span>
<span class="nv">name</span><span class="p">,</span>
<span class="p">}:</span> <span class="p">(</span><span class="nv">rustPlatform</span><span class="o">.</span><span class="nv">buildRustPackage</span>
<span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">name</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="nv">lib</span><span class="o">.</span><span class="nv">cleanSource</span> <span class="sx">./.</span><span class="p">;</span>
<span class="nv">cargoLock</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="nv">outputHashes</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"esp-wifi-0.1.0"</span> <span class="o">=</span> <span class="s2">"sha256-IUkX3inbeeRZk9q/mdg56h+qft+0/TVpOM4rCKNOwz8="</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="nv">SSID</span> <span class="o">=</span> <span class="s2">"foo"</span><span class="p">;</span>
<span class="nv">PASSWORD</span> <span class="o">=</span> <span class="s2">"bar"</span><span class="p">;</span>
<span class="nv">doCheck</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">})</span>
</code></pre></div></div>
<p>Currently working versions of the flake inputs:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix flake metadata
<span class="go">Resolved URL: git+file:///Users/n8henrie/git/no_std-training?dir=intro%2fhttp-client
Locked URL: git+file:///Users/n8henrie/git/no_std-training?dir=intro%2fhttp-client
Description: Flake to accompany https://n8henrie.com/2023/09/compiling-rust-for-the-esp32-with-nix/
Path: /nix/store/dr1pc7kzsal5ndzwgj0lgypkr7fyvsiy-source
Last modified: 2023-09-18 00:51:10
Inputs:
├───nixpkgs: github:nixos/nixpkgs/43257a0d289e9f3fd5e3ad0dd022e911d9781a37
└───rust-overlay: github:oxalica/rust-overlay/23224b680af0b27b320adec2a0dae4eef29350e6
├───flake-utils: github:numtide/flake-utils/cfacdce06f30d2b68473a46042957675eebb3401
│ └───systems: github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e
└───nixpkgs follows input 'nixpkgs'
</span></code></pre></div></div>
<p>Finally, as noted above, I used <code class="language-plaintext highlighter-rouge">esp-rs/no_std-training</code> at commit
<code class="language-plaintext highlighter-rouge">88bc692d81dfcf9491c80dc7c9e8601b702e465a</code>. If at some point this repo (or
<code class="language-plaintext highlighter-rouge">esp-wifi</code>) are taken down, I’ve made forks available at e.g.
<a href="https://github.com/n8henrie/esp-wifi">github.com/n8henrie/esp-wifi</a>.</p>
Thu, 21 Sep 2023 14:36:44 -0600
https://n8henrie.com/2023/09/compiling-rust-for-the-esp32-with-nix/
https://n8henrie.com/2023/09/compiling-rust-for-the-esp32-with-nix/
arduino
electronics
nix
rust
tech
tech
-
Cross-Compile Rust for x86 Linux from M1 Mac with Nix
<p><strong>Bottom Line:</strong> Nix makes cross-compiling Rust fairly straightforward.
<!--more--></p>
<p>I have been tinkering with using nix to build Rust projects over the last
couple of weeks and decided to try my hand at cross-compiling Rust for
x86_64-linux from my M1 Mac (aarch64-darwin) via nix. Currently, several of my
machines are running various flavors of NixOS (several aarch64-linux Raspberry
Pis, a few x86_64-linux machines, an aarch64-linux Asahi-turned-NixOS machine,
my MBP with nix-darwin), but it’s still really important for me to be able to
compile for regular non-NixOS Linux machines.</p>
<p>Via rustup, rust does a great job providing toolchains to facilitate
cross-compiling: simply <code class="language-plaintext highlighter-rouge">rustup target add x86_64-unknown-linux-gnu</code>.
Unfortunately it doesn’t provide <em>linkers</em>, so even after you’ve added the
toolchain, if you try to compile for linux, it’s not going to work:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo new linux-cross-example <span class="o">&&</span> <span class="nb">cd </span>linux-cross-example
<span class="gp">$</span><span class="w"> </span>rustup target add x86_64-unknown-linux-gnu
<span class="go">info: component 'rust-std' for target 'x86_64-unknown-linux-gnu' is up to date
</span><span class="gp">$</span><span class="w"> </span>cargo build <span class="nt">--target</span><span class="o">=</span>x86_64-unknown-linux-gnu
<span class="c">...
</span><span class="go"> = note: clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
ld: unknown option: --as-needed
</span><span class="c">...
</span></code></pre></div></div>
<p>There are a number of workarounds, including downloading linkers via homebrew,
various GitHub projects, using docker, or – my favorite – using <a href="https://actually.fyi/posts/zig-makes-rust-cross-compilation-just-work/">zig to do
the work for you</a> (although this doesn’t seem to be working currently for
musl targets, <a href="https://github.com/ziglang/zig/issues/5320">issue</a>).</p>
<p>With nix, the current best practice seems to be having nix (as opposed to cargo
/ rust) do the heavy lifting of cross-compilation. Continuing in the
<code class="language-plaintext highlighter-rouge">linux-cross-example</code> directory created above, I created a basic <code class="language-plaintext highlighter-rouge">flake.nix</code>,
including these inputs:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
};
</code></pre></div></div>
<p>Perhaps the key feature of nix is ensuring reproducibility, so to that end, if
readers are not having luck following this post, it may be necessary to pin the
inputs to these specific revisions:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix flake metadata
<span class="go">warning: Git tree '/Users/n8henrie/Desktop/linux-cross' is dirty
Resolved URL: git+file:///Users/n8henrie/Desktop/linux-cross
Locked URL: git+file:///Users/n8henrie/Desktop/linux-cross
Description: Example of cross-compiling Rust on aarch64-darwin for x86_64-linux
Path: /nix/store/d67wnc6v391x4gq5a24wzxbxxxfbvx07-source
Last modified: 1969-12-31 17:00:00
Inputs:
├───nixpkgs: github:nixos/nixpkgs/3c15feef7770eb5500a4b8792623e2d6f598c9c1
└───rust-overlay: github:oxalica/rust-overlay/a8b4bb4cbb744baaabc3e69099f352f99164e2c1
├───flake-utils: github:numtide/flake-utils/cfacdce06f30d2b68473a46042957675eebb3401
│ └───systems: github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e
└───nixpkgs: github:NixOS/nixpkgs/96ba1c52e54e74c3197f4d43026b3f3d92e83ff9
</span></code></pre></div></div>
<p>For our first trick, we’ll try to compile a <code class="language-plaintext highlighter-rouge">hello world</code> program for
<code class="language-plaintext highlighter-rouge">x86_64-unknown-linux-gnu</code>, probably better known as “your run-of-the-mill
standard Linux system.” Here is the rest of my <code class="language-plaintext highlighter-rouge">flake.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"nixpkgs/nixos-unstable"</span><span class="p">;</span>
<span class="nv">rust-overlay</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:oxalica/rust-overlay"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">outputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">self</span><span class="p">,</span>
<span class="nv">nixpkgs</span><span class="p">,</span>
<span class="nv">rust-overlay</span><span class="p">,</span>
<span class="p">}:</span> <span class="kd">let</span>
<span class="nv">system</span> <span class="o">=</span> <span class="s2">"aarch64-darwin"</span><span class="p">;</span>
<span class="nv">overlays</span> <span class="o">=</span> <span class="p">[(</span><span class="kr">import</span> <span class="nv">rust-overlay</span><span class="p">)];</span>
<span class="nv">pkgs</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">overlays</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">crossSystem</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">config</span> <span class="o">=</span> <span class="s2">"x86_64-unknown-linux-gnu"</span><span class="p">;</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"x86_64-unknown-linux-gnu"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kn">in</span> <span class="p">{</span>
<span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">default</span> <span class="o">=</span> <span class="nv">self</span><span class="o">.</span><span class="nv">outputs</span><span class="o">.</span><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">x86_64-linux-example</span><span class="p">;</span>
<span class="nv">x86_64-linux-example</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{};</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To go along with the above, we’ll use this very simple <code class="language-plaintext highlighter-rouge">default.nix</code> (which is
the file that will be called by “default” via <code class="language-plaintext highlighter-rouge">pkgs.callPackage ./.</code>, or if one
were to <code class="language-plaintext highlighter-rouge">import</code> a directory as in <code class="language-plaintext highlighter-rouge">import ./.</code>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{rustPlatform}:
rustPlatform.buildRustPackage {
name = "rust-cross-test";
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
}
</code></pre></div></div>
<p>So currently our working directory looks like this:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>tree <span class="nb">.</span>
<span class="c">.
</span><span class="go">├── Cargo.toml
├── default.nix
├── flake.nix
└── src
└── main.rs
2 directories, 4 files
</span></code></pre></div></div>
<p>Amazingly, all that it takes for a successful build from here is to first run
<code class="language-plaintext highlighter-rouge">cargo update</code> (or <code class="language-plaintext highlighter-rouge">cargo build</code>) to generate <code class="language-plaintext highlighter-rouge">Cargo.lock</code>, and then run <code class="language-plaintext highlighter-rouge">nix
build</code>!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>cargo update
<span class="gp">$</span><span class="w"> </span>nix build
<span class="go">warning: Git tree '/Users/n8henrie/Desktop/linux-cross' is dirty
warning: creating lock file '/Users/n8henrie/Desktop/linux-cross/flake.lock'
warning: Git tree '/Users/n8henrie/Desktop/linux-cross' is dirty
</span><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$?</span>
<span class="go">0
</span></code></pre></div></div>
<p>We can see that the resulting file seems to have the expected architecture:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>file result/bin/linux-cross-example
<span class="go">result/bin/linux-cross-example: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /nix/store/2abz7cq1p8c1pg38prm2gpja67bzr9gq-glibc-x86_64-unknown-linux-gnu-2.37-8/lib/ld-linux-x86-64.so.2, for GNU/Linux 3.10.0, not stripped
</span></code></pre></div></div>
<p>I used <code class="language-plaintext highlighter-rouge">scp</code> to copy the binary from <code class="language-plaintext highlighter-rouge">./result/bin/linux-cross-example</code> to my
Arch linux machine. Unfortunately, upon trying to run it, I got a surprising
error:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> /etc/os-release
<span class="go">NAME="Arch Linux"
PRETTY_NAME="Arch Linux"
ID=arch
BUILD_ID=rolling
</span><span class="gp">ANSI_COLOR="38;</span>2<span class="p">;</span>23<span class="p">;</span>147<span class="p">;</span>209<span class="s2">"
</span><span class="go">HOME_URL="https://archlinux.org/"
DOCUMENTATION_URL="https://wiki.archlinux.org/"
SUPPORT_URL="https://bbs.archlinux.org/"
BUG_REPORT_URL="https://bugs.archlinux.org/"
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
LOGO=archlinux-logo
</span><span class="gp">$</span><span class="w"> </span>./linux-cross-example
<span class="go">-bash: ./linux-cross-example: cannot execute: required file not found
</span></code></pre></div></div>
<p>Huh.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>ldd ./linux-cross-example
<span class="go"> linux-vdso.so.1 (0x00007ffc363a4000)
</span><span class="gp"> libgcc_s.so.1 =></span><span class="w"> </span>/usr/lib/libgcc_s.so.1 <span class="o">(</span>0x00007f5dc90ed000<span class="o">)</span>
<span class="gp"> libc.so.6 =></span><span class="w"> </span>/usr/lib/libc.so.6 <span class="o">(</span>0x00007f5dc8e00000<span class="o">)</span>
<span class="gp"> /nix/store/2abz7cq1p8c1pg38prm2gpja67bzr9gq-glibc-x86_64-unknown-linux-gnu-2.37-8/lib/ld-linux-x86-64.so.2 =></span><span class="w"> </span>/usr/lib64/ld-linux-x86-64.so.2 <span class="o">(</span>0x00007f5dc91a6000<span class="o">)</span>
</code></pre></div></div>
<p>Huh, so it seems to be looking for <code class="language-plaintext highlighter-rouge">ld-linux-x86-64.so.2</code> in <code class="language-plaintext highlighter-rouge">/nix/store</code> but
isn’t finding it (because it’s not there).</p>
<p>Skipping back up a few lines, we actually already saw this path in the output
from the <code class="language-plaintext highlighter-rouge">file</code> command, run locally on MacOS: <code class="language-plaintext highlighter-rouge">dynamically linked, interpreter
/nix/store/2abz7c...</code></p>
<p>After a bit of investigative work, it seems that rust binaries are <a href="https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables"><em>mostly</em>
statically linked by default</a>, but do need to find a few libraries like
<code class="language-plaintext highlighter-rouge">glibc</code>, which are dynamically linked. Nix is creating this binary in such a
way that it is trying to find nix’s copy of this required file, but the nix
version doesn’t exist on my Arch machine. Apparently most Linux machines put it
in <code class="language-plaintext highlighter-rouge">/lib64/</code> or perhaps <code class="language-plaintext highlighter-rouge">/usr/lib64/</code>; on my Arch machine, it looks like
<code class="language-plaintext highlighter-rouge">/lib64/</code> should work (which is a symlink to <code class="language-plaintext highlighter-rouge">/usr/lib/</code>):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">stat</span> /lib64/ld-linux-x86-64.so.2
<span class="go"> File: /lib64/ld-linux-x86-64.so.2
Size: 216192 Blocks: 424 IO Block: 4096 regular file
Device: 0,25 Inode: 17427431 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2023-09-05 14:40:37.920798992 -0600
Modify: 2023-08-17 09:05:37.000000000 -0600
Change: 2023-08-22 14:10:02.729886634 -0600
Birth: 2023-08-22 14:10:02.729886634 -0600
</span></code></pre></div></div>
<p>Thankfully, nix provides a tool called <code class="language-plaintext highlighter-rouge">patchelf</code> that can patch the binary to
look in a non-default location for this required file. We’ll add it to
<code class="language-plaintext highlighter-rouge">default.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nv">rustPlatform</span><span class="p">}:</span>
<span class="nv">rustPlatform</span><span class="o">.</span><span class="nv">buildRustPackage</span> <span class="p">{</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"rust-cross-test"</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="sx">./.</span><span class="p">;</span>
<span class="nv">cargoLock</span><span class="o">.</span><span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="nv">postBuild</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2"> patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 target/x86_64-unknown-linux-gnu/release/linux-cross-example</span><span class="err">
</span><span class="s2"> ''</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We’ll once again run <code class="language-plaintext highlighter-rouge">nix build</code>, use <code class="language-plaintext highlighter-rouge">scp</code> to copy the binary, and…</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>./linux-cross-example
<span class="go">Hello, world!
</span></code></pre></div></div>
<p>Sweet, it works! We cross-compiled Rust from our M1 Mac to x86_64-linux with
just a few lines of nix code!</p>
<p>For our next challenge, let’s see if we can build a <strong>fully static</strong>
<code class="language-plaintext highlighter-rouge">x86_64-unknown-linux-gnu</code> binary! We’ll modify <code class="language-plaintext highlighter-rouge">default.nix</code> by removing the
<code class="language-plaintext highlighter-rouge">patchelf</code> code (since this will by <em>fully static</em> and not require the
<code class="language-plaintext highlighter-rouge">--set-interpreter</code> business) and adding a few lines of code from <a href="https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables">the same
StackOverflow thread from above</a>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">rustPlatform</span><span class="p">,</span>
<span class="nv">glibc</span><span class="p">,</span>
<span class="p">}:</span>
<span class="nv">rustPlatform</span><span class="o">.</span><span class="nv">buildRustPackage</span> <span class="p">{</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"rust-cross-test"</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="sx">./.</span><span class="p">;</span>
<span class="nv">cargoLock</span><span class="o">.</span><span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span><span class="nv">glibc</span><span class="o">.</span><span class="nv">static</span><span class="p">];</span>
<span class="nv">RUSTFLAGS</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"-C"</span> <span class="s2">"target-feature=+crt-static"</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="gp">$</span><span class="w"> </span>file result/bin/linux-cross-example
<span class="go">result/bin/linux-cross-example: ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), static-pie linked, for GNU/Linux 3.10.0, not stripped
</span></code></pre></div></div>
<p>It certainly <em>looks</em> like a static binary. Sure enough, our it runs like a
champ on our Linux machine!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>ldd linux-cross-example
<span class="go"> statically linked
</span><span class="gp">$</span><span class="w"> </span>./linux-cross-example
<span class="go">Hello, world!
</span></code></pre></div></div>
<p>For our last trick, we’ll try to compile a fully static musl build, which
should run on basically any <code class="language-plaintext highlighter-rouge">x86_64-linux</code> machine. For this, we can revert our
<code class="language-plaintext highlighter-rouge">default.nix</code> back to the very simple way it started:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nv">rustPlatform</span><span class="p">}:</span>
<span class="nv">rustPlatform</span><span class="o">.</span><span class="nv">buildRustPackage</span> <span class="p">{</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"rust-cross-test"</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="sx">./.</span><span class="p">;</span>
<span class="nv">cargoLock</span><span class="o">.</span><span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And simply change <code class="language-plaintext highlighter-rouge">flake.nix</code> to reflect the <code class="language-plaintext highlighter-rouge">musl</code> target triple, setting
<code class="language-plaintext highlighter-rouge">isStatic = true;</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"nixpkgs/nixos-unstable"</span><span class="p">;</span>
<span class="nv">rust-overlay</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:oxalica/rust-overlay"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">outputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">self</span><span class="p">,</span>
<span class="nv">nixpkgs</span><span class="p">,</span>
<span class="nv">rust-overlay</span><span class="p">,</span>
<span class="p">}:</span> <span class="kd">let</span>
<span class="nv">system</span> <span class="o">=</span> <span class="s2">"aarch64-darwin"</span><span class="p">;</span>
<span class="nv">overlays</span> <span class="o">=</span> <span class="p">[(</span><span class="kr">import</span> <span class="nv">rust-overlay</span><span class="p">)];</span>
<span class="nv">pkgs</span> <span class="o">=</span> <span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">overlays</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">crossSystem</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">config</span> <span class="o">=</span> <span class="s2">"x86_64-unknown-linux-musl"</span><span class="p">;</span>
<span class="nv">rustc</span><span class="o">.</span><span class="nv">config</span> <span class="o">=</span> <span class="s2">"x86_64-unknown-linux-musl"</span><span class="p">;</span>
<span class="nv">isStatic</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kn">in</span> <span class="p">{</span>
<span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">default</span> <span class="o">=</span> <span class="nv">self</span><span class="o">.</span><span class="nv">outputs</span><span class="o">.</span><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">x86_64-linux-musl-example</span><span class="p">;</span>
<span class="nv">x86_64-linux-musl-example</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{};</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix build
<span class="gp">$</span><span class="w"> </span>file result/bin/linux-cross-example
<span class="go">result/bin/linux-cross-example: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, not stripped
</span></code></pre></div></div>
<p>On the Arch machine:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>ldd linux-cross-example
<span class="go"> statically linked
</span><span class="gp">$</span><span class="w"> </span>./linux-cross-example
<span class="go">Hello, world!
</span></code></pre></div></div>
<p>Cool! It took a little bit of reading and tinkering to sort this out, but in
the end it’s a <em>remarkably</em> simple setup requiring very few lines of code (at
least for this <code class="language-plaintext highlighter-rouge">hello world</code> project). As a side note, I didn’t have any luck
statically compiling for <code class="language-plaintext highlighter-rouge">x86_64-unknown-linux-gnu</code> with the <code class="language-plaintext highlighter-rouge">isStatic</code>
setting; for me this results in a unsupported system error.</p>
<p>Putting everything together, with a little bit of refactoring, and adding a
bonus config for <code class="language-plaintext highlighter-rouge">aarch64-unknown-linux-musl</code> (which runs without issue on an
<code class="language-plaintext highlighter-rouge">aarch64-linux</code> Raspberry Pi):</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">description</span> <span class="o">=</span> <span class="s2">"Example of cross-compiling Rust on aarch64-darwin for x86_64-linux"</span><span class="p">;</span>
<span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:nixos/nixpkgs/nixos-unstable"</span><span class="p">;</span>
<span class="nv">rust-overlay</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:oxalica/rust-overlay"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">outputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">self</span><span class="p">,</span>
<span class="nv">nixpkgs</span><span class="p">,</span>
<span class="nv">rust-overlay</span><span class="p">,</span>
<span class="p">}:</span> <span class="kd">let</span>
<span class="nv">system</span> <span class="o">=</span> <span class="s2">"aarch64-darwin"</span><span class="p">;</span>
<span class="nv">overlays</span> <span class="o">=</span> <span class="p">[(</span><span class="kr">import</span> <span class="nv">rust-overlay</span><span class="p">)];</span>
<span class="nv">makePkgs</span> <span class="o">=</span> <span class="nv">config</span><span class="p">:</span>
<span class="kr">import</span> <span class="nv">nixpkgs</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">overlays</span> <span class="nv">system</span><span class="p">;</span>
<span class="nv">crossSystem</span> <span class="o">=</span> <span class="p">{</span>
<span class="kn">inherit</span> <span class="nv">config</span><span class="p">;</span>
<span class="nv">rustc</span> <span class="o">=</span> <span class="p">{</span><span class="kn">inherit</span> <span class="nv">config</span><span class="p">;};</span>
<span class="nv">isStatic</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">elem</span> <span class="nv">config</span> <span class="p">[</span>
<span class="s2">"aarch64-unknown-linux-musl"</span>
<span class="s2">"x86_64-unknown-linux-musl"</span>
<span class="p">];</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kn">in</span> <span class="p">{</span>
<span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">default</span> <span class="o">=</span> <span class="nv">self</span><span class="o">.</span><span class="nv">outputs</span><span class="o">.</span><span class="nv">packages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">}</span><span class="o">.</span><span class="nv">x86_64-linux-gnu-example</span><span class="p">;</span>
<span class="nv">x86_64-linux-gnu-example</span> <span class="o">=</span> <span class="p">(</span><span class="nv">makePkgs</span> <span class="s2">"x86_64-unknown-linux-gnu"</span><span class="p">)</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{};</span>
<span class="nv">x86_64-linux-gnu-static-example</span> <span class="o">=</span> <span class="p">(</span><span class="nv">makePkgs</span> <span class="s2">"x86_64-unknown-linux-gnu"</span><span class="p">)</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{</span><span class="nv">buildGNUStatic</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;};</span>
<span class="nv">x86_64-linux-musl-example</span> <span class="o">=</span> <span class="p">(</span><span class="nv">makePkgs</span> <span class="s2">"x86_64-unknown-linux-musl"</span><span class="p">)</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{};</span>
<span class="nv">aarch64-linux-musl-example</span> <span class="o">=</span> <span class="p">(</span><span class="nv">makePkgs</span> <span class="s2">"aarch64-unknown-linux-musl"</span><span class="p">)</span><span class="o">.</span><span class="nv">callPackage</span> <span class="sx">./.</span> <span class="p">{};</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nv">rustPlatform</span><span class="p">,</span>
<span class="nv">glibc</span><span class="p">,</span>
<span class="nv">targetPlatform</span><span class="p">,</span>
<span class="nv">lib</span><span class="p">,</span>
<span class="nv">buildGNUStatic</span> <span class="o">?</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">}:</span>
<span class="nv">rustPlatform</span><span class="o">.</span><span class="nv">buildRustPackage</span> <span class="p">({</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"rust-cross-test"</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="sx">./.</span><span class="p">;</span>
<span class="nv">cargoLock</span><span class="o">.</span><span class="nv">lockFile</span> <span class="o">=</span> <span class="sx">./Cargo.lock</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">//</span> <span class="p">(</span>
<span class="k">if</span> <span class="nv">buildGNUStatic</span>
<span class="k">then</span> <span class="p">{</span>
<span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span><span class="nv">glibc</span><span class="o">.</span><span class="nv">static</span><span class="p">];</span>
<span class="nv">RUSTFLAGS</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"-C"</span> <span class="s2">"target-feature=+crt-static"</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="nv">lib</span><span class="o">.</span><span class="nv">optionalAttrs</span> <span class="p">(</span><span class="nv">targetPlatform</span><span class="o">.</span><span class="nv">config</span> <span class="o">==</span> <span class="s2">"x86_64-unknown-linux-gnu"</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">postBuild</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2"> patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 target/x86_64-unknown-linux-gnu/release/linux-cross-example</span><span class="err">
</span><span class="s2"> ''</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">))</span>
</code></pre></div></div>
<p>From here, one should be able to <code class="language-plaintext highlighter-rouge">nix build .#x86_64-linux-musl-example</code> and be
off to the races! And thanks to the power of nix, with any luck, and if you pin
your inputs to the versions listed towards the beginning of this post, you
should theoretically be able to rely on a successful build today, tomorrow, and
maybe months, years, or – who knows – <a href="https://blinry.org/nix-time-travel/">maybe even a decade from
now</a>!</p>
Wed, 06 Sep 2023 14:24:35 -0600
https://n8henrie.com/2023/09/crosscompile-rust-for-x86-linux-from-m1-mac-with-nix/
https://n8henrie.com/2023/09/crosscompile-rust-for-x86-linux-from-m1-mac-with-nix/
nix
rust
linux
MacOS
tech
tech
-
Hacking on nixpkgs with nix develop
<p><strong>Bottom Line:</strong> Failing to add MPS support to nixpkgs.python310Packages.torch.
<!--more--></p>
<p>I’ve been using nix / nixpkgs / NixOS on and off for a year or two now. I’ve
been genuinely impressed at how open and welcome the community is to
contributions, so I’d like to start taking advantage of that by contributing
back.</p>
<p>I’m writing this post as I go, in the spirit of this (highly recommended)
series: <a href="https://ianthehenry.com/posts/how-to-learn-nix/">https://ianthehenry.com/posts/how-to-learn-nix/</a>.</p>
<p>The issue I’d like to work on is adding MPS support to the pytorch derivation:
<a href="https://github.com/NixOS/nixpkgs/issues/243868">https://github.com/NixOS/nixpkgs/issues/243868</a>; NB: the <code class="language-plaintext highlighter-rouge">torch-bin</code>
derivation already has it.</p>
<p>(SPOILER: I was not successful, but I did learn a few things along the way.)</p>
<p>I start by cloning nixpkgs, and for the sake of reproducibility for readers in
years to come (one of the big advantages of nix!) we’ll checkout a specific
commit (though one would normally stay on <code class="language-plaintext highlighter-rouge">master</code> in order to contribute a
PR).</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git clone https://github.com/NixOS/nixpkgs.git
<span class="gp">$</span><span class="w"> </span><span class="nb">cd </span>nixpkgs
<span class="gp">$</span><span class="w"> </span>git checkout 7d053c812bb59bbb15293f9bb6087748e7c21b1a
</code></pre></div></div>
<p>First we’ll make sure we can build and run the current derivation (some
<a href="https://github.com/NixOS/nix/issues/5567#issuecomment-1662738721">context</a> for this command):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix shell <span class="s2">"</span><span class="si">$(</span>
<span class="go"> nix eval --raw --apply '
py: (py.withPackages (pp: [ pp.torch ])).drvPath
</span><span class="gp"> ' .#</span>python310
<span class="go">)"
</span><span class="gp">$</span><span class="w"> </span><span class="nb">type</span> <span class="nt">-p</span> python
<span class="go">/nix/store/1sgzgqbfyj8sn7rjzhvrzy1nj38cwfi1-python3-3.10.12-env/bin/python
</span><span class="gp">$</span><span class="w"> </span>python <span class="nt">-c</span> <span class="s1">'import torch; print(torch.__version__, torch.backends.mps.is_available())'</span>
<span class="go">2.0.1 False
</span><span class="gp">$</span><span class="w"> </span><span class="nb">exit</span>
<span class="gp">$</span><span class="w">
</span></code></pre></div></div>
<p>Cool.</p>
<p>The nix build process is documented in a few places, to get an idea of what’s
going on above:</p>
<ul>
<li><a href="https://nixos.org/guides/nix-pills/fundamentals-of-stdenv.html#id1466">https://nixos.org/guides/nix-pills/fundamentals-of-stdenv.html#id1466</a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#chap-stdenv">https://nixos.org/manual/nixpkgs/stable/#chap-stdenv</a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#sec-building-stdenv-package-in-nix-shell">https://nixos.org/manual/nixpkgs/stable/#sec-building-stdenv-package-in-nix-shell</a></li>
</ul>
<blockquote>
<p>While inside an interactive nix-shell, if you wanted to run all phases in the
order they would be run in an actual build, you can invoke genericBuild
yourself.</p>
</blockquote>
<p>So basically, it looks like the process is:</p>
<ul>
<li>run <code class="language-plaintext highlighter-rouge">nix-shell -A foo</code> <- this sources <code class="language-plaintext highlighter-rouge">$stdenv/setup</code>, which defines a bunch
of phases</li>
<li>run <code class="language-plaintext highlighter-rouge">genericBuild</code> <- this was defined in the step above</li>
</ul>
<p>Let’s try it:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="c"># show that e.g. buildPhase and stdenv are undefined:</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-p</span> stdenv
<span class="go">bash: declare: stdenv: not found
</span><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-f</span> buildPhase
<span class="gp">$</span><span class="w">
</span><span class="gp">$</span><span class="w"> </span>nix-shell <span class="nt">-A</span> python3Packages.torch
<span class="go">Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
</span><span class="gp">$</span><span class="w">
</span><span class="gp">$</span><span class="w"> </span><span class="c"># we can see that stdenv and buildPhase are now defined:</span>
<span class="gp">[nix-shell:~/git/nixpkgs]$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-p</span> stdenv
<span class="go">declare -x stdenv="/nix/store/lplcqh67ldaj5f4pg4js2sgf860nn4iz-stdenv-darwin"
</span><span class="gp">[nix-shell:~/git/nixpkgs]$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-f</span> buildPhase
<span class="go">buildPhase ()
{
</span><span class="gp"> runHook preBuild;</span><span class="w">
</span><span class="gp"> if [[ -z "$</span><span class="o">{</span>makeFlags-<span class="o">}</span><span class="s2">" && -z "</span><span class="k">${</span><span class="nv">makefile</span><span class="k">:-}</span><span class="s2">" && ! ( -e Makefile || -e makefile || -e GNUmakefile ) ]]; then
</span><span class="gp"> echo "no Makefile or custom buildPhase, doing nothing";</span><span class="w">
</span><span class="go"> else
</span><span class="gp"> foundMakefile=1;</span><span class="w">
</span><span class="gp"> local flagsArray=($</span><span class="o">{</span>enableParallelBuilding:+-j<span class="k">${</span><span class="nv">NIX_BUILD_CORES</span><span class="k">}</span><span class="o">}</span> <span class="nv">SHELL</span><span class="o">=</span><span class="nv">$SHELL</span><span class="o">)</span><span class="p">;</span>
<span class="gp"> _accumFlagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray;</span><span class="w">
</span><span class="gp"> echoCmd 'build flags' "$</span><span class="o">{</span>flagsArray[@]<span class="o">}</span><span class="s2">";
</span><span class="gp"> make $</span><span class="s2">{makefile:+-f </span><span class="nv">$makefile</span><span class="s2">} "</span><span class="k">${</span><span class="nv">flagsArray</span><span class="p">[@]</span><span class="k">}</span><span class="s2">";
</span><span class="gp"> unset flagsArray;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="go"> runHook postBuild
}
</span><span class="gp">[nix-shell:~/git/nixpkgs]$</span><span class="w">
</span><span class="gp">[nix-shell:~/git/nixpkgs]$</span><span class="w"> </span><span class="c"># we can see where buildPhase is defined:</span>
<span class="gp">[nix-shell:~/git/nixpkgs]$</span><span class="w"> </span><span class="nb">grep</span> <span class="s1">'^buildPhase'</span> <span class="nv">$stdenv</span>/setup
<span class="go">buildPhase() {
</span></code></pre></div></div>
<p>Now we’ll try with the <code class="language-plaintext highlighter-rouge">nix develop</code> environment. You can add the <code class="language-plaintext highlighter-rouge">--debug</code>
flag for a lot of extra information on what it’s doing.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="c"># show that stdenv and buildPhase are undefined:</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-p</span> stdenv
<span class="go">bash: declare: stdenv: not found
</span><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-f</span> buildPhase
<span class="gp">$</span><span class="w">
</span><span class="gp">$</span><span class="w"> </span>nix develop .#python310Packages.pytorch
<span class="gp">$</span><span class="w"> </span><span class="c"># it looks like the same environment is available:</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-p</span> stdenv
<span class="go">declare -x stdenv="/nix/store/lplcqh67ldaj5f4pg4js2sgf860nn4iz-stdenv-darwin"
</span><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-f</span> buildPhase
<span class="go">buildPhase ()
{
</span><span class="gp"> runHook preBuild;</span><span class="w">
</span><span class="gp"> if [[ -z "$</span><span class="o">{</span>makeFlags-<span class="o">}</span><span class="s2">" && -z "</span><span class="k">${</span><span class="nv">makefile</span><span class="k">:-}</span><span class="s2">" && ! ( -e Makefile || -e makefile || -e GNUmakefile ) ]]; then
</span><span class="gp"> echo "no Makefile or custom buildPhase, doing nothing";</span><span class="w">
</span><span class="go"> else
</span><span class="gp"> foundMakefile=1;</span><span class="w">
</span><span class="gp"> local flagsArray=($</span><span class="o">{</span>enableParallelBuilding:+-j<span class="k">${</span><span class="nv">NIX_BUILD_CORES</span><span class="k">}</span><span class="o">}</span> <span class="nv">SHELL</span><span class="o">=</span><span class="nv">$SHELL</span><span class="o">)</span><span class="p">;</span>
<span class="gp"> _accumFlagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray;</span><span class="w">
</span><span class="gp"> echoCmd 'build flags' "$</span><span class="o">{</span>flagsArray[@]<span class="o">}</span><span class="s2">";
</span><span class="gp"> make $</span><span class="s2">{makefile:+-f </span><span class="nv">$makefile</span><span class="s2">} "</span><span class="k">${</span><span class="nv">flagsArray</span><span class="p">[@]</span><span class="k">}</span><span class="s2">";
</span><span class="gp"> unset flagsArray;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="go"> runHook postBuild
}
</span></code></pre></div></div>
<p>If we look at the definition of <code class="language-plaintext highlighter-rouge">genericBuild</code> in <code class="language-plaintext highlighter-rouge">$stdenv/setup</code>, we can see
the list of <code class="language-plaintext highlighter-rouge">phases</code> that the manual keeps talking about:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="k">${</span><span class="nv">phases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">phases</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">prePhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> unpackPhase patchPhase </span><span class="k">${</span><span class="nv">preConfigurePhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> </span><span class="se">\</span><span class="s2">
configurePhase </span><span class="k">${</span><span class="nv">preBuildPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> buildPhase checkPhase </span><span class="se">\</span><span class="s2">
</span><span class="k">${</span><span class="nv">preInstallPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> installPhase </span><span class="k">${</span><span class="nv">preFixupPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> fixupPhase installCheckPhase </span><span class="se">\</span><span class="s2">
</span><span class="k">${</span><span class="nv">preDistPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> distPhase </span><span class="k">${</span><span class="nv">postPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2">"</span><span class="p">;</span>
<span class="k">fi</span>
</code></pre></div></div>
<p>In this case <code class="language-plaintext highlighter-rouge">phases</code> doesn’t seem to be defined…</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> | <span class="nb">grep</span> <span class="s1">'^phases'</span>
<span class="gp">$</span><span class="w">
</span></code></pre></div></div>
<p>… so I assume it’s using the default phases listed above.</p>
<p>Let’s see what happens if we manually follow those phases:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="k">${</span><span class="nv">prePhases</span><span class="p">[*]</span><span class="k">}</span>
<span class="gp">$</span><span class="w"> </span>unpackPhase
<span class="go">unpacking source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
source root is source
setting SOURCE_DATE_EPOCH to timestamp 315644400 of file source/version.txt
</span><span class="gp">$</span><span class="w"> </span>patchPhase
<span class="gp">$</span><span class="w"> </span><span class="k">${</span><span class="nv">preConfigurePhases</span><span class="p">[*]</span><span class="k">}</span>
<span class="gp">$</span><span class="w"> </span>configurePhase
<span class="go">no configure script, doing nothing
</span><span class="gp">$</span><span class="w"> </span><span class="k">${</span><span class="nv">preBuildPhases</span><span class="p">[*]</span><span class="k">}</span>
<span class="gp">$</span><span class="w"> </span>buildPhase
<span class="go">/nix/store/bq1q4gk52gsx4fg4pf07f2kxqgazkcls-python3-3.10.12/bin/python3.10: can't open file '/Users/n8henrie/git/nixpkgs/setup.py': [Errno 2] No such file or directory
CMake Error: The source directory "/Users/n8henrie/git/nixpkgs/build" does not exist.
Specify --help for usage, or press the help button on the CMake GUI.
no Makefile or custom buildPhase, doing nothing
bash: pushd: dist: No such file or directory
Bad wheel filename 'torch-2.0.1*.whl'
sed: can't read unpacked/torch-2.0.1/torch-2.0.1.dist-info/METADATA: No such file or directory
Traceback (most recent call last):
File "/nix/store/bq1q4gk52gsx4fg4pf07f2kxqgazkcls-python3-3.10.12/lib/python3.10/runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/nix/store/bq1q4gk52gsx4fg4pf07f2kxqgazkcls-python3-3.10.12/lib/python3.10/runpy.py", line 86, in _run_code
exec(code, run_globals)
</span><span class="gp"> File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/__main__.py", line 23, in <module></span><span class="w">
</span><span class="go"> sys.exit(main())
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/__main__.py", line 19, in main
sys.exit(wheel.cli.main())
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/cli/__init__.py", line 91, in main
args.func(args)
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/cli/__init__.py", line 25, in pack_f
pack(args.directory, args.dest_dir, args.build_number)
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/cli/pack.py", line 25, in pack
for fn in os.listdir(directory)
FileNotFoundError: [Errno 2] No such file or directory: 'unpacked/torch-2.0.1'
bash: popd: directory stack empty
</span></code></pre></div></div>
<p>Huh, a bunch of errors there.</p>
<p><code class="language-plaintext highlighter-rouge">unpackPhase</code> seems to create a directory <code class="language-plaintext highlighter-rouge">./source</code>. My guess is that we’re
supposed to <code class="language-plaintext highlighter-rouge">cd</code> here at some point. Sure enough, looking at the end of
<code class="language-plaintext highlighter-rouge">genericBuild</code>, we see:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">if [ "$</span>curPhase<span class="s2">" = unpackPhase ]; then
</span><span class="gp"> [ -z "$</span><span class="s2">{sourceRoot}"</span> <span class="o">]</span> <span class="o">||</span> <span class="nb">chmod</span> +x <span class="s2">"</span><span class="k">${</span><span class="nv">sourceRoot</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span>
<span class="gp"> cd "$</span><span class="o">{</span>sourceRoot:-.<span class="o">}</span><span class="s2">";
</span><span class="gp">fi;</span><span class="w">
</span></code></pre></div></div>
<p>So let’s try again:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-p</span> sourceRoot
<span class="go">declare -- sourceRoot="source"
</span><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="nv">$sourceRoot</span>
<span class="gp">$</span><span class="w"> </span>buildPhase
</code></pre></div></div>
<p>After which we see lots of build output, ending in a few errors:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- Build files have been written to: /Users/n8henrie/git/nixpkgs/source/build
make[1]: Entering directory '/Users/n8henrie/git/nixpkgs/source/build'
make[1]: *** No targets specified and no makefile found. Stop.
make[1]: Leaving directory '/Users/n8henrie/git/nixpkgs/source/build'
make: *** [Makefile:6: all] Error 2
bash: pushd: dist: No such file or directory
Bad wheel filename 'torch-2.0.1*.whl'
sed: can't read unpacked/torch-2.0.1/torch-2.0.1.dist-info/METADATA: No such file or directory
Traceback (most recent call last):
File "/nix/store/bq1q4gk52gsx4fg4pf07f2kxqgazkcls-python3-3.10.12/lib/python3.10/runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/nix/store/bq1q4gk52gsx4fg4pf07f2kxqgazkcls-python3-3.10.12/lib/python3.10/runpy.py", line 86, in _run_code
exec(code, run_globals)
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/__main__.py", line 23, in <module>
sys.exit(main())
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/__main__.py", line 19, in main
sys.exit(wheel.cli.main())
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/cli/__init__.py", line 91, in main
args.func(args)
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/cli/__init__.py", line 25, in pack_f
pack(args.directory, args.dest_dir, args.build_number)
File "/nix/store/gdh5vg1j8b4qmri26hzl520asq9j3h8a-python3.10-wheel-0.38.4/lib/python3.10/site-packages/wheel/cli/pack.py", line 25, in pack
for fn in os.listdir(directory)
FileNotFoundError: [Errno 2] No such file or directory: 'unpacked/torch-2.0.1'
bash: popd: directory stack empty
$
</code></pre></div></div>
<p>Huh. Let’s dig deeper.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-p</span> buildPhase
<span class="go">declare -- buildPhase="setuptoolsBuildPhase"
</span><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> <span class="nt">-f</span> setuptoolsBuildPhase
<span class="go">setuptoolsBuildPhase ()
{
</span><span class="gp"> echo "Executing setuptoolsBuildPhase";</span><span class="w">
</span><span class="gp"> local args;</span><span class="w">
</span><span class="gp"> runHook preBuild;</span><span class="w">
</span><span class="gp"> cp -f /nix/store/fscd8f71wmpwphcmi5mx8qnif2402x9m-run_setup.py nix_run_setup;</span><span class="w">
</span><span class="gp"> args="";</span><span class="w">
</span><span class="gp"> if [ -n "$</span>setupPyGlobalFlags<span class="s2">" ]; then
</span><span class="gp"> args+="$</span><span class="s2">setupPyGlobalFlags"</span><span class="p">;</span>
<span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [ -n "$</span>enableParallelBuilding<span class="s2">" ]; then
</span><span class="gp"> setupPyBuildFlags+=" --parallel $</span><span class="s2">NIX_BUILD_CORES"</span><span class="p">;</span>
<span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [ -n "$</span>setupPyBuildFlags<span class="s2">" ]; then
</span><span class="gp"> args+=" build_ext $</span><span class="s2">setupPyBuildFlags"</span><span class="p">;</span>
<span class="gp"> fi;</span><span class="w">
</span><span class="gp"> eval "/nix/store/bq1q4gk52gsx4fg4pf07f2kxqgazkcls-python3-3.10.12/bin/python3.10 nix_run_setup $</span>args bdist_wheel<span class="s2">";
</span><span class="gp"> runHook postBuild;</span><span class="w">
</span><span class="go"> echo "Finished executing setuptoolsBuildPhase"
}
</span><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="nv">$sourceRoot</span>
<span class="gp">$</span><span class="w"> </span>setuptoolsBuildPhase
<span class="gp">$</span><span class="w"> </span><span class="c"># this fails with the same error, obviously</span>
</code></pre></div></div>
<p>Bummer. Digging into this a bit, it looks like <code class="language-plaintext highlighter-rouge">nix develop</code> may just not be
<a href="https://github.com/NixOS/nix/issues/7265">quite ready for primetime</a> with some
differences in behavior compared to <code class="language-plaintext highlighter-rouge">nix-shell</code>.</p>
<p>Further, reading in <a href="https://github.com/NixOS/nix/issues/6202#issuecomment-1090498875">this issue</a>, it looks like sometimes a particular phase
may be a variable and other times a function, sometimes with one overriding the
other. This means that running <code class="language-plaintext highlighter-rouge">buildPhase</code> and <code class="language-plaintext highlighter-rouge">eval "$buildPhase"</code> may
produce totally different results, which is why <a href="https://nixos.org/manual/nixpkgs/stable/#sec-building-stdenv-package-in-nix-shell">the manual suggests</a>
running these interactively as <code class="language-plaintext highlighter-rouge">eval "${buildPhase:-buildPhase}"</code>.</p>
<p>I had to run an example to wrap my head around this:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="c"># define a variable `foo`</span>
<span class="gp">$</span><span class="w"> </span><span class="nv">foo</span><span class="o">=</span><span class="s1">'echo bar'</span>
<span class="gp">$</span><span class="w"> </span><span class="c"># define a function `foo`</span>
<span class="gp">$</span><span class="w"> </span>foo<span class="o">()</span> <span class="o">{</span> <span class="nb">echo </span>baz<span class="p">;</span> <span class="o">}</span>
<span class="gp">$</span><span class="w"> </span><span class="c"># `type` calls `foo` a function -- is this just because it was defined last?</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">type</span> <span class="nt">-t</span> foo
<span class="go">function
</span><span class="gp">$</span><span class="w"> </span><span class="c"># `eval` seems to run the variable, as if we had run `$ "${foo}"` or `$ $foo`</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">foo</span><span class="k">:-</span><span class="nv">foo</span><span class="k">}</span><span class="s2">"</span>
<span class="go">bar
</span><span class="gp">$</span><span class="w"> </span><span class="c"># plain `foo` runs the function</span>
<span class="gp">$</span><span class="w"> </span>foo
<span class="go">baz
</span><span class="gp">$</span><span class="w"> </span><span class="nv">foo</span><span class="o">=</span><span class="s1">'echo bar'</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">type</span> <span class="nt">-t</span> foo
<span class="go">function
</span><span class="gp">$</span><span class="w"> </span>asdf<span class="o">()</span> <span class="o">{</span> <span class="nb">echo </span>asdfasdf<span class="p">;</span> <span class="o">}</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">asdf</span><span class="k">:-</span><span class="nv">asdf</span><span class="k">}</span><span class="s2">"</span>
<span class="go">asdfasdf
</span><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> | <span class="nb">grep</span> <span class="s1">'^foo'</span>
<span class="go">foo='echo bar'
foo ()
</span></code></pre></div></div>
<p>I’m a little embarrassed that it wasn’t immediately obvious to me, but clearly
<code class="language-plaintext highlighter-rouge">eval "${foo:-foo}"</code> means “run the variable if it’s defined, otherwise run the
function,” so the variable should take priority if present.</p>
<p>Going back to our nix-shell, we can see which functions seem to be overridden:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> | <span class="nb">grep</span> <span class="nt">-o</span> <span class="s1">'^\w[^= ]\+'</span> | <span class="nb">sort</span> | <span class="nb">uniq</span> <span class="nt">-d</span>
<span class="go">buildPhase
checkPhase
installCheckPhase
installPhase
</span></code></pre></div></div>
<p>So it looks like <code class="language-plaintext highlighter-rouge">buildPhase</code> should be our first phase that we need to <code class="language-plaintext highlighter-rouge">echo
$buildPhase"</code> to see in this case; e.g. <code class="language-plaintext highlighter-rouge">type checkPhase</code> should work for the
preceding phases. Unfortunately, this doesn’t solve our issue, as manually
running each phase as <code class="language-plaintext highlighter-rouge">eval "${thePhase:-thePhase}"</code> also fails.</p>
<p>Looking closer at the errors, there are several errors about protobuf paths:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>source/third_party/protobuf/src/google/protobuf/implicit_weak_message.cc:31:10: fatal error: 'google/protobuf/implicit_weak_message.h' file not found
#include <google/protobuf/implicit_weak_message.h>
</code></pre></div></div>
<p>The file exists:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>find <span class="nb">.</span> <span class="nt">-name</span> implicit_weak_message.h
<span class="go">./third_party/protobuf/src/google/protobuf/implicit_weak_message.h
</span></code></pre></div></div>
<p>Interestingly, it looks like <code class="language-plaintext highlighter-rouge">nix-shell</code> <em>also</em> fails if I run from this <code class="language-plaintext highlighter-rouge">bash
--norc</code> environment:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix-shell <span class="nt">--pure</span> <span class="se">\</span>
<span class="go"> -I nixpkgs=https://github.com/nixos/nixpkgs/archive/nixpkgs-unstable.tar.gz \
-A python310Packages.pytorch \
</span><span class="gp"> '<nixpkgs></span><span class="s1">' \
</span><span class="go"> --command 'bash --norc'
</span></code></pre></div></div>
<p>I’m not sure what to make of this, other than that there may be some impurities
in this derivation that are requiring paths or environment variables outside
the nix store?</p>
<p>Let’s try cleaning the <code class="language-plaintext highlighter-rouge">nix develop</code> environment’s <code class="language-plaintext highlighter-rouge">PATH</code> of all non-<code class="language-plaintext highlighter-rouge">/nix</code>
elements:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python310Packages.pytorch
<span class="gp">$</span><span class="w"> </span><span class="c"># prints path delimited by linebreak instead of :</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">printf</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PATH</span><span class="p">//</span>:/<span class="s1">'\n'</span><span class="k">}</span><span class="se">\n</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="si">$(</span><span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span>: <span class="nt">-v</span> <span class="nv">ORS</span><span class="o">=</span>: <span class="s1">'$0 ~ /^\/nix\/.*/'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span><span class="c"># verify it worked</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">printf</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PATH</span><span class="p">//</span>:/<span class="s1">'\n'</span><span class="k">}</span><span class="se">\n</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span>genericBuild
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> ../outputs/dist/
<span class="go">torch-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
</span></code></pre></div></div>
<p>Huh, so getting rid of the non-nix paths allowed it to succeed. Let’s try with
the <code class="language-plaintext highlighter-rouge">--ignore-environment</code> flag:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.pytorch
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="si">$(</span><span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span>: <span class="nt">-v</span> <span class="nv">ORS</span><span class="o">=</span>: <span class="s1">'$0 ~ /^\/nix\/.*/'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span>genericBuild
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> ../outputs/dist/
<span class="go">torch-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
</span></code></pre></div></div>
<p>Sure enough that also works. I don’t know why building fails with <code class="language-plaintext highlighter-rouge">bash
--norc</code>.</p>
<p>I <em>think</em> that removing the non-<code class="language-plaintext highlighter-rouge">/nix</code> paths allows it to succeed because it
finds <code class="language-plaintext highlighter-rouge">xcrun</code> at <code class="language-plaintext highlighter-rouge">/usr/bin/xcrun</code>, which points it to the system MacOS SDK:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>xcrun <span class="nt">--sdk</span> macosx <span class="nt">--show-sdk-path</span>
<span class="go">/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk
</span></code></pre></div></div>
<p>where it finds the <code class="language-plaintext highlighter-rouge">MetalPerformanceShaders</code> framework and tries to enable
<code class="language-plaintext highlighter-rouge">MPS</code> (one can see <code class="language-plaintext highlighter-rouge">USE_MPS</code> is enabled in the build logs). However, this
framework (which seems to lives within nixpkgs at
<code class="language-plaintext highlighter-rouge">nixpkgs#darwin.apple_sdk.frameworks</code>) isn’t passed into the derivation, so it
fails with <code class="language-plaintext highlighter-rouge">ld: framework not found MetalPerformanceShaders</code>.</p>
<p>Let’s try patching nixpkgs to pass in this framework:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/pkgs/development/python-modules/torch/default.nix b/pkgs/development/python-modules/torch/default.nix
index 912628bf9497..eb452442201d 100644
</span><span class="gd">--- a/pkgs/development/python-modules/torch/default.nix
</span><span class="gi">+++ b/pkgs/development/python-modules/torch/default.nix
</span><span class="p">@@ -10,7 +10,7 @@</span>
# Build inputs
numactl,
<span class="gd">- Accelerate, CoreServices, libobjc,
</span><span class="gi">+ Accelerate, CoreServices, MetalPerformanceShaders, libobjc,
</span>
# Propagated build inputs
filelock,
<span class="p">@@ -27,6 +27,9 @@</span>
# this is also what official pytorch build does
mklDnnSupport ? !(stdenv.isDarwin && stdenv.isAarch64),
+ # Use MPS on M1 machines
<span class="gi">+ mpsSupport ? (stdenv.isDarwin && stdenv.isAarch64),
+
</span> # virtual pkg that consistently instantiates blas across nixpkgs
# See https://github.com/NixOS/nixpkgs/pull/83888
blas,
<span class="p">@@ -294,7 +297,8 @@</span> in buildPythonPackage rec {
++ lib.optionals rocmSupport [ openmp ]
++ lib.optionals (cudaSupport || rocmSupport) [ magma ]
++ lib.optionals stdenv.isLinux [ numactl ]
<span class="gd">- ++ lib.optionals stdenv.isDarwin [ Accelerate CoreServices libobjc ];
</span><span class="gi">+ ++ lib.optionals stdenv.isDarwin [ Accelerate CoreServices libobjc ]
+ ++ lib.optionals mpsSupport [ MetalPerformanceShaders ];
</span>
propagatedBuildInputs = [
cffi
<span class="gh">diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix
index 088b79d86c37..1b91ef99a445 100644
</span><span class="gd">--- a/pkgs/top-level/python-packages.nix
</span><span class="gi">+++ b/pkgs/top-level/python-packages.nix
</span><span class="p">@@ -12681,7 +12681,7 @@</span> self: super: with self; {
if pkgs.config.cudaSupport
then pkgs.magma-cuda-static
else pkgs.magma;
<span class="gd">- inherit (pkgs.darwin.apple_sdk.frameworks) Accelerate CoreServices;
</span><span class="gi">+ inherit (pkgs.darwin.apple_sdk.frameworks) Accelerate CoreServices MetalPerformanceShaders;
</span> inherit (pkgs.darwin) libobjc;
inherit (pkgs.llvmPackages_rocm) openmp;
};
</code></pre></div></div>
<p>With this patch, the build succeeds, but MPS is still not present:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.pytorch
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="si">$(</span><span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span>: <span class="nt">-v</span> <span class="nv">ORS</span><span class="o">=</span>: <span class="s1">'$0 ~ /^\/nix\/.*/'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span>genericBuild
<span class="gp">$</span><span class="w"> </span><span class="c"># ... build succeeds ...</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> ../outputs/dist
<span class="go">torch-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
</span></code></pre></div></div>
<p>It would save a lot of time to figure out how to run the phases independently
so that I can avoid needing to e.g. download / unpack the source code
repeatedly (which requires replaying any modifications repeatedly). Reading
<code class="language-plaintext highlighter-rouge">nix develop --help</code>, it seemed like I should be able to take advantage of
built-in flags to run the appropriate phase, for example starting with <code class="language-plaintext highlighter-rouge">nix
develop --unpack ~/git/nixpkgs#python310Packages.torch</code>. I struggled with this
for a day or two because i kept trying to run this from a clean working
directory (first running <code class="language-plaintext highlighter-rouge">cd $(mktemp -d)</code>) and ran into weird errors that
referenced the code code’s root directory (at <code class="language-plaintext highlighter-rouge">~/git/nixpkgs</code> in this case).
Reading the <code class="language-plaintext highlighter-rouge">nix develop</code> manpage didn’t help much.</p>
<p>I eventually figured out a few gotchas that – to me – were nonobvious:</p>
<ul>
<li>that these commands should be run from the flake’s root directory, which in
this case is the <code class="language-plaintext highlighter-rouge">nixpkgs</code> repo, which I’ve cloned to <code class="language-plaintext highlighter-rouge">~/git/nixpkgs</code></li>
<li>runnning <code class="language-plaintext highlighter-rouge">unpackPhase</code> twice in a row fails because <code class="language-plaintext highlighter-rouge">./source</code> (and maybe
<code class="language-plaintext highlighter-rouge">./outputs</code>) already exists</li>
<li>running with a specified phase does <strong>not</strong> drop you into a nix environment
for subsequent commands (such as manually running <code class="language-plaintext highlighter-rouge">genericBuild</code>)
<ul>
<li><code class="language-plaintext highlighter-rouge">nix develop ~/git/nixpkgs#python310Packages.torch</code> followed by <code class="language-plaintext highlighter-rouge">echo
$IN_NIX_SHELL</code> prints <code class="language-plaintext highlighter-rouge">impure</code></li>
<li><code class="language-plaintext highlighter-rouge">nix develop --configure ~/git/nixpkgs#python310Packages.torch</code> doesn’t
print anything</li>
<li>the same is true for e.g. <code class="language-plaintext highlighter-rouge">nix develop
~/git/nixpkgs#python310Packages.torch --command bash -c 'echo
$IN_NIX_SHELL'</code>, which prints <code class="language-plaintext highlighter-rouge">impure</code> when the command is run, but
if run a second time the result is empty, shows that you are not left in
that environment after the <code class="language-plaintext highlighter-rouge">nix develop</code> command completes</li>
</ul>
</li>
<li>running <code class="language-plaintext highlighter-rouge">nix develop -i ~/git/nixpkgs#python3Packages.pytorch --unpack</code> from
<em>any</em> directory creates <code class="language-plaintext highlighter-rouge">source</code> at <code class="language-plaintext highlighter-rouge">~/git/nixpkgs/source</code>, <strong>not</strong> in the
current directory
<ul>
<li>this really confused me, as the command leaves you in your current
directory, so unless you’re in the root directory, <code class="language-plaintext highlighter-rouge">./source</code> never
appears in your current working directory, but running the command twice
fails with an error suggesting that it exists</li>
</ul>
</li>
</ul>
<p>Demonstrating the last point, and how behavior differs between entering the
develop environment and manually running <code class="language-plaintext highlighter-rouge">unpackPhase</code> vs using flags like
<code class="language-plaintext highlighter-rouge">--unpack</code>:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="c"># Go to a clean temporary directory</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source ~/git/nixpkgs/source
<span class="go">ls: cannot access './source': No such file or directory
ls: cannot access '/Users/n8henrie/git/nixpkgs/source': No such file or directory
</span><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python3Packages.pytorch
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source ~/git/nixpkgs/source
<span class="go">ls: cannot access './source': No such file or directory
ls: cannot access '/Users/n8henrie/git/nixpkgs/source': No such file or directory
</span><span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">unpackPhase</span><span class="k">:-</span><span class="nv">unpackPhase</span><span class="k">}</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span><span class="c"># source is unpacked to PWD, not to ~/git/nixpkgs</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source ~/git/nixpkgs/source
<span class="go">ls: cannot access '/Users/n8henrie/git/nixpkgs/source': No such file or directory
drwxr-xr-x 80 n8henrie staff 2560 Jan 2 1980 ./source
</span></code></pre></div></div>
<p>contrast that with:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="c"># Go to a clean temporary directory</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source ~/git/nixpkgs/source
<span class="go">ls: cannot access './source': No such file or directory
ls: cannot access '/Users/n8henrie/git/nixpkgs/source': No such file or directory
</span><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python3Packages.pytorch <span class="nt">--unpack</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source ~/git/nixpkgs/source
<span class="go">ls: cannot access './source': No such file or directory
drwxr-xr-x 80 n8henrie staff 2560 Jan 2 1980 /Users/n8henrie/git/nixpkgs/source
</span></code></pre></div></div>
<p>Keep in mind, this means that with the former approach, one can change to a
clean workdir and repeat the exact same steps without error, but with the
second approach, you’ll get an error if you don’t first remove
<code class="language-plaintext highlighter-rouge">~/git/nixpkgs/source</code>:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span>
<span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python3Packages.pytorch <span class="nt">--unpack</span>
<span class="go">Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
unpacking source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
Cannot copy /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source to source: destination already exists!
Did you specify two "srcs" with the same "name"?
do not know how to unpack source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
</span></code></pre></div></div>
<p>Note that the error above doesn’t mention anything about <code class="language-plaintext highlighter-rouge">~/git/nixpkgs</code>, which
threw me off.</p>
<p>Other notes worth mentioning:</p>
<ul>
<li>the phases are <em>not</em> already defined within the context of <code class="language-plaintext highlighter-rouge">--command</code> (with
or without <code class="language-plaintext highlighter-rouge">-i</code>), so one needs to <code class="language-plaintext highlighter-rouge">source $stdenv/setup</code> to get much done</li>
<li><code class="language-plaintext highlighter-rouge">--command</code> runs within the context of the current working directory (not the
flake root)</li>
</ul>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span>
<span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python3Packages.pytorch <span class="se">\</span>
<span class="gp"> --command bash -c 'eval "$</span><span class="o">{</span>unpackPhase:-unpackPhase<span class="o">}</span><span class="s2">"'
</span><span class="go">Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
bash: line 1: unpackPhase: command not found
</span><span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python3Packages.pytorch <span class="nt">--command</span> bash <span class="nt">-c</span> <span class="s1">'
</span><span class="gp"> source $</span><span class="s1">stdenv/setup
</span><span class="gp"> eval "$</span><span class="s1">{unpackPhase:-unpackPhase}"
</span><span class="go">'
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
unpacking source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
source root is source
setting SOURCE_DATE_EPOCH to timestamp 315619200 of file source/version.txt
</span><span class="gp">$</span><span class="w"> </span><span class="nb">ls</span>
<span class="go">source
</span></code></pre></div></div>
<ul>
<li>commands like <code class="language-plaintext highlighter-rouge">--configure</code> don’t seem to respect overridden phases (such as a
custom <code class="language-plaintext highlighter-rouge">configurePhase</code>) and run their phase within the context of the flake’s
root directory. For example, I manually added to the torch derivation:
<code class="language-plaintext highlighter-rouge">configurePhase = ''echo "I am in configure!"; ls -l'';</code>. With that change:</li>
</ul>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="nt">--configure</span>
<span class="go">Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
no configure script, doing nothing
</span><span class="gp">$</span><span class="w"> </span><span class="c"># Add a fake executable named `./configure`:</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">touch</span> ~/git/nixpkgs/configure
<span class="gp">$</span><span class="w"> </span><span class="nb">chmod</span> +x <span class="o">!</span><span class="err">$</span>
<span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="nt">--configure</span>
<span class="go">Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
configure flags: --prefix=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/out --bindir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/out/bin --sbindir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/out/sbin --includedir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/dev/include --oldincludedir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/dev/include --mandir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/out/share/man --infodir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/out/share/info --docdir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/out/share/doc/python3.10-torch --libdir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/lib/lib --libexecdir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/lib/libexec --localedir=/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.aZbb96cCot/outputs/lib/share/locale
</span><span class="gp">$</span><span class="w">
</span><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="gp"> eval "$</span><span class="o">{</span>configurePhase:-configurePhase<span class="o">}</span><span class="s2">"
</span><span class="go"> '
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
I am in configure!
total 0
</span></code></pre></div></div>
<p>So I’m not exactly sure how <code class="language-plaintext highlighter-rouge">nix develop ... --configure</code> is supposed to work,
since it seems to look for <code class="language-plaintext highlighter-rouge">./configure</code> at <code class="language-plaintext highlighter-rouge">~/git/nixpkgs/configure</code>, while it
seems that <code class="language-plaintext highlighter-rouge">--unpack</code> is going to put it at <code class="language-plaintext highlighter-rouge">~/git/nixpkgs/source/configure</code>,
and <code class="language-plaintext highlighter-rouge">unpackPhase</code> puts it at <code class="language-plaintext highlighter-rouge">$PWD/source/configure</code>.</p>
<p>Perhaps the most reliable approach is just to run the following:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="gp"> eval "$</span><span class="o">{</span>unpackPhase:-unpackPhase<span class="o">}</span><span class="s2">"
</span><span class="gp"> cd $</span><span class="s2">sourceRoot
</span><span class="gp"> eval "$</span><span class="s2">{patchPhase:-patchPhase}"</span>
<span class="gp"> eval "$</span><span class="o">{</span>configurePhase:-configurePhase<span class="o">}</span><span class="s2">"
</span><span class="go"> '
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
unpacking source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
source root is source
setting SOURCE_DATE_EPOCH to timestamp 315619200 of file source/version.txt
I am in configure!
total 612
-rw-r--r-- 1 n8henrie staff 4103 Jan 2 1980 BUCK.oss
-rw-r--r-- 1 n8henrie staff 64723 Jan 2 1980 BUILD.bazel
</span><span class="gp">$</span><span class="w"> </span><span class="c"># ... lots of other files, truncated...</span>
</code></pre></div></div>
<p>Ok, so I think we’re back in business, realizing that we should probably either
work from the rootdir of the repo in question or manually <code class="language-plaintext highlighter-rouge">cd</code> there
beforehand. Note that we can’t necessarily <code class="language-plaintext highlighter-rouge">cd $sourceRoot</code> in the context of
our <code class="language-plaintext highlighter-rouge">--command</code>, because in this case <code class="language-plaintext highlighter-rouge">sourceRoot</code> is defined somewhere during
<code class="language-plaintext highlighter-rouge">unpackPhase</code>, so if we’re not running that phase, <code class="language-plaintext highlighter-rouge">sourceRoot</code> is undefined.</p>
<p>Let’s start again, from the beginning:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="gp"> eval "$</span><span class="o">{</span>unpackPhase:-unpackPhase<span class="o">}</span><span class="s2">"
</span><span class="gp"> cd $</span><span class="s2">sourceRoot
</span><span class="gp"> eval "$</span><span class="s2">{patchPhase:-patchPhase}"</span>
<span class="go"> '
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
unpacking source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
</span></code></pre></div></div>
<p>Cool, so far so good.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd source</span>
<span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="gp"> eval "$</span><span class="o">{</span>configurePhase:-configurePhase<span class="o">}</span><span class="s2">"
</span><span class="go"> '
Checking if build backend supports build_editable ... done
Preparing editable metadata (pyproject.toml) ... |
</span></code></pre></div></div>
<p>This seems to hang here indefinitely. SMH. Adding <code class="language-plaintext highlighter-rouge">export sourceRoot=.</code> doesn’t
help, nor does re-running <code class="language-plaintext highlighter-rouge">patchPhase</code>. Adding <code class="language-plaintext highlighter-rouge">eval
"${unpackPhase:-unpackPhase}"</code> works, which is a pain since that’s what I’m
trying to avoid running over and over again.</p>
<p>For whatever reason <code class="language-plaintext highlighter-rouge">export sourceRoot=source</code> works (and running from the
parent directory):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source
<span class="go">drwxr-xr-x 82 n8henrie staff 2624 Aug 18 11:03 ./source
</span><span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python310Packages.torch <span class="nt">--command</span> bash <span class="nt">-c</span> <span class="s1">'
</span><span class="gp"> source $</span><span class="s1">stdenv/setup
</span><span class="go"> export sourceRoot=source
</span><span class="gp"> eval "$</span><span class="o">{</span>patchPhase:-patchPhase<span class="o">}</span><span class="s2">"
</span><span class="gp"> eval "$</span><span class="s2">{configurePhase:-configurePhase}"</span>
<span class="go"> '
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
no configure script, doing nothing
</span></code></pre></div></div>
<p>Phew, finally on to the buildphase!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python310Packages.torch <span class="nt">--command</span> bash <span class="nt">-c</span> <span class="s1">'
</span><span class="gp"> source $</span><span class="s1">stdenv/setup
</span><span class="go"> export sourceRoot=source
</span><span class="gp"> eval "$</span><span class="o">{</span>buildPhase:-buildPhase<span class="o">}</span><span class="s2">"
</span><span class="go"> '
</span><span class="c">...
</span><span class="go">FileNotFoundError: [Errno 2] No such file or directory: 'setup.py'
</span><span class="c">...
</span></code></pre></div></div>
<p>Huh, so this expects to already be in <code class="language-plaintext highlighter-rouge">source</code>. Let’s try again:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd source</span>
<span class="gp">nix develop ~/git/nixpkgs#</span>python310Packages.torch <span class="nt">--command</span> bash <span class="nt">-c</span> <span class="s1">'
</span><span class="gp"> source $</span><span class="s1">stdenv/setup
</span><span class="go"> export sourceRoot=source
</span><span class="gp"> eval "$</span><span class="o">{</span>buildPhase:-buildPhase<span class="o">}</span><span class="s2">"
</span><span class="go">'
Preparing editable metadata (pyproject.toml) ... -
</span></code></pre></div></div>
<p>This hangs indefinitely at this step again. I am so frustrated. Leaving
<code class="language-plaintext highlighter-rouge">sourceRoot</code> undefined or <code class="language-plaintext highlighter-rouge">export sourceRoot=.</code> hangs at the same place. Going
through the <code class="language-plaintext highlighter-rouge">PATH</code> fixes from above and using <code class="language-plaintext highlighter-rouge">-i</code> make no difference.</p>
<p>However, if I both export <code class="language-plaintext highlighter-rouge">sourceRoot</code> <em>and</em> <code class="language-plaintext highlighter-rouge">cd</code> there, we make progress
(meaning I probably should go back and rerun <code class="language-plaintext highlighter-rouge">patchPhase</code> and <code class="language-plaintext highlighter-rouge">configurePhase</code>,
since they would have run in the wrong directory):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="go"> export sourceRoot=source
</span><span class="gp"> cd $</span>sourceRoot
<span class="gp"> eval "$</span><span class="o">{</span>patchPhase:-patchPhase<span class="o">}</span><span class="s2">"
</span><span class="gp"> eval "$</span><span class="s2">{configurePhase:-configurePhase}"</span>
<span class="go"> '
Executing setuptoolsShellHook
Finished executing setuptoolsShellHook
Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Using setuptoolsBuildPhase
Using setuptoolsShellHook
Sourcing pip-install-hook
Using pipInstallPhase
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
no configure script, doing nothing
</span><span class="gp">$</span><span class="w">
</span><span class="gp">$</span><span class="w">
</span><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="go"> export sourceRoot=source
</span><span class="gp"> cd $</span>sourceRoot
<span class="gp"> eval "$</span><span class="o">{</span>buildPhase:-buildPhase<span class="o">}</span><span class="s2">"
</span><span class="go"> '
... tries to build...
fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
<span class="go">... and many other similar errors ..
</span></code></pre></div></div>
<p>Oh man, not this again. Running without <code class="language-plaintext highlighter-rouge">-i</code> makes no difference.</p>
<p>Maybe the <code class="language-plaintext highlighter-rouge">PATH</code> workaround (note the extra <code class="language-plaintext highlighter-rouge">'</code> escaping)?</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="gp"> --command bash -c $</span><span class="s1">'
</span><span class="gp"> export PATH=$</span><span class="s1">(awk -v RS=: -v ORS=: \'</span><span class="nv">$0</span> ~ /^<span class="se">\/</span>nix<span class="se">\/</span>.<span class="k">*</span>/<span class="se">\'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="o">)</span>
<span class="gp"> source $</span>stdenv/setup
<span class="go"> export sourceRoot=source
</span><span class="gp"> cd $</span>sourceRoot
<span class="gp"> eval "$</span><span class="o">{</span>buildPhase:-buildPhase<span class="o">}</span><span class="s2">"
</span><span class="go"> '
</span><span class="c">...
</span><span class="go">fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
</code></pre></div></div>
<p>What is going on here?!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">rm</span> <span class="nt">-rf</span> ./source ./outputs
<span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="gp"> --command bash -c $</span><span class="s1">'
</span><span class="gp"> export PATH=$</span><span class="s1">(awk -v RS=: -v ORS=: \'</span><span class="nv">$0</span> ~ /^<span class="se">\/</span>nix<span class="se">\/</span>.<span class="k">*</span>/<span class="se">\'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="o">)</span>
<span class="gp"> source $</span>stdenv/setup
<span class="gp"> eval "$</span><span class="o">{</span>unpackPhase:-unpackPhase<span class="o">}</span><span class="s2">"
</span><span class="gp"> cd $</span><span class="s2">sourceRoot
</span><span class="gp"> eval "$</span><span class="s2">{patchPhase:-patchPhase}"</span>
<span class="gp"> eval "$</span><span class="o">{</span>configurePhase:-configurePhase<span class="o">}</span><span class="s2">"
</span><span class="gp"> eval "$</span><span class="s2">{buildPhase:-buildPhase}"</span>
<span class="go"> '
fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
</code></pre></div></div>
<p>Huh. So still stuck at the same error from <em>way</em> up above (which was like a
week ago now). Running interactively worked before, so I guess we’ll do that.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="si">$(</span><span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span>: <span class="nt">-v</span> <span class="nv">ORS</span><span class="o">=</span>: <span class="s1">'$0 ~ /^\/nix\/.*/'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">source</span> <span class="nv">$stdenv</span>/setup
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">sourceRoot</span><span class="o">=</span><span class="nb">source</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd source</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">buildPhase</span><span class="k">:-</span><span class="nv">buildPhase</span><span class="k">}</span><span class="s2">"</span>
<span class="go">fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
</code></pre></div></div>
<p>How about without <code class="language-plaintext highlighter-rouge">-i</code>?</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python310Packages.torch
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="si">$(</span><span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span>: <span class="nt">-v</span> <span class="nv">ORS</span><span class="o">=</span>: <span class="s1">'$0 ~ /^\/nix\/.*/'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">source</span> <span class="nv">$stdenv</span>/setup
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">sourceRoot</span><span class="o">=</span><span class="nb">source</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd source</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">buildPhase</span><span class="k">:-</span><span class="nv">buildPhase</span><span class="k">}</span><span class="s2">"</span>
<span class="go">fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
</code></pre></div></div>
<p>How about manually walking through everything, no <code class="language-plaintext highlighter-rouge">PATH</code> manipulation, and no
<code class="language-plaintext highlighter-rouge">-i</code>?</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">mktemp</span> <span class="nt">-d</span><span class="si">)</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span>nix develop ~/git/nixpkgs#python310Packages.torch
<span class="gp">$</span><span class="w"> </span><span class="nb">source</span> <span class="nv">$stdenv</span>/setup
<span class="go">Sourcing python-remove-tests-dir-hook
Sourcing python-catch-conflicts-hook.sh
Sourcing python-remove-bin-bytecode-hook.sh
Sourcing setuptools-build-hook
Sourcing pip-install-hook
Sourcing python-imports-check-hook.sh
Using pythonImportsCheckPhase
Sourcing python-namespaces-hook
Sourcing python-catch-conflicts-hook.sh
</span><span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">unpackPhase</span><span class="k">:-</span><span class="nv">unpackPhase</span><span class="k">}</span><span class="s2">"</span>
<span class="go">unpacking source archive /nix/store/dxqxfw4r00s0v033w7yam3bkblynrad7-source
source root is source
setting SOURCE_DATE_EPOCH to timestamp 315619200 of file source/version.txt
</span><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <span class="nv">$sourceRoot</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">patchPhase</span><span class="k">:-</span><span class="nv">patchPhase</span><span class="k">}</span><span class="s2">"</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">configurePhase</span><span class="k">:-</span><span class="nv">configurePhase</span><span class="k">}</span><span class="s2">"</span>
<span class="go">no configure script, doing nothing
</span><span class="gp">$</span><span class="w"> </span><span class="nb">eval</span> <span class="s2">"</span><span class="k">${</span><span class="nv">buildPhase</span><span class="k">:-</span><span class="nv">buildPhase</span><span class="k">}</span><span class="s2">"</span>
<span class="go">fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
</code></pre></div></div>
<p>Trying the same approach in <code class="language-plaintext highlighter-rouge">~/git/nixpkgs</code> gives the same <code class="language-plaintext highlighter-rouge">file not found</code>
error. We showed above that <code class="language-plaintext highlighter-rouge">genericBuild</code> runs <em>without</em> this error. What is
<code class="language-plaintext highlighter-rouge">genericBuild</code> doing that I’m missing?</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="nv">$genericBuild</span>
<span class="gp">$</span><span class="w"> </span><span class="c"># empty output -- it is not defined as a variable, just a function</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">type </span>genericBuild
<span class="go">genericBuild is a function
genericBuild ()
{
</span><span class="gp"> export GZIP_NO_TIMESTAMPS=1;</span><span class="w">
</span><span class="gp"> if [ -f "$</span><span class="o">{</span>buildCommandPath:-<span class="o">}</span><span class="s2">" ]; then
</span><span class="gp"> source "$</span><span class="s2">buildCommandPath"</span><span class="p">;</span>
<span class="gp"> return;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [ -n "$</span><span class="o">{</span>buildCommand:-<span class="o">}</span><span class="s2">" ]; then
</span><span class="gp"> eval "$</span><span class="s2">buildCommand"</span><span class="p">;</span>
<span class="gp"> return;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [ -z "$</span><span class="o">{</span>phases[<span class="k">*</span><span class="o">]</span>:-<span class="o">}</span><span class="s2">" ]; then
</span><span class="gp"> phases="$</span><span class="s2">{prePhases[*]:-} unpackPhase patchPhase </span><span class="k">${</span><span class="nv">preConfigurePhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> configurePhase </span><span class="k">${</span><span class="nv">preBuildPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> buildPhase checkPhase </span><span class="k">${</span><span class="nv">preInstallPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> installPhase </span><span class="k">${</span><span class="nv">preFixupPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> fixupPhase installCheckPhase </span><span class="k">${</span><span class="nv">preDistPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2"> distPhase </span><span class="k">${</span><span class="nv">postPhases</span><span class="p">[*]</span><span class="k">:-}</span><span class="s2">"</span><span class="p">;</span>
<span class="gp"> fi;</span><span class="w">
</span><span class="gp"> for curPhase in $</span><span class="o">{</span>phases[<span class="k">*</span><span class="o">]}</span><span class="p">;</span>
<span class="go"> do
</span><span class="gp"> if [[ "$</span>curPhase<span class="s2">" = unpackPhase && -n "</span><span class="k">${</span><span class="nv">dontUnpack</span><span class="k">:-}</span><span class="s2">" ]]; then
</span><span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span><span class="s2">curPhase"</span> <span class="o">=</span> patchPhase <span class="o">&&</span> <span class="nt">-n</span> <span class="s2">"</span><span class="k">${</span><span class="nv">dontPatch</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
<span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span>curPhase<span class="s2">" = configurePhase && -n "</span><span class="k">${</span><span class="nv">dontConfigure</span><span class="k">:-}</span><span class="s2">" ]]; then
</span><span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span><span class="s2">curPhase"</span> <span class="o">=</span> buildPhase <span class="o">&&</span> <span class="nt">-n</span> <span class="s2">"</span><span class="k">${</span><span class="nv">dontBuild</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
<span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span>curPhase<span class="s2">" = checkPhase && -z "</span><span class="k">${</span><span class="nv">doCheck</span><span class="k">:-}</span><span class="s2">" ]]; then
</span><span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span><span class="s2">curPhase"</span> <span class="o">=</span> installPhase <span class="o">&&</span> <span class="nt">-n</span> <span class="s2">"</span><span class="k">${</span><span class="nv">dontInstall</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
<span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span>curPhase<span class="s2">" = fixupPhase && -n "</span><span class="k">${</span><span class="nv">dontFixup</span><span class="k">:-}</span><span class="s2">" ]]; then
</span><span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span><span class="s2">curPhase"</span> <span class="o">=</span> installCheckPhase <span class="o">&&</span> <span class="nt">-z</span> <span class="s2">"</span><span class="k">${</span><span class="nv">doInstallCheck</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
<span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ "$</span>curPhase<span class="s2">" = distPhase && -z "</span><span class="k">${</span><span class="nv">doDist</span><span class="k">:-}</span><span class="s2">" ]]; then
</span><span class="gp"> continue;</span><span class="w">
</span><span class="gp"> fi;</span><span class="w">
</span><span class="gp"> if [[ -n $</span><span class="s2">NIX_LOG_FD ]]; then
</span><span class="gp"> echo "@nix { \"action\": \"setPhase\", \"phase\": \"$</span><span class="s2">curPhase</span><span class="se">\"</span><span class="s2"> }"</span> <span class="o">></span>&<span class="s2">"</span><span class="nv">$NIX_LOG_FD</span><span class="s2">"</span><span class="p">;</span>
<span class="gp"> fi;</span><span class="w">
</span><span class="gp"> showPhaseHeader "$</span>curPhase<span class="s2">";
</span><span class="gp"> dumpVars;</span><span class="w">
</span><span class="gp"> local startTime=$</span><span class="s2">(date +"</span>%s<span class="s2">");
</span><span class="gp"> eval "$</span><span class="s2">{!curPhase:-</span><span class="nv">$curPhase</span><span class="s2">}"</span><span class="p">;</span>
<span class="gp"> local endTime=$</span><span class="o">(</span><span class="nb">date</span> +<span class="s2">"%s"</span><span class="o">)</span><span class="p">;</span>
<span class="gp"> showPhaseFooter "$</span>curPhase<span class="s2">" "</span><span class="nv">$startTime</span><span class="s2">" "</span><span class="nv">$endTime</span><span class="s2">";
</span><span class="gp"> if [ "$</span><span class="s2">curPhase"</span> <span class="o">=</span> unpackPhase <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
<span class="gp"> [ -z "$</span><span class="o">{</span>sourceRoot<span class="o">}</span><span class="s2">" ] || chmod +x "</span><span class="k">${</span><span class="nv">sourceRoot</span><span class="k">}</span><span class="s2">";
</span><span class="gp"> cd "$</span><span class="s2">{sourceRoot:-.}"</span><span class="p">;</span>
<span class="gp"> fi;</span><span class="w">
</span><span class="go"> done
}
</span></code></pre></div></div>
<p>It looks like the <code class="language-plaintext highlighter-rouge">buildCommand</code> stuff can be ignored, and phases are not
overridden:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> | <span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^buildCommand'</span> <span class="nt">-e</span> <span class="s1">'^phases'</span>
</code></pre></div></div>
<p>Going through the default phases, it looks like we might be missing a few!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">declare</span> | <span class="nb">grep</span> <span class="s1">'^preConfigurePhase'</span>
<span class="go">preConfigurePhases=' updateAutotoolsGnuConfigScriptsPhase'
</span></code></pre></div></div>
<p>Let’s see if we can find all the defined phases:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">type </span>genericBuild | <span class="nb">awk</span> <span class="nt">-F</span><span class="o">=</span> <span class="s1">'/phases=/ { print $2 }'</span> | <span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span><span class="s1">' +'</span> <span class="s1">'{ gsub(/[^[:alnum:]]/, ""); print}'</span> | <span class="nb">sort</span> <span class="nt">-u</span>
<span class="go">buildPhase
checkPhase
configurePhase
distPhase
fixupPhase
installCheckPhase
installPhase
patchPhase
postPhases
preBuildPhases
preConfigurePhases
preDistPhases
preFixupPhases
preInstallPhases
prePhases
unpackPhase
</span><span class="gp">$</span><span class="w"> </span><span class="nv">phasenames</span><span class="o">=(</span><span class="si">$(</span><span class="o">!!</span><span class="si">)</span><span class="o">)</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${#</span><span class="nv">phasenames</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span>
<span class="go">16
</span><span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">phasenames</span><span class="p">[0]</span><span class="k">}</span><span class="s2">"</span>
<span class="go">buildPhase
</span><span class="gp">$</span><span class="w"> </span><span class="k">for </span>pn <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="nv">phasenames</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do </span><span class="nb">declare</span> | <span class="nb">grep</span> <span class="nt">-o</span> <span class="s2">"^</span><span class="k">${</span><span class="nv">pn</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">done</span> | <span class="nb">sort</span> <span class="nt">-u</span>
<span class="go">buildPhase
checkPhase
configurePhase
distPhase
fixupPhase
installCheckPhase
installPhase
patchPhase
preConfigurePhases
preDistPhases
preFixupPhases
unpackPhase
</span></code></pre></div></div>
<p>Wow, it looks like there are a <em>lot</em> of phases that are defined, but which we
haven’t been using. So perhaps instead of trying to run all of these manually,
if our goal is to avoid unpacking the source every time, we should take
advantage of the <code class="language-plaintext highlighter-rouge">dontUnpack</code> variable, which is checked prior to running that
phase.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop .#python310Packages.torch
<span class="gp">$</span><span class="w"> </span><span class="c"># source already exists:</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">ls</span> <span class="nt">-ld</span> ./source
<span class="go">drwxr-xr-x 82 n8henrie staff 2624 Aug 18 12:23 ./source
</span><span class="gp">$</span><span class="w"> </span><span class="nv">dontUnpack</span><span class="o">=</span>1
<span class="gp">$</span><span class="w"> </span>genericBuild
<span class="go">FileNotFoundError: [Errno 2] No such file or directory: 'setup.py'
</span></code></pre></div></div>
<p>Ah, I keep forgetting about <code class="language-plaintext highlighter-rouge">sourceRoot</code> being defined in <code class="language-plaintext highlighter-rouge">unpackPhase</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nix develop .#python310Packages.torch
$ cd source
$ dontUnpack=1
$ genericBuild
$ # building proceeds
</code></pre></div></div>
<p>Hooray!</p>
<p>However, in the output we can see <code class="language-plaintext highlighter-rouge">sdk version: 13.3</code> – which means it’s using
my local system’s SDK, since that SDK version doesn’t exist in nixpkgs yet:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix <span class="nb">eval</span> <span class="nt">--json</span> <span class="nt">--apply</span> <span class="s1">'builtins.attrNames'</span> nixpkgs#darwin |
<span class="go"> jq -r '.[] | select(contains("sdk"))'
apple_sdk
apple_sdk_10_12
apple_sdk_11_0
</span></code></pre></div></div>
<p>Running with <code class="language-plaintext highlighter-rouge">-i</code> gives me the same result, so I probably need to clean my
path. Maybe I can try again with the <code class="language-plaintext highlighter-rouge">--norc</code> approach, now that the <code class="language-plaintext highlighter-rouge">phases</code>
issue has been figured out?</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> .#python310Packages.torch <span class="nt">--command</span> bash <span class="nt">--norc</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">source</span> <span class="nv">$stdenv</span>/setup
<span class="gp">$</span><span class="w"> </span><span class="nb">cd source</span>
<span class="gp">$</span><span class="w"> </span><span class="nv">dontUnpack</span><span class="o">=</span>1
<span class="gp">$</span><span class="w"> </span>genericBuild
<span class="go">fatal error: 'google/protobuf/any.h' file not found
</span><span class="gp">#</span>include <google/protobuf/any.h>
</code></pre></div></div>
<p>Nope, I guess not.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> .#python310Packages.torch
<span class="gp">$</span><span class="w"> </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="si">$(</span><span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">RS</span><span class="o">=</span>: <span class="nt">-v</span> <span class="nv">ORS</span><span class="o">=</span>: <span class="s1">'$0 ~ /^\/nix\/.*/'</span> <span class="o"><<<</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">"</span><span class="si">)</span>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd source</span>
<span class="gp">$</span><span class="w"> </span><span class="nv">dontUnpack</span><span class="o">=</span>1
<span class="gp">$</span><span class="w"> </span>genericBuild
<span class="c">...
</span><span class="go">-- MPS: unable to get MacOS sdk version
</span><span class="c">...
</span></code></pre></div></div>
<p>Huh, looking through the <code class="language-plaintext highlighter-rouge">nix develop</code> source, I found a new command: <code class="language-plaintext highlighter-rouge">nix
print-dev-env</code>. Looks handy, seems like it should let you redirect the build
environment to a file which you can then source in a regular shell.</p>
<p>Well, having exhausted all the troubleshooting steps I could come up with, I
<a href="https://discourse.nixos.org/t/nix-develop-fails-with-command-bash-norc">started a thread on the NixOS
discourse</a>,
and so far nobody has pointed out an obvious mistake in my approach.</p>
<p>For the moment, this seems to work for a rebuild without repeating the “get the
source code” step.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>nix develop <span class="nt">-i</span> ~/git/nixpkgs#python310Packages.torch <span class="se">\</span>
<span class="go"> --command bash -c '
</span><span class="gp"> source $</span>stdenv/setup
<span class="go"> dontUnpack=1
export sourceRoot=source
</span><span class="gp"> cd $</span>sourceRoot
<span class="go"> genericBuild
'
</span></code></pre></div></div>
<p>Unfortunately, this is roughly where our adventure ends. With the below patch,
I override <code class="language-plaintext highlighter-rouge">xcrun</code> with nixpkgs’ <code class="language-plaintext highlighter-rouge">xcbuild.xcrun</code>, which should give it paths to
the nixpkgs-based frameworks that I have included in the build inputs, such as
<code class="language-plaintext highlighter-rouge">MetalPerformanceShaders</code> and <code class="language-plaintext highlighter-rouge">MetalPerformanceShadersGraph</code>. Unfortunately, it
complains that the SDK is not new enough (requires MacOS SDK >=12.3, nixpkgs
currently only provides 11).</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/pkgs/development/python-modules/torch/default.nix b/pkgs/development/python-modules/torch/default.nix
index 912628bf9497..e022974f5687 100644
</span><span class="gd">--- a/pkgs/development/python-modules/torch/default.nix
</span><span class="gi">+++ b/pkgs/development/python-modules/torch/default.nix
</span><span class="p">@@ -10,7 +10,7 @@</span>
# Build inputs
numactl,
<span class="gd">- Accelerate, CoreServices, libobjc,
</span><span class="gi">+ Accelerate, CoreServices, MetalPerformanceShaders, MetalPerformanceShadersGraph, libobjc, xcbuild,
</span>
# Propagated build inputs
filelock,
<span class="p">@@ -27,6 +27,9 @@</span>
# this is also what official pytorch build does
mklDnnSupport ? !(stdenv.isDarwin && stdenv.isAarch64),
+ # Use MPS on M1 machines
<span class="gi">+ mpsSupport ? (stdenv.isDarwin && stdenv.isAarch64),
+
</span> # virtual pkg that consistently instantiates blas across nixpkgs
# See https://github.com/NixOS/nixpkgs/pull/83888
blas,
<span class="p">@@ -190,6 +193,9 @@</span> in buildPythonPackage rec {
substituteInPlace third_party/pocketfft/pocketfft_hdronly.h --replace '#if __cplusplus >= 201703L
inline void *aligned_alloc(size_t align, size_t size)' '#if __cplusplus >= 201703L && 0
inline void *aligned_alloc(size_t align, size_t size)'
<span class="gi">+ '' + lib.optionalString mpsSupport ''
+ substituteInPlace CMakeLists.txt \
+ --replace 'xcrun' "${xcbuild.xcrun}/bin/xcrun"
</span> '';
preConfigure = lib.optionalString cudaSupport ''
<span class="p">@@ -294,7 +300,8 @@</span> in buildPythonPackage rec {
++ lib.optionals rocmSupport [ openmp ]
++ lib.optionals (cudaSupport || rocmSupport) [ magma ]
++ lib.optionals stdenv.isLinux [ numactl ]
<span class="gd">- ++ lib.optionals stdenv.isDarwin [ Accelerate CoreServices libobjc ];
</span><span class="gi">+ ++ lib.optionals stdenv.isDarwin [ Accelerate CoreServices libobjc ]
+ ++ lib.optionals mpsSupport [ MetalPerformanceShaders MetalPerformanceShadersGraph ];
</span>
propagatedBuildInputs = [
cffi
</code></pre></div></div>
<p>At this point, I made one more desperate attempt to <em>force</em> it to try to use
the nixpkgs MPS libraries, changing only the following from the above diff:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+ '' + lib.optionalString mpsSupport ''
+ substituteInPlace CMakeLists.txt \
+ --replace 'bash -c "xcrun ' 'bash -c "${xcbuild.xcrun}/bin/xcrun ' \
+ --replace '"MPS_FOUND" OFF)' '"MPS_FOUND" ON)'
</span></code></pre></div></div>
<p>Re-running everything with this patch shows that it seems to work (it <em>tries</em>
to use <code class="language-plaintext highlighter-rouge">MPS</code>):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>rg <span class="nt">-i</span> <span class="s1">'^--.*( use_| )mps'</span> build.log
<span class="go">29:-- sdk version: 11.0, mps supported: OFF
30:-- MPSGraph framework not found
572:-- USE_MPS : ON
619:-- sdk version: 11.0, mps supported: OFF
620:-- MPSGraph framework not found
912:-- USE_MPS : ON
</span></code></pre></div></div>
<p>but the build ultimately fails with an enormous number of not-unexpected errors
about MPS not having the same API it is expecting, in keeping with it being
brought in from a far too old SDK.</p>
<p>Here’s where I leave things for now, a little discouraged, but I have learned a
fair amount about the nix build process in the meantime. Hope this is
educational to someone out there!</p>
Wed, 02 Aug 2023 12:53:23 -0600
https://n8henrie.com/2023/08/hacking-on-nixpkgs-with-nix-develop/
https://n8henrie.com/2023/08/hacking-on-nixpkgs-with-nix-develop/
linux
Mac OSX
MacOS
nix
tech
tech
-
Write a Firefox Extension in Python
<p><strong>Bottom Line:</strong> One can write a Firefox extension in (mostly) Python via Pyodide.
<!--more--></p>
<p><strong>Disclaimer:</strong> I’m not a huge fan of JavaScript, and I don’t use it much, so I
am likely not following best practices. I’ve also never written a Firefox
extension, so the below is pretty bare-bones, but hopefully enough to get you
off the ground.</p>
<p>With the recent amazing advancements in Python and wasm, brought to us in
large part by way of <a href="https://pyodide.org/en/stable/">Pyodide</a> (<a href="https://github.com/pyodide/pyodide/">repo</a>) and <a href="https://pyscript.net/">PyScript</a> (<a href="https://github.com/pyscript/pyscript">repo</a>), I
thought it would be interesting to try to build a Firefox extension in Python.</p>
<p>I found a very helpful <a href="https://medium.com/pythoniq/write-chrome-extensions-in-python-6c6b0e2e1573">Medium article</a> and corresponding <a href="https://github.com/PFython/pyscript-local-runtime">GitHub repo</a>
for building a Chrome extension in Python, which provided some examples and a
framework. I wanted to do things a little differently (for no good reason) –
specifically, I didn’t want to rely on an <code class="language-plaintext highlighter-rouge">html</code>-based pop-up page, which that
project uses to load all the JavaScript files.</p>
<p>I struggled to get PyScript to work in the way I wanted, but I was eventually
able to get Pyodide to help me create an extension that contains its own Python
wasm runtime (and therefore doesn’t need to load it from their web-hosted
version and should be a little snappier to load in some cases).</p>
<p>To try out the toy extension:</p>
<ol>
<li>clone the example repo, which is at <a href="https://github.com/n8henrie/python_firefox_ext.git">https://github.com/n8henrie/python_firefox_ext.git</a></li>
<li>inspect (for safety) <code class="language-plaintext highlighter-rouge">setup.sh</code> (MacOS / probably Linux) or <code class="language-plaintext highlighter-rouge">setup.ps1</code>
(Windows) and afterwards run them; this will download the necessary files
from Pyodide so you can embed them in your extension.
<ul>
<li>You can also consider changing the script to download the debug version
during development</li>
</ul>
</li>
<li>open Firefox to <code class="language-plaintext highlighter-rouge">about:debugging</code></li>
<li>click the link for <code class="language-plaintext highlighter-rouge">This Firefox</code></li>
<li>click <code class="language-plaintext highlighter-rouge">Load Temporary Add-on...</code></li>
<li>Select the <code class="language-plaintext highlighter-rouge">manifest.json</code> from the cloned repo</li>
</ol>
<p>In short, I found that you can import <code class="language-plaintext highlighter-rouge">pyodide.js</code> in your <code class="language-plaintext highlighter-rouge">manifest.json</code>
using a local path. That defines a function <code class="language-plaintext highlighter-rouge">loadPyodide</code>, which can accept an
object with an <code class="language-plaintext highlighter-rouge">indexURL</code> argument. <code class="language-plaintext highlighter-rouge">manifest.json</code> then loads a local
JavaScript file, <code class="language-plaintext highlighter-rouge">hello.js</code>, which calls <code class="language-plaintext highlighter-rouge">loadPyodide</code> with <code class="language-plaintext highlighter-rouge">indexURL</code> set to
a local path to the rest of the necessary files.</p>
<p>From here, loading and running some Python is a little janky, but seems to work
– I just read the contents of <code class="language-plaintext highlighter-rouge">hello.py</code> and pass it (as a string) to
<code class="language-plaintext highlighter-rouge">pyodide.runPython</code>. One reason I wanted to structure things this way is it
allows me to use my usual Python workflow to write / edit / lint / format the
Python code.</p>
<p>In <code class="language-plaintext highlighter-rouge">hello.py</code>, I demonstrate very basic functionality for both a
<code class="language-plaintext highlighter-rouge">content_script</code> extension, which can modify the content one sees, as well as a
background extension, which has access to inspect, open, and close tabs (among
many other things). To demonstrate the <code class="language-plaintext highlighter-rouge">content_script</code> functionality, the
extension sets a red border around the currently open webpage. In
<code class="language-plaintext highlighter-rouge">manifest.json</code>, I restrict the extension to only run this content script on
<code class="language-plaintext highlighter-rouge">n8henrie.com</code>, so if you open a page to my site you should see a red border.</p>
<p>For the background script functionality, I print out a list of currently open
tabs into the devtools console; to view this, click the <code class="language-plaintext highlighter-rouge">Inspect</code> button in
Firefox’s <code class="language-plaintext highlighter-rouge">about:debugging</code> tab, then go to the <code class="language-plaintext highlighter-rouge">Console</code> tab. I also print out
the current webpage’s URL, and open a new page to this blog post (which should
get a red border).</p>
<p>This was a fun project, if a little frustrating to sort out (given my
unfamiliarity with JavaScript). If you have any recommendations or other
example projects, I’d love to hear about it in the comments below!</p>
Sat, 03 Jun 2023 08:43:58 -0600
https://n8henrie.com/2023/06/write-a-firefox-extension-in-python/
https://n8henrie.com/2023/06/write-a-firefox-extension-in-python/
firefox
javascript
python
tech
-
Persistent VMs on pfSense with vm-bhyve
<p><strong>Bottom Line:</strong> Configure your VMs to start and run automatically on pfSense
with vm-bhyve.
<!--more--></p>
<p><strong>20251104 Update</strong>: Changed <code class="language-plaintext highlighter-rouge">/boot/loader.conf</code> to <code class="language-plaintext highlighter-rouge">/boot/loader.conf.local</code> thanks to @gmipf, based on <a href="https://docs.netgate.com/pfsense/en/latest/config/advanced-tunables.html">https://docs.netgate.com/pfsense/en/latest/config/advanced-tunables.html</a></p>
<p>If you’re trying to get a Linux VM running under bhyve on pfSense, I strongly
recommend that you start with <a href="/2023/03/running-nixos-and-ubuntu-vms-on-pfsense-via-bhyve/">my first post on the topic</a>. Once you have
things running interactively, it’s time to try to get the VM to start and run
automatically.</p>
<p><a href="https://github.com/churchers/vm-bhyve">vm-bhyve</a> is designed to help make the process a little easier, and it’s
available in the default pfSense repo, which is highly convenient.</p>
<p>First off, it’s important to realize that pfSense apparently <a href="https://forum.netgate.com/topic/158096/what-is-the-pfsense-alternative-for-etc-rc-conf-in-freebsd-is-that-etc-rc-conf-local/2?_=1682355481742&lang=en-US">does not use
the standard FreeBSD rc init
system</a>;
I would have saved a lot of time had I realized this earlier, as it means that
the <a href="https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html#virtualization-bhyve-onboot">default FreeBSD instructions on this
topic</a>, which advise adding a number of settings to <code class="language-plaintext highlighter-rouge">/etc/rc.conf</code>, <strong>won’t work</strong>.</p>
<p>Further, at boot time, you <em>can</em> automatically run scripts in
<code class="language-plaintext highlighter-rouge">/usr/local/etc/rc.d/</code>, but they
<a href="https://docs.netgate.com/pfsense/en/latest/development/boot-commands.html#shell-script-option">must end in <code class="language-plaintext highlighter-rouge">.sh</code> and be executable</a>.</p>
<p>With these two facts in mind, the rest wasn’t too difficult. As a reminder, I’m
using <code class="language-plaintext highlighter-rouge">bash</code> as my shell, and all commands below are being run as root (I’m
using <code class="language-plaintext highlighter-rouge">$</code> instead of <code class="language-plaintext highlighter-rouge">#</code> in codeblocks below for better markdown syntax
highlighting.)</p>
<p>You’ll probably want to keep <a href="https://github.com/churchers/vm-bhyve">vm-bhyve’s GitHub page</a> open to
references its documentation.</p>
<ol>
<li>Install <code class="language-plaintext highlighter-rouge">vm-bhyve</code> with <code class="language-plaintext highlighter-rouge">pkg install vm-bhyve</code></li>
<li>Using the web interface, configure pfSense to load the necessary kernel modules on boot by adding the following to <code class="language-plaintext highlighter-rouge">/boot/loader.conf.local</code> (following the <a href="https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html">official FreeBSD instructions</a>, though I didn’t need <code class="language-plaintext highlighter-rouge">nmdm_load</code>):
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vmm_load="YES"
if_bridge_load="YES"
if_tap_load="YES"
</code></pre></div> </div>
</li>
<li>Configure pfSense to bring up your TAP interface on boot:
<ul>
<li><code class="language-plaintext highlighter-rouge">System</code> -> <code class="language-plaintext highlighter-rouge">Advanced</code> -> <code class="language-plaintext highlighter-rouge">System Tunables</code> (<code class="language-plaintext highlighter-rouge">/system_advanced_sysctl.php</code>)</li>
<li><code class="language-plaintext highlighter-rouge">+ New</code></li>
<li>Tunable: <code class="language-plaintext highlighter-rouge">net.link.tap.up_on_open</code></li>
<li>Value: <code class="language-plaintext highlighter-rouge">1</code></li>
<li>Description: <code class="language-plaintext highlighter-rouge">Open TAP on boot for vm-bhyve</code></li>
</ul>
</li>
<li>This is probably a good time to reboot, which should load / activate the
above settings and make sure they are working</li>
<li>Symlink <code class="language-plaintext highlighter-rouge">vm-bhyve</code>’s rc script to something pfSense will run:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ln -s /usr/local/etc/rc.d/vm /usr/local/etc/rc.d/vm.sh
</code></pre></div> </div>
</li>
<li>Edit <code class="language-plaintext highlighter-rouge">/etc/rc.conf.local</code> and add <code class="language-plaintext highlighter-rouge">vm-bhyve</code>’s config. The contents of mine
are the following lines, yours may vary:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vm_enable="YES"
vm_dir="zfs:pfSense/vm"
vm_list="nixos0"
vm_delay="5"
</code></pre></div> </div>
</li>
<li>You may need to remove the remnants of the zfs virtual drive created in the
prior post:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>zfs destroy pfSense/vm/nixos0
</code></pre></div> </div>
</li>
<li>Create and populate vm-bhyve’s directory structure: <code class="language-plaintext highlighter-rouge">$ vm init</code></li>
<li>Copy the UEFI file we downloaded in the last post to a place that <code class="language-plaintext highlighter-rouge">vm-bhyve</code>
will look for it:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cp </span>BHYVE_UEFI.fd /pfSense/vm/.config/
</code></pre></div> </div>
</li>
<li>Next you need to configure the VM
<ol>
<li>Start by looking through a few of the samples:
<ul>
<li><a href="https://github.com/churchers/vm-bhyve/blob/master/sample-templates/default.conf">https://github.com/churchers/vm-bhyve/blob/master/sample-templates/default.conf</a></li>
<li><a href="https://github.com/churchers/vm-bhyve/blob/master/sample-templates/config.sample">https://github.com/churchers/vm-bhyve/blob/master/sample-templates/config.sample</a></li>
<li><a href="https://github.com/churchers/vm-bhyve/blob/master/sample-templates/ubuntu.conf">https://github.com/churchers/vm-bhyve/blob/master/sample-templates/ubuntu.conf</a></li>
</ul>
</li>
<li>Next, I downloaded and then edited the default config:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp"> $</span><span class="w"> </span>curl <span class="s1">'https://raw.githubusercontent.com/churchers/vm-bhyve/master/sample-templates/default.conf'</span> <span class="o">></span> /pfSense/vm/.templates/nixos.conf
<span class="gp"> $</span><span class="w"> </span>vim /pfSense/vm/.templates/nixos.conf
</code></pre></div> </div>
</li>
</ol>
<p>My config ultimately ended up looking like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> loader="uefi-custom"
cpu=1
memory=1024M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_dev="sparse-zvol"
disk0_name="nixos0"
disk0_size="16G"
graphics="no"
</code></pre></div> </div>
</li>
<li>I had trouble with the vm-bhyve console until I configured it to run in
tmux; if you’re not a tmux user maybe skip this: <code class="language-plaintext highlighter-rouge">$ vm set console="tmux"</code></li>
<li>Create a “manually” managed switch (since we’ve configured it in pfSense in
the prior post, and pfSense will manage it)
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>vm switch create <span class="nt">-t</span> manual <span class="nt">-b</span> bridge0 public
</code></pre></div> </div>
</li>
<li>Create a VM named <code class="language-plaintext highlighter-rouge">nixos0</code> based on your customized <code class="language-plaintext highlighter-rouge">nixos</code> template:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>vm create <span class="nt">-t</span> nixos nixos0
</code></pre></div> </div>
</li>
<li>Tell <code class="language-plaintext highlighter-rouge">vm-bhyve</code> to download the installer ISO:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>vm iso https://releases.nixos.org/nixos/22.11/nixos-22.11.2979.47c00341629/nixos-minimal-22.11.2979.47c00341629-x86_64-linux.iso
</code></pre></div> </div>
</li>
<li>Install in the foreground using your currently active terminal session. Note
that immediately after running this command I had to hold down the down
arrow key and <em>keep tapping it</em> for 15 seconds or so, during which the
entire SSH session seemed be frozen. Afterwards, it comes up with the option
to go into <code class="language-plaintext highlighter-rouge">Accessibility</code> and redirect its output to the serial console,
just like in the last post.
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>vm <span class="nb">install</span> <span class="nt">-f</span> nixos0 nixos-minimal-22.11.2979.47c00341629-x86_64-linux.iso
</code></pre></div> </div>
</li>
<li>At this point, you should be able to get a shell, <code class="language-plaintext highlighter-rouge">sudo su</code> to elevate,
<code class="language-plaintext highlighter-rouge">systemctl start sshd</code>, <code class="language-plaintext highlighter-rouge">passwd</code> to set a root password, <code class="language-plaintext highlighter-rouge">ip addr</code> to get
your IP address, and you’re off to the races! (Don’t forget to add
<code class="language-plaintext highlighter-rouge">boot.kernelParams = [ "console=ttyS0" ];</code> if using NixOS, and you’ll need
SSH access configured so you can access the machine once it’s booting in the
background)</li>
<li>Once you’ve completed your installation, see if your VM comes up (make sure
to give it a minute):
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>vm poweroff nixos0
<span class="gp">$</span><span class="w"> </span>vm start <span class="nt">-f</span> nixos0
</code></pre></div> </div>
</li>
<li>If that works, try SSH access. If that also works, try rebooting pfSense –
your VM should automatically start in the background a few seconds after
bootup is complete, at which point you should be able to connect via SSH
(check your pfSense logs for the IP address, for which you might want to add
a DHCP reservation at this point).</li>
<li>If you’ve made it to this point, everything seems to be working.
Congratulations! All that’s left is to make a snapshot of your working VM,
which I suppose you should be able to <code class="language-plaintext highlighter-rouge">zfs send</code> to another machine, or to
which you can roll back if something goes wrong in the future:</li>
</ol>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>vm stop nixos0
<span class="gp">$</span><span class="w"> </span>vm snapshot nixos0@booting
<span class="gp">$</span><span class="w"> </span>vm info nixos0
<span class="go">vm info nixos0
------------------------
Virtual Machine: nixos0
------------------------
state: stopped
datastore: default
loader: uefi-custom
uuid: 0b49b710-e2ce-11ed-b922-00e0672a504a
uefi: default
cpu: 1
memory: 1024M
network-interface
number: 0
emulation: virtio-net
virtual-switch: public
fixed-mac-address: 58:9c:fc:03:dc:eb
fixed-device: -
virtual-disk
number: 0
device-type: sparse-zvol
emulation: virtio-blk
options: -
system-path: /dev/zvol/pfSense/vm/nixos0/nixos0
bytes-size: 17179869184 (16.000G)
bytes-used: 1650454528 (1.537G)
snapshots
pfSense/vm/nixos0@booting 0 Mon Apr 24 15:25 2023
pfSense/vm/nixos0/nixos0@booting 0 Mon Apr 24 15:25 2023
</span></code></pre></div></div>
<p>I hope you’ve found this useful – I still have a lot to learn, so if you see
any major missteps or recommendations for improvement please let me know in the
comments.</p>
Thu, 16 Mar 2023 10:40:20 -0600
https://n8henrie.com/2023/03/persistent-vms-on-pfsense-with-vmbhyve/
https://n8henrie.com/2023/03/persistent-vms-on-pfsense-with-vmbhyve/
pfsense
freebsd
linux
nix
tech
-
Running NixOS and Ubuntu VMs on pfSense via bhyve
<p><strong>Bottom Line:</strong> With some effort, I got VMs running on my pfSense
router/firewall.
<!--more--></p>
<h2 id="preface">Preface</h2>
<p>Being a hobbyist without much experience in networking, this project took me a
fair amount of effort over a period of weeks. It is mostly proof-of-concept,
and I think there are good reasons <em>not</em> to use your firewall to host virtual
machines. I’m sure this could impair performanc or even compromise the security
and integrity of your network. If you decide to give this a shot, <em>caveat
emptor</em>! If you have recommendations for improving the process, please let me
know in the comments.</p>
<p>NB: I’m booting the VM via UEFI and using ZFS for storage, so you may need to
make adjustments if this is incompatible with your setup.</p>
<p><strong>Spoiler alert:</strong> You might want to scroll down and read the <a href="#Fixing DNS"><code class="language-plaintext highlighter-rouge">Fixing
DNS</code></a> part <em>first</em>, since it requires changing a setting that
requires a reboot, and you basically have to start from scratch after a reboot.</p>
<h2 id="introduction">Introduction</h2>
<p>I started using pfSense firewalls a year or so ago, and I’ve been overall very
happy with them. I put one on a <a href="https://amzn.to/3SSnMOA">pre-built device from Amazon</a> that ran a
couple hundred dollars, and after warming up to the configuration and a few
power-user options, I bought a used Lenovo M93P for $80 US, designed and
printed a <a href="https://www.thingiverse.com/thing:5382521">custom bracket for a few SSDs</a>, and installed pfSense on a
mirrored ZFS root there as well. My internet speeds went from ~300 mbps (I
always forget whether that’s supposed to be capitalized or not) with the
commercial router I’d been using to a full 1 gbps with the cheaper used
hardware setup, which was fantastic! I also have unbound doing local DNS
resolution for performance and privacy, pfblocker-ng for network-wide
adblocking and improved security, tailscale and wireguard, automatic config
backups, bandwidthd, iperf… lots of great stuff.</p>
<p>My only beef with pfSense is that I don’t know FreeBSD as well as I do Linux,
so when I want to do something simple like set up a little python service, I’m
kind of lost. Because pfSense is somewhat locked down (for security purposes),
it’s harder than plain FreeBSD to <a href="/2023/01/quickly-add-freebsd-packages-to-pfsense/">install freely available FreeBSD
packages</a>.</p>
<p>After a year or so of stable performance and no major issues, and having heard
good things about the bhyve hypervisor, I thought I would try my hand at
installing a Linux VM, which would hopefully let me use my Linux knowledge
while still getting the benefits of the pfSense host.</p>
<p>This article will mostly be me trying to adapt the instructions from
<a href="https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html">https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html</a>,
which are for FreeBSD but not necessarily pfSense, and troubleshooting issues I
found along the way.</p>
<h2 id="preparation">Preparation</h2>
<p>First of all, there are a few general notes and debugging steps I used along
the way that might have saved me a lot of time and effort had I adhered to them
from the beginning:</p>
<ul>
<li>If you see it below, <code class="language-plaintext highlighter-rouge">192.168.0.2</code> is my pfSense router’s LAN address. It’s
running a DHCP server and local DNS resolution via unbound.</li>
<li>pfSense’s firewall filters and rules <strong>do not</strong> like it when you change
things from the CLI. When it seems like something isn’t working that was
<em>just working a minute ago</em>, especially after a reboot, go back to the
interfaces page at <code class="language-plaintext highlighter-rouge">/interfaces_assign.php</code>, click each interface in question
as if to edit its configuration, change nothing, then click <code class="language-plaintext highlighter-rouge">Save</code>. Then do
the same for the bridge at <code class="language-plaintext highlighter-rouge">/interfaces_bridge.php</code>. Then do the same for
each relevant firewall rule at <code class="language-plaintext highlighter-rouge">/firewall_rules.php</code>. Then go to <code class="language-plaintext highlighter-rouge">Status</code> ->
<code class="language-plaintext highlighter-rouge">Filter Reload</code> (<code class="language-plaintext highlighter-rouge">/status_filter_reload.php</code>) and reload the firewall.
Several times this process got things working; I think it helps things
re-sync after you change things from the command line.</li>
<li>Anywhere possible, use <code class="language-plaintext highlighter-rouge">tmux</code> sessions to which you can reconnect (<code class="language-plaintext highlighter-rouge">tmux
-a</code>), since you may end up interrupting your SSH connection repeatedly with
all the firewall flushes and rule changes.
<ul>
<li>pfSense: <code class="language-plaintext highlighter-rouge">pkg install tmux</code></li>
<li>Ubuntu server: pre-installed</li>
<li>NixOS: <code class="language-plaintext highlighter-rouge">nix-shell -p tmux</code>
<ul>
<li>You’ll first need to get an IP address and possible need to specify
an alternative DNS server by adding e.g. <code class="language-plaintext highlighter-rouge">nameserver 1.1.1.1</code> to
<code class="language-plaintext highlighter-rouge">/etc/resolv.conf</code></li>
</ul>
</li>
</ul>
</li>
<li>In Ubuntu, don’t forget to disable and flush the firewall when
troubleshooting:
<ul>
<li><code class="language-plaintext highlighter-rouge">ufw disable; iptables -F</code></li>
</ul>
</li>
<li>When in doubt, use <code class="language-plaintext highlighter-rouge">tcpdump</code> (preinstalled on pfSense and Ubuntu, <code class="language-plaintext highlighter-rouge">nix-shell
-p tcpdump</code>) on the host and guest to determine if packets are being sent and
received as expected. A few useful flags:
<ul>
<li><code class="language-plaintext highlighter-rouge">-i enp0s2</code>: only look at interface <code class="language-plaintext highlighter-rouge">enp0s2</code></li>
<li><code class="language-plaintext highlighter-rouge">-XXvv</code>: greatly increase verbosity and show the text content of the
packet</li>
<li><code class="language-plaintext highlighter-rouge">host 192.168.0.2 and udp</code>: only packets that <em>involve</em> <code class="language-plaintext highlighter-rouge">192.168.0.2</code> and
are udp</li>
<li><code class="language-plaintext highlighter-rouge">src 192.168.0.2 and udp</code>: only packets that are <em>from</em> <code class="language-plaintext highlighter-rouge">192.168.0.2</code> and
are udp</li>
<li><code class="language-plaintext highlighter-rouge">ether host 00:a0:98:c9:2a:33</code>: filter by mac address</li>
</ul>
</li>
</ul>
<p>Without further delay, starting in the pfSense CLI:</p>
<ol>
<li>Follow the <a href="https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html">FreeBSD.org instructions</a> to ensure your CPU is compatible
and the prior bios settings are enabled. My <a href="https://amzn.to/3SSnMOA">pre-built device</a> was ready
to rock, but my Lenovo device did not have the approach bios settings. If
the below <code class="language-plaintext highlighter-rouge">awk</code> script prints <code class="language-plaintext highlighter-rouge">OK</code> you should be set.
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">awk</span> < /var/run/dmesg.boot <span class="s1">'
</span><span class="go"> /Features2.*POPCNT/ { popcnt=1 }
/VT-x.*EPT.*UG/ { vtx=1 }
/VT-x.*UG.*EPT/ { vtx=1 }
</span><span class="gp"> popcnt && vtx { print "OK";</span><span class="w"> </span><span class="nb">exit</span> <span class="o">}</span>
<span class="go"> '
</span></code></pre></div> </div>
</li>
<li>Ensure bhyve is installed: <code class="language-plaintext highlighter-rouge">bhyve --help</code></li>
<li>Follow the <a href="https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html">freebsd.org instructions</a> to:
<ol>
<li>Load the kernel module: <code class="language-plaintext highlighter-rouge">kldload vmm</code></li>
<li>Create a TAP device for your VM: <code class="language-plaintext highlighter-rouge">ifconfig tap0 create</code></li>
<li>Enable the TAP device: <code class="language-plaintext highlighter-rouge">sysctl net.link.tap.up_on_open=1</code></li>
<li>Stop here and skip to <code class="language-plaintext highlighter-rouge">Creating a FreeBSD Guest</code>. Specifically, do <em>not</em>
create the bridge or do any bridge steps from the CLI.</li>
<li>Create a dataset for VMs and a 16gb zvol inside that for this VM’s
storage disk, named <code class="language-plaintext highlighter-rouge">nixos0</code> in this case:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>zfs create pfSense/vm
<span class="gp">$</span><span class="w"> </span>zfs create <span class="nt">-V16G</span> <span class="nt">-o</span> <span class="nv">volmode</span><span class="o">=</span>dev pfSense/vm/nixos0
</code></pre></div> </div>
</li>
<li>Because – for the moment – these have to be repeated (once) after
every reboot, I saved these in a script named <code class="language-plaintext highlighter-rouge">prepare.sh</code>:</li>
</ol>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c">#!/usr/bin/env bash</span>
main<span class="o">()</span> <span class="o">{</span>
kldload vmm
ifconfig tap0 create
sysctl net.link.tap.up_on_open<span class="o">=</span>1
<span class="o">}</span>
main <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div> </div>
</li>
<li>Download the ISO image for your distro of choice:
<ul>
<li>Ubuntu Server:
<a href="https://releases.ubuntu.com/22.04.2/ubuntu-22.04.2-live-server-amd64.iso">https://releases.ubuntu.com/22.04.2/ubuntu-22.04.2-live-server-amd64.iso</a></li>
<li>NixOS minimal:
<a href="https://releases.nixos.org/nixos/22.11/nixos-22.11.2979.47c00341629/nixos-minimal-22.11.2979.47c00341629-x86_64-linux.iso">https://releases.nixos.org/nixos/22.11/nixos-22.11.2979.47c00341629/nixos-minimal-22.11.2979.47c00341629-x86_64-linux.iso</a></li>
<li>My commands below might have a slightly different version number,
reflecting the time elapsed between downloading them and writing this</li>
<li>Because pfSense doesn’t have <code class="language-plaintext highlighter-rouge">wget</code>, I used <code class="language-plaintext highlighter-rouge">curl 'https://...' >
image.iso</code> to download the images, but it looks like I could have used
<code class="language-plaintext highlighter-rouge">fetch 'https://...'</code> instead</li>
</ul>
</li>
<li>Preparing for UEFI booting was a little tricky, because pfSense doesn’t
include <code class="language-plaintext highlighter-rouge">edk2-bhyve</code> in its repos. We need this to get a copy of
<code class="language-plaintext highlighter-rouge">BHYVE_UEFI.fd</code>, which is required for UEFI booting. This was the
inspiration for <a href="/2023/01/quickly-add-freebsd-packages-to-pfsense/">my recent post on installing FreeBSD packages on
pfSense</a>; please refer there for the <code class="language-plaintext highlighter-rouge">install_from_freebsd</code> function that
you’ll need below.
<ol>
<li><code class="language-plaintext highlighter-rouge">install_from_freebsd edk2-bhyve</code></li>
<li>Copy the file to a safe place: <code class="language-plaintext highlighter-rouge">cp /usr/local/share/uefi-firmware/BHYVE_UEFI.fd .</code>
<ul>
<li>FWIW, mine has the sha256
<code class="language-plaintext highlighter-rouge">7f93ab9fbd196c61b4a9e7040e94647b30d23acae14c2157fb015b223a9c8d5d</code></li>
</ul>
</li>
<li>You can now remove <code class="language-plaintext highlighter-rouge">edk2-bhyve</code>, that’s all we needed: <code class="language-plaintext highlighter-rouge">pkg remove
edk2-bhyve</code></li>
</ol>
</li>
<li>Using a minimally modified command from <a href="https://people.freebsd.org/~blackend/doc/handbook/virtualization-host-bhyve.html">the FreeBSD instructions</a>, start
the installer image in a VM. Because I ran this <em>many</em> times, I saved it in
a script named <code class="language-plaintext highlighter-rouge">run.sh</code>. You may need to alter the paths to your installer
ISO, to the <code class="language-plaintext highlighter-rouge">.fd</code> file, etc.</li>
</ol>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
bhyve <span class="nt">-A</span> <span class="nt">-H</span> <span class="nt">-P</span> <span class="nt">-D</span> <span class="se">\</span>
<span class="nt">-c</span> 2 <span class="se">\</span>
<span class="nt">-m</span> 1024M <span class="se">\</span>
<span class="nt">-s</span> 0:0,hostbridge <span class="se">\</span>
<span class="nt">-s</span> 1:0,lpc <span class="se">\</span>
<span class="nt">-s</span> 2:0,virtio-net,tap0 <span class="se">\</span>
<span class="nt">-s</span> 3:0,ahci-cd,./nixos-minimal-22.11.1705.b83e7f5a04a-x86_64-linux.iso <span class="se">\</span>
<span class="nt">-s</span> 4:0,virtio-blk,/dev/zvol/pfSense/vm/nixos0 <span class="se">\</span>
<span class="nt">-l</span> com1,stdio <span class="se">\</span>
<span class="nt">-l</span> bootrom,./BHYVE_UEFI.fd <span class="se">\</span>
nixos0
<span class="c"># Easily copy and paste these above to switch distros</span>
<span class="c"># -s 3:0,ahci-cd,./nixos-minimal-22.11.1705.b83e7f5a04a-x86_64-linux.iso \</span>
<span class="c"># -s 3:0,ahci-cd,./ubuntu-22.04.1-live-server-amd64.iso \</span>
</code></pre></div></div>
<ol>
<li><code class="language-plaintext highlighter-rouge">./run.sh</code> and you should see the installer image start booting.</li>
<li>I was unable to complete the boot process for either image initially and had
to take an extra step or two to enable serial output:
<ul>
<li>NixOS:
<ol>
<li>Hit an uninteresting key a few times (like the down arrow)</li>
<li>When able, arrow down to <code class="language-plaintext highlighter-rouge">HiDPI, Quirks and Accessibility</code></li>
<li>From this submenu, choose <code class="language-plaintext highlighter-rouge">Serial console=ttyS0,115200n8</code></li>
<li>Continue the boot process</li>
</ol>
</li>
<li>Ubuntu has some weird keybindings, so be careful not to mistype:
<ol>
<li>Arrow to <code class="language-plaintext highlighter-rouge">Try or Install Ubuntu Server</code></li>
<li>Hit the letter <code class="language-plaintext highlighter-rouge">e</code></li>
<li>Arrow down to the line with <code class="language-plaintext highlighter-rouge">linux</code></li>
<li>Hit <code class="language-plaintext highlighter-rouge">ctrl-e</code> to jump to the end of the line (after <code class="language-plaintext highlighter-rouge">---</code>)</li>
<li>Add <code class="language-plaintext highlighter-rouge">console=ttyS0</code></li>
<li>Hit <code class="language-plaintext highlighter-rouge">ctrl-x</code> to boot</li>
<li>If you mess up, hit <code class="language-plaintext highlighter-rouge">F2</code> and try again</li>
<li>Once the boot process is complete and you see <code class="language-plaintext highlighter-rouge">Continue in rich
mode</code>, hit <code class="language-plaintext highlighter-rouge">F2</code> to get a shell</li>
</ol>
</li>
</ul>
</li>
<li>Run <code class="language-plaintext highlighter-rouge">ip addr</code> ands note that you probably don’t have an IP address.</li>
<li>As an aside, if you need to reboot the VM, I had to run <code class="language-plaintext highlighter-rouge">bhyvectl --destroy
--vm=nixos0</code> from pfSense prior to being able to boot the VM a second time.</li>
</ol>
<h2 id="networking">Networking</h2>
<p>Next we want to give this VM access to the LAN; run the followuping steps from
the pfSense web interface. I’ll try to list both the <code class="language-plaintext highlighter-rouge">link name</code> as well as the
(<code class="language-plaintext highlighter-rouge">/url.php</code>) for these, since navigating the nested menus can be tough.</p>
<p>For much of this, I was following <a href="https://forum.netgate.com/topic/76340/100-us-dollars-for-working-bhyve-instructions-on-pfsense-2-2/31">this helpful thread</a> on the Netgate
forum.</p>
<h3 id="assign-tap0-to-an-interface">Assign <code class="language-plaintext highlighter-rouge">tap0</code> to an interface</h3>
<ol>
<li><code class="language-plaintext highlighter-rouge">Interfaces</code> -> <code class="language-plaintext highlighter-rouge">Assignments</code> (<code class="language-plaintext highlighter-rouge">/interfaces_assign.php</code>)</li>
<li><code class="language-plaintext highlighter-rouge">Available network ports:</code> -> <code class="language-plaintext highlighter-rouge">tap0</code> -> <code class="language-plaintext highlighter-rouge">Add</code></li>
<li>Click the new interface to edit its configuration (<code class="language-plaintext highlighter-rouge">/interfaces.php?if=opt1</code>, mine was automatically named <code class="language-plaintext highlighter-rouge">OPT1</code>)</li>
<li>Check the box to <code class="language-plaintext highlighter-rouge">Enable interface</code></li>
<li>Change description to <code class="language-plaintext highlighter-rouge">TAP0</code></li>
<li>Leave remaining defaults, <code class="language-plaintext highlighter-rouge">Save</code>, <code class="language-plaintext highlighter-rouge">Apply Changes</code></li>
</ol>
<h3 id="create-a-bridge-with-lan-and-tap0">Create a bridge with <code class="language-plaintext highlighter-rouge">LAN</code> and <code class="language-plaintext highlighter-rouge">TAP0</code></h3>
<ol>
<li><code class="language-plaintext highlighter-rouge">Interfaces</code> -> <code class="language-plaintext highlighter-rouge">Assignments</code> -> <code class="language-plaintext highlighter-rouge">Bridges</code> (<code class="language-plaintext highlighter-rouge">/interfaces_bridge.php</code>)</li>
<li><code class="language-plaintext highlighter-rouge">Add</code></li>
<li>Select both <code class="language-plaintext highlighter-rouge">LAN</code> and <code class="language-plaintext highlighter-rouge">TAP0</code></li>
<li><code class="language-plaintext highlighter-rouge">Save</code></li>
</ol>
<h3 id="create-an-allow-all-firewall-rule">Create an “allow all” firewall rule</h3>
<ol>
<li><code class="language-plaintext highlighter-rouge">Firewall</code> -> <code class="language-plaintext highlighter-rouge">Rules</code> -> <code class="language-plaintext highlighter-rouge">TAP0</code> (<code class="language-plaintext highlighter-rouge">/firewall_rules.php?if=opt1</code>, not sure why
the URL doesn’t update with the new name)</li>
<li><code class="language-plaintext highlighter-rouge">Add</code>
<ol>
<li><code class="language-plaintext highlighter-rouge">Interface</code> -> <code class="language-plaintext highlighter-rouge">TAP0</code></li>
<li><code class="language-plaintext highlighter-rouge">Address Family</code> -> <code class="language-plaintext highlighter-rouge">IPv4+IPv6</code></li>
<li><code class="language-plaintext highlighter-rouge">Protocol</code> -> <code class="language-plaintext highlighter-rouge">Any</code></li>
<li><code class="language-plaintext highlighter-rouge">Save</code> and <code class="language-plaintext highlighter-rouge">Apply</code></li>
</ol>
</li>
</ol>
<h3 id="test-dhcp">Test DHCP</h3>
<ol>
<li>Return to your VM and see if you can get an IP address:
<ul>
<li>Ubuntu: <code class="language-plaintext highlighter-rouge">dhclient -v enp0s2</code></li>
<li>NixOS: <code class="language-plaintext highlighter-rouge">sudo systemctl restart dhcpcd</code></li>
</ul>
</li>
<li>Hopefully <code class="language-plaintext highlighter-rouge">ip addr</code> now shows an IP address on your LAN!</li>
<li>From here, I found it <em>much</em> easier to SSH directly to the VM guest
<ul>
<li>Ubuntu
<ol>
<li>set a password for <code class="language-plaintext highlighter-rouge">root</code> with <code class="language-plaintext highlighter-rouge">passwd</code></li>
<li>enable SSH password authentication for root by changing
<code class="language-plaintext highlighter-rouge">PermitRootLogin</code> to <code class="language-plaintext highlighter-rouge">yes</code> in <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code></li>
<li><code class="language-plaintext highlighter-rouge">systemctl restart ssh</code> to pick up the new settings</li>
<li>From your main workstation <code class="language-plaintext highlighter-rouge">ssh root@your_guest_ip_address</code></li>
</ol>
</li>
<li>NixOS
<ol>
<li>set a password for <code class="language-plaintext highlighter-rouge">root</code>: <code class="language-plaintext highlighter-rouge">sudo passwd root</code></li>
<li>From your main workstation <code class="language-plaintext highlighter-rouge">ssh root@your_guest_ip_address</code></li>
</ol>
</li>
</ul>
</li>
</ol>
<h3 id="fixing-dns"><a name="Fixing DNS"></a>Fixing DNS</h3>
<p>At this point, I found that I could:</p>
<ul>
<li>get an IP address via DHCP (in the LAN subnet)</li>
<li>ping both internal and external hosts by IP address, including the host
pfsense machine at 192.168.0.2</li>
<li>send and receive TCP and UDP data with netcat to both internal and external
hosts</li>
<li>resolve DNS using an external DNS resolver (e.g. <code class="language-plaintext highlighter-rouge">host n8henrie.com 1.1.1.1</code>)</li>
</ul>
<p>However, for some bizarre reason, I couldn’t use my local DNS from the pfSense
host:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>host <span class="nt">-4</span> n8henrie.com 192.168.0.2
<span class="gp">;</span><span class="p">;</span> connection timed out<span class="p">;</span> no servers could be reached
</code></pre></div></div>
<p>I didn’t see any relevant blocked packets in <code class="language-plaintext highlighter-rouge">/var/log/filter.log</code> (or in the
GUI), and the weirdest part was that I could <em>see</em> the responses – including
the properly resolved IP address:</p>
<ul>
<li>First, in the VM guest, start requesting DNS resolution for <code class="language-plaintext highlighter-rouge">n8henrie.com</code>
every second with a 1 second timeout: <code class="language-plaintext highlighter-rouge">watch host -W1 -4 n8henrie.com 192.168.0.2</code></li>
<li>CLI (from pfSense): <code class="language-plaintext highlighter-rouge">tcpdump -i tap0 src vm_ip_address and udp</code>, note
requests to resolve <code class="language-plaintext highlighter-rouge">n8henrie.com</code></li>
<li>GUI: <code class="language-plaintext highlighter-rouge">Firewall</code> -> <code class="language-plaintext highlighter-rouge">pfblockerng</code> -> <code class="language-plaintext highlighter-rouge">Reports</code> -> <code class="language-plaintext highlighter-rouge">DNS Reply</code>
(<code class="language-plaintext highlighter-rouge">/pfblockerng/pfblockerng_alerts.php?view=reply</code>), note propertly resolved
requests to <code class="language-plaintext highlighter-rouge">n8henrie.com</code></li>
</ul>
<p>Even more strange was that I could see the DNS reply <em>in the VM as well</em>:</p>
<ol>
<li>Open 2 panes in tmux</li>
<li>Pane 1: <code class="language-plaintext highlighter-rouge">watch host -W1 -4 n8henrie.com 192.168.0.2</code></li>
<li>Pane 2:
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">root@ubuntu-server:/#</span><span class="w"> </span>tcpdump <span class="nt">-vv</span> <span class="nt">-i</span> enp0s2 host 192.168.0.2 and udp
<span class="go">11:46:49.569313 IP (tos 0x0, ttl 64, id 34767, offset 0, flags [none], proto UDP (17), length 58)
</span><span class="gp"> 192.168.0.202.44462 ></span><span class="w"> </span>192.168.0.2.domain: <span class="o">[</span>udp <span class="nb">sum </span>ok] 59526+ A? n8henrie.com. <span class="o">(</span>30<span class="o">)</span>
<span class="go">11:46:49.576273 IP (tos 0x0, ttl 64, id 30262, offset 0, flags [none], proto UDP (17), length 90)
</span><span class="gp"> 192.168.0.2.domain ></span><span class="w"> </span>192.168.0.202.44462: <span class="o">[</span>bad udp <span class="nb">cksum </span>0x8274 -> 0x871c!] 59526 q: A? n8henrie.com. 2/0/0 n8henrie.com. A 104.21.37.209, n8henrie.com. A 172.67.213.115 <span class="o">(</span>62<span class="o">)</span>
</code></pre></div> </div>
</li>
</ol>
<p>I got stuck here for over a week and just <em>could not</em> figure out why DNS
resolution was fine from a remote DNS server but not my VM host, with the same
behavior in both NixOS and Ubuntu. I tried asking on <a href="https://www.reddit.com/r/PFSENSE/comments/11a8t80/comment/j9v828a/?context=3">r/PFSENSE</a>,
<a href="https://superuser.com/questions/1771966/pfsense-host-dns-resolver-not-working-in-guest-vm-ubuntu-or-nixos">StackExchange</a>, and the Netgate forums (the last of which I eventually
deleted with zero responses in a week or so).</p>
<p>Finally, this morning I took a closer look at the <code class="language-plaintext highlighter-rouge">tcpdump</code> output from the
guest, increasing verbosity with <code class="language-plaintext highlighter-rouge">-XXvv</code> and comparing it to the response for
an identical request on one of my other machines (which was working fine with
the same DNS server). I noticed a lot of <code class="language-plaintext highlighter-rouge">bad udp cksum</code> in the VM, where as
the other machine had all <code class="language-plaintext highlighter-rouge">udp sum ok</code>.</p>
<p>With a bit of searching, I eventually came across <a href="https://security.stackexchange.com/questions/227220/tcpdump-packets-have-bad-and-incorrect-checksums-on-localhost-how-to-investigat">this SO thread</a>, which
led me to <a href="https://wiki.wireshark.org/CaptureSetup/Offloading#Checksum_Offload">this article from wireshark.org</a>, and finally I came across
<a href="https://docs.netgate.com/pfsense/en/latest/virtualization/virtio.html">https://docs.netgate.com/pfsense/en/latest/virtualization/virtio.html</a>:</p>
<blockquote>
<p>With the current state of VirtIO network drivers in FreeBSD, it is necessary
to disable hardware checksum offload to reach systems (at least other VM
guests, possibly others) protected by pfSense software directly from the VM
host.</p>
</blockquote>
<p>Sure enough <code class="language-plaintext highlighter-rouge">System</code> -> <code class="language-plaintext highlighter-rouge">Advanced</code> -> <code class="language-plaintext highlighter-rouge">Networking</code>
(<code class="language-plaintext highlighter-rouge">/system_advanced_network.php</code>), check to disable <code class="language-plaintext highlighter-rouge">Hardware
Checksum Offloading</code>, reboot, go through the above steps again, and I was
delighted to see:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">[root@nixos:~]#</span><span class="w"> </span>host <span class="nt">-4</span> n8henrie.com 192.168.0.2
<span class="go">Using domain server:
Name: 192.168.0.2
</span><span class="gp">Address: 192.168.0.2#</span>53
<span class="go">Aliases:
n8henrie.com has address 104.21.37.209
n8henrie.com has address 172.67.213.115
n8henrie.com has IPv6 address 2606:4700:3037::6815:25d1
n8henrie.com has IPv6 address 2606:4700:3037::ac43:d573
</span></code></pre></div></div>
<p>Phew!</p>
<p>From here, you should be able to follow your choice of installation guides,
such as <a href="https://nixos.wiki/wiki/NixOS_Installation_Guide">https://nixos.wiki/wiki/NixOS_Installation_Guide</a>, to install your VM
into the zvol you created earlier. Don’t forget to enable serial output
(<code class="language-plaintext highlighter-rouge">boot.kernelParams = [ "console=ttyS0" ];</code>) in your configuration prior to
<code class="language-plaintext highlighter-rouge">nixos-install</code>. After going through the install process, you should be able to
remove from <code class="language-plaintext highlighter-rouge">run.sh</code> the line referencing the installer ISO, run the <code class="language-plaintext highlighter-rouge">bhyvectl
destroy</code> step, then run <code class="language-plaintext highlighter-rouge">run.sh</code> again and you should boot into your installed
system.</p>
<p>In a future post I’ll go over using <code class="language-plaintext highlighter-rouge">vm-bhyve</code> for a friendlier interface as
well as some settings that will persist the VM and configuration across
reboots; as is, you’ll have to start from scratch (more or less) after a
reboot.</p>
<p>In the meantime, you almost certainly want to go back and tighten up some
security settings:</p>
<ul>
<li>turn off SSH password authentication for <code class="language-plaintext highlighter-rouge">root</code></li>
<li>rethink your life choices because you’re running a VM on your firewall</li>
<li>pick a stronger root password</li>
<li>add some additional firewall rules</li>
</ul>
Tue, 07 Mar 2023 10:03:24 -0700
https://n8henrie.com/2023/03/running-nixos-and-ubuntu-vms-on-pfsense-via-bhyve/
https://n8henrie.com/2023/03/running-nixos-and-ubuntu-vms-on-pfsense-via-bhyve/
pfsense
freebsd
linux
nix
tech
-
Quickly add FreeBSD Packages to pfSense
<p><strong>Bottom Line:</strong> I wrote a bash function to add packages directly from FreeBSD
to pfSense.
<!--more--></p>
<p>Disclaimer: Modifying your firewall is a security risk. Please don’t use the
information on this page unless you know what you are doing and are willing to
accept the consequences for yourself and anyone on your network.</p>
<p>I’ve been happily using pfSense for a year or so now. I am getting more</p>
<p>comfortable with Linux over time but know very little about FreeBSD, on which
pfSense is based.</p>
<p>As I explore pfSnse, I occasionally want to add a package from the main FreeBSD
repos. Netgate provides instructions on how to add the FreeBSD repos at
<a href="https://docs.netgate.com/pfsense/en/latest/recipes/freebsd-pkg-repo.html">https://docs.netgate.com/pfsense/en/latest/recipes/freebsd-pkg-repo.html</a>;
essentially you change <code class="language-plaintext highlighter-rouge">FreeBSD: { enabled: yes }</code> in
<code class="language-plaintext highlighter-rouge">/usr/local/etc/pkg/repos/FreeBSD.conf</code> and <code class="language-plaintext highlighter-rouge">/usr/local/etc/pkg/repos/pfSense.conf</code>.</p>
<p>However, changing this messes with your whole <code class="language-plaintext highlighter-rouge">pkg</code> database (ask how I know)
and they have a very visible warning that this is generally <strong>not</strong> a good
idea.</p>
<p>They also list another way to install a specific package:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>pkg add http://pkg.freebsd.org/FreeBSD:11:amd64/latest/All/tshark-3.2.6.txz
</code></pre></div></div>
<p>This looks much better to me, but unfortunately it’s pretty difficult (or was
for me anyway) to figure out the exact path to a specific package.
Unfortunately, attempting to browse
<a href="https://pkg.freebsd.org/FreeBSD:12:amd64/latest/All">https://pkg.freebsd.org/FreeBSD:12:amd64/latest/All</a> gives me a <code class="language-plaintext highlighter-rouge">403
Forbidden</code> error, and a directory above that just includes some compressed
directories that aren’t really helpful in a web browser.</p>
<p>Thankfuly, the base pfSense install includes a few basic utilities like <code class="language-plaintext highlighter-rouge">curl</code>
and <code class="language-plaintext highlighter-rouge">jq</code> that let me piece together the below function:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>install_from_freebsd<span class="o">()</span> <span class="o">{</span>
<span class="nv">pkgname</span><span class="o">=</span><span class="nv">$1</span>
<span class="nv">base_url</span><span class="o">=</span><span class="s1">'https://pkg.freebsd.org/FreeBSD:12:amd64/latest'</span>
<span class="nv">path</span><span class="o">=</span><span class="si">$(</span>
curl <span class="nt">-s</span> <span class="s2">"</span><span class="k">${</span><span class="nv">base_url</span><span class="k">}</span><span class="s2">/packagesite.txz"</span> |
<span class="nb">tar</span> <span class="nt">-xzf-</span> <span class="nt">--to-stdout</span> packagesite.yaml |
jq <span class="nt">-r</span> <span class="nt">--arg</span> pkgname <span class="s2">"</span><span class="k">${</span><span class="nv">pkgname</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
<span class="s1">'select(.name == $pkgname) | .path'</span>
<span class="si">)</span>
pkg add <span class="s2">"</span><span class="k">${</span><span class="nv">base_url</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">path</span><span class="k">}</span><span class="s2">"</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Once you’ve entered <code class="language-plaintext highlighter-rouge">bash</code> and defined / sourced the function, it works like
a charm:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>bash
<span class="gp">$</span><span class="w"> </span>install_from_freebsd tshark
</code></pre></div></div>
<p>Once I verified it was working, I went ahead and put it in <code class="language-plaintext highlighter-rouge">~/.bashrc</code> so it
would be available automatically in <code class="language-plaintext highlighter-rouge">bash</code>.</p>
Tue, 31 Jan 2023 11:19:22 -0700
https://n8henrie.com/2023/01/quickly-add-freebsd-packages-to-pfsense/
https://n8henrie.com/2023/01/quickly-add-freebsd-packages-to-pfsense/
tech
terminal
pfsense
freebsd
bash
tech