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&gt; buildPhase completed in 38 seconds mpv&gt; Running phase: installPhase mpv&gt; mesonInstallPhase flags: '' mpv&gt; Installing mpv.1 to /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man/share/man/man1 mpv&gt; Installing libmpv.2.dylib to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib mpv&gt; Installing mpv to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin mpv&gt; 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&gt; 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&gt; 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&gt; 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&gt; 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&gt; Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv mpv&gt; Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/input.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc/mpv mpv&gt; 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&gt; 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&gt; 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&gt; 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&gt; 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&gt; 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&gt; Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/mpv.metainfo.xml to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/metainfo mpv&gt; Installing /nix/var/nix/builds/nix-58338-1051359316/source/etc/encoding-profiles.conf to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/etc/mpv mpv&gt; 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&gt; 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&gt; 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&gt; 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&gt; 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&gt; 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&gt; Installing symlink pointing to libmpv.2.dylib to /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/libmpv.dylib mpv&gt; /nix/var/nix/builds/nix-58338-1051359316/source/TOOLS /nix/var/nix/builds/nix-58338-1051359316/source/build mpv&gt; /nix/var/nix/builds/nix-58338-1051359316/source/build mpv&gt; Running phase: fixupPhase mpv&gt; Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc to /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc/share/doc mpv&gt; Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/pkgconfig to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig mpv&gt; 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&gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0... mpv&gt; patching script interpreter paths in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0 mpv&gt; /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&gt; /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&gt; 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&gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev... mpv&gt; patching script interpreter paths in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev mpv&gt; stripping (with command strip and flags -S) in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib mpv&gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc... mpv&gt; patching script interpreter paths in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc mpv&gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man... mpv&gt; gzipping man pages under /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man/share/man/ mpv&gt; patching script interpreter paths in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man mpv&gt; Running phase: installCheckPhase mpv&gt; Executing versionCheckPhase mpv&gt; Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --help mpv&gt; mpv&gt; Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --version mpv&gt; 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: &gt; /nix/var/nix/builds/nix-58338-1051359316/source/TOOLS /nix/var/nix/builds/nix-58338-1051359316/source/build &gt; /nix/var/nix/builds/nix-58338-1051359316/source/build &gt; Running phase: fixupPhase &gt; Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/share/doc to /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc/share/doc &gt; Moving /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/lib/pkgconfig to /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig &gt; Patching '/nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib/pkgconfig/mpv.pc' includedir to output /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev &gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0... &gt; patching script interpreter paths in /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0 &gt; /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" &gt; /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" &gt; 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 &gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev... &gt; patching script interpreter paths in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev &gt; stripping (with command strip and flags -S) in /nix/store/xbv4gki5sms5zcx592dnb8n8sirylpqv-mpv-0.40.0-dev/lib &gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc... &gt; patching script interpreter paths in /nix/store/lzr5jj41d40378vwvpix0lw5mp8vf8lz-mpv-0.40.0-doc &gt; checking for references to /nix/var/nix/builds/nix-58338-1051359316/ in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man... &gt; gzipping man pages under /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man/share/man/ &gt; patching script interpreter paths in /nix/store/fpvji1dkf6ciql0icyc4fa7rab8k5zvb-mpv-0.40.0-man &gt; Running phase: installCheckPhase &gt; Executing versionCheckPhase &gt; Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --help &gt; &gt; Did not find version 0.40.0 in the output of the command /nix/store/6jdaccxf7ic245vvqslcd28imkwikzn8-mpv-0.40.0/bin/mpv --version &gt; 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>&gt; 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&gt; +++++ /nix/store/grzyi5fn7wv5d5v0hc8fbhh3r5zrmzjm-mpv-0.40.0/bin/mpv --version mpv&gt; /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&gt;&amp;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> &lt; mpv-debug.log <span class="se">\</span> <span class="go"> -v header='Generated sandbox profile:' \ -v leader='sandbox setup: ' \ ' </span><span class="gp"> flag &amp;&amp; $</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") &amp;&amp; (subsystem == "com.apple.sandbox.reporting") &amp;&amp; (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") &amp;&amp; (subsystem == "com.apple.sandbox.reporting") &amp;&amp; (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() { -&gt; 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"); -&gt; 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: &lt;user expression 3&gt;: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"); -&gt; 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">&amp;</span><span class="n">haystack</span><span class="p">);</span> <span class="c1">// &lt;- 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 -&gt; 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: &lt;https://n8henrie.com/2025/12/how-to-set-a-conditional-breakpoint-when-debugging-rust-with-lldb/&gt; &lt;https://lldb.llvm.org/use/tutorials/writing-custom-commands.html&gt; &lt;https://lldb.llvm.org/use/tutorials/breakpoint-triggered-scripts.html&gt; """</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: &gt; current directory: /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/gems/nokogiri-1.16.0/ext/nokogiri &gt; make DESTDIR\= sitearchdir\=./.gem.20251015-88848-9l6020 sitelibdir\=./.gem.20251015-88848-9l6020 &gt; compiling gumbo.c &gt; In file included from gumbo.c:30: &gt; In file included from ./nokogiri.h:77: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby.h:38: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/ruby.h:28: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic.h:24: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic/char.h:29: &gt; /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] &gt; 398 | struct RString retval; &gt; | ^ &gt; /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 &gt; 88 | const VALUE klass; &gt; | ^ &gt; gumbo.c:32:10: fatal error: 'nokogiri_gumbo.h' file not found &gt; 32 | #include "nokogiri_gumbo.h" &gt; | ^~~~~~~~~~~~~~~~~~ &gt; 1 warning and 1 error generated. &gt; make: *** [Makefile:248: gumbo.o] Error 1 &gt; &gt; make failed, exit code 2 &gt; &gt; 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. &gt; 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: &gt; current directory: /nix/store/plibr3n8ym9d4n3v9yzlr72wsslfqfrs-ruby3.3-nokogiri-1.16.0/lib/ruby/gems/3.3.0/gems/nokogiri-1.16.0/ext/nokogiri &gt; make DESTDIR\= sitearchdir\=./.gem.20251017-95545-55qfgi sitelibdir\=./.gem.20251017-95545-55qfgi &gt; compiling gumbo.c &gt; In file included from gumbo.c:30: &gt; In file included from ./nokogiri.h:77: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby.h:38: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/ruby.h:28: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic.h:24: &gt; In file included from /nix/store/5kmiw2wqmhk6y0ij77z6xpw3jddbqmii-ruby-3.3.9/include/ruby-3.3.0/ruby/internal/arithmetic/char.h:29: &gt; /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] &gt; 398 | struct RString retval; &gt; | ^ &gt; /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 &gt; 88 | const VALUE klass; &gt; | ^ &gt; gumbo.c:32:10: fatal error: 'nokogiri_gumbo.h' file not found &gt; 32 | #include "nokogiri_gumbo.h" &gt; | ^~~~~~~~~~~~~~~~~~ &gt; 1 warning and 1 error generated. &gt; make: *** [Makefile:248: gumbo.o] Error 1 &gt; &gt; make failed, exit code 2 &gt; &gt; 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. &gt; 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">&gt;</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" = "&lt;hash&gt;</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"> &gt;</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"> &gt;</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">&gt;</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">&gt;</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>&gt; error: failed to select a version for the requirement `addr2line = "^0.19.0"` (locked to 0.19.0) &gt; 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 &amp;&amp; nix build ... &gt; 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 &amp;&amp; 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 &amp;&amp; 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">&amp;&amp;</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 &amp;&amp; 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"> &gt;</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"> &gt;</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"> &gt;</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"> &gt;</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">&lt;</span><span class="nv">nixpkgs</span><span class="o">&gt;</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">&lt;</span><span class="sx">nixpkgs/lib</span><span class="o">&gt;</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">&amp;&amp;</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"> &gt;</span><span class="w"> </span>error[E0432]: unresolved import <span class="sb">`</span>core::sync::atomic::AtomicUsize<span class="sb">`</span> <span class="gp"> &gt;</span><span class="w"> </span><span class="nt">--</span><span class="o">&gt;</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"> &gt;</span><span class="w"> </span>| <span class="gp"> &gt;</span><span class="w"> </span>27 | use core::sync::atomic::AtomicUsize<span class="p">;</span> <span class="gp"> &gt;</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"> &gt;</span><span class="w"> </span><span class="gp"> &gt;</span><span class="w"> </span>Compiling managed v0.8.0 <span class="gp"> &gt;</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"> &gt;</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"> &gt;</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">&amp;&amp;</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"> &gt;</span><span class="w"> </span>Compiling enumset v1.1.2 <span class="gp"> &gt;</span><span class="w"> </span>Compiling managed v0.8.0 <span class="gp"> &gt;</span><span class="w"> </span>Compiling atomic-waker v1.1.2 <span class="gp"> &gt;</span><span class="w"> </span>Compiling bitflags v1.3.2 <span class="gp"> &gt;</span><span class="w"> </span>Compiling no-std-net v0.5.0 <span class="gp"> &gt;</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"> &gt;</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"> &gt;</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"> &gt;</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"> &gt;</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">&amp;&amp;</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"> &gt;</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"> &gt;</span><span class="w"> </span><span class="nt">--</span><span class="o">&gt;</span> src/main.rs:26:24 <span class="gp"> &gt;</span><span class="w"> </span>| <span class="gp"> &gt;</span><span class="w"> </span>26 | const PASSWORD: &amp;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"> &gt;</span><span class="w"> </span>| ^^^^^^^^^^^^^^^^ <span class="gp"> &gt;</span><span class="w"> </span>| <span class="gp"> &gt;</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"> &gt;</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"> &gt;</span><span class="w"> </span><span class="gp"> &gt;</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">&amp;&amp;</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"> &gt;</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"> &gt;</span><span class="w"> </span><span class="gp"> &gt;</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"> &gt;</span><span class="w"> </span>| <span class="gp"> &gt;</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"> &gt;</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"> &gt;</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"> &gt;</span><span class="w"> </span><span class="gp"> &gt;</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"> &gt;</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">&amp;&amp;</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">&lt;!DOCTYPE html&gt;</span><span class="w"> </span><span class="gp">&lt;html&gt;</span><span class="w"> </span><span class="gp">&lt;head&gt;</span><span class="w"> </span><span class="gp"> &lt;title&gt;</span>Nothing here&lt;/title&gt; <span class="gp">&lt;/head&gt;</span><span class="w"> </span><span class="gp">&lt;body&gt;</span><span class="w"> </span><span class="gp">&lt;pre&gt;</span><span class="w"> </span><span class="go"> __________________________ </span><span class="gp"> &lt; Hello fellow Rustaceans! &gt;</span><span class="w"> </span><span class="go"> -------------------------- \ \ _~^~^~_ \) / o o \ (/ '_ - _' / '-----' \ </span><span class="gp">&lt;/pre&gt;</span><span class="w"> </span><span class="gp">&lt;/body&gt;</span><span class="w"> </span><span class="gp">&lt;/html&gt;</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 &lt;[email protected]&gt;"</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">&amp;&amp;</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 =&gt;</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 =&gt;</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 =&gt;</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> &lt;- 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> &lt;- 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">" &amp;&amp; -z "</span><span class="k">${</span><span class="nv">makefile</span><span class="k">:-}</span><span class="s2">" &amp;&amp; ! ( -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">" &amp;&amp; -z "</span><span class="k">${</span><span class="nv">makefile</span><span class="k">:-}</span><span class="s2">" &amp;&amp; ! ( -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 &lt;module&gt;</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 &lt;module&gt; 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 &lt;google/protobuf/implicit_weak_message.h&gt; </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"> '&lt;nixpkgs&gt;</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">&lt;&lt;&lt;</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">&lt;&lt;&lt;</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 &amp;&amp; stdenv.isAarch64), + # Use MPS on M1 machines <span class="gi">+ mpsSupport ? (stdenv.isDarwin &amp;&amp; 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">&lt;&lt;&lt;</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 &lt;google/protobuf/any.h&gt; <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">&lt;&lt;&lt;</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 &lt;google/protobuf/any.h&gt; </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">&lt;&lt;&lt;</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 &lt;google/protobuf/any.h&gt; </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">&lt;&lt;&lt;</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 &lt;google/protobuf/any.h&gt; </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">&lt;&lt;&lt;</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 &lt;google/protobuf/any.h&gt; </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 &lt;google/protobuf/any.h&gt; </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 &amp;&amp; -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">&amp;&amp;</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 &amp;&amp; -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">&amp;&amp;</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 &amp;&amp; -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">&amp;&amp;</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 &amp;&amp; -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">&amp;&amp;</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 &amp;&amp; -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">&gt;</span>&amp;<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 &lt;google/protobuf/any.h&gt; </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">&lt;&lt;&lt;</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 &gt;=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 &amp;&amp; stdenv.isAarch64), + # Use MPS on M1 machines <span class="gi">+ mpsSupport ? (stdenv.isDarwin &amp;&amp; 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 &gt;= 201703L inline void *aligned_alloc(size_t align, size_t size)' '#if __cplusplus &gt;= 201703L &amp;&amp; 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&amp;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> -&gt; <code class="language-plaintext highlighter-rouge">Advanced</code> -&gt; <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">&gt;</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> -&gt; <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> &lt; /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 &amp;&amp; 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://...' &gt; 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> -&gt; <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> -&gt; <code class="language-plaintext highlighter-rouge">tap0</code> -&gt; <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> -&gt; <code class="language-plaintext highlighter-rouge">Assignments</code> -&gt; <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> -&gt; <code class="language-plaintext highlighter-rouge">Rules</code> -&gt; <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> -&gt; <code class="language-plaintext highlighter-rouge">TAP0</code></li> <li><code class="language-plaintext highlighter-rouge">Address Family</code> -&gt; <code class="language-plaintext highlighter-rouge">IPv4+IPv6</code></li> <li><code class="language-plaintext highlighter-rouge">Protocol</code> -&gt; <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> -&gt; <code class="language-plaintext highlighter-rouge">pfblockerng</code> -&gt; <code class="language-plaintext highlighter-rouge">Reports</code> -&gt; <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 &gt;</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 &gt;</span><span class="w"> </span>192.168.0.202.44462: <span class="o">[</span>bad udp <span class="nb">cksum </span>0x8274 -&gt; 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> -&gt; <code class="language-plaintext highlighter-rouge">Advanced</code> -&gt; <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