<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Macos on Charlie Chiang's blog</title><link>https://charlie0129.github.io/blog/tags/macos/</link><description>Recent content in Macos on Charlie Chiang's blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 08 Jun 2026 12:25:00 +0800</lastBuildDate><atom:link href="https://charlie0129.github.io/blog/tags/macos/index.xml" rel="self" type="application/rss+xml"/><item><title>Bundling macOS Dynamic Libraries</title><link>https://charlie0129.github.io/blog/p/bundling-macos-dynamic-libraries/</link><pubDate>Mon, 08 Jun 2026 12:25:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/bundling-macos-dynamic-libraries/</guid><description>&lt;p>I recently tried to build QEMU on macOS and make the result portable enough that it could still run after uninstalling the Homebrew packages used during the build, or I can move the whole QEMU directory to another machine. I don&amp;rsquo;t want the Homebrew build because it installs too many dependencies on the system, and I want a more self-contained bundle, not a system-wide installation.&lt;/p>
&lt;p>The short version: &lt;strong>fully static linking on macOS is not really the right target&lt;/strong>. macOS binaries still dynamically link against Apple system libraries such as &lt;code>libSystem&lt;/code>. But for tools like QEMU, what I really wanted was not a “pure static binary”; I wanted a self-contained directory like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">_qemu-11.0.1/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── bin/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-img
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-system-x86_64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libgobject-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libintl.8.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libpcre2-8.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The binaries in &lt;code>bin/&lt;/code> should load their non-system dependencies from &lt;code>../lib&lt;/code> (note that it&amp;rsquo;s relative, not absolute, so it will work even if the whole directory is moved), instead of from &lt;code>/opt/homebrew&lt;/code>.&lt;/p>
&lt;p>This post records the process, using QEMU as the example.&lt;/p>
&lt;h2 id="the-original-problem">The original problem
&lt;/h2>&lt;p>I built QEMU roughly like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># You should refer to https://wiki.qemu.org/Hosts/Mac for the latest build instructions. This is just an example.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">brew install libffi gettext glib pkg-config zstd &lt;span class="c1"># may not be the exact set of dependencies you need&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --prefix&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/bin/_qemu-11.0.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make -j&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>sysctl -n hw.ncpu&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The build worked. But after uninstalling some Homebrew dependencies, running &lt;code>qemu-img&lt;/code> failed:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">dyld[82783]: Library not loaded: /opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Referenced from: /Users/charlie/bin/_qemu-11.0.1/bin/qemu-img
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Reason: tried: &amp;#39;/opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib&amp;#39; (no such file)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">zsh: abort qemu-img
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This means the installed QEMU binary still contains an absolute dependency path:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We can confirm that with:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">otool -L &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">/bin/qemu-img&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Example output:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">qemu-img:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib (compatibility version 8801.0.0, current version 8801.0.0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /opt/homebrew/opt/zstd/lib/libzstd.1.dylib (compatibility version 1.0.0, current version 1.5.7)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The &lt;code>/usr/lib&lt;/code> and &lt;code>/System/Library&lt;/code> entries are fine. They are macOS system libraries. The &lt;code>/opt/homebrew/...&lt;/code> entries are the ones that make the binary depend on my local Homebrew installation.&lt;/p>
&lt;h2 id="why-not-just-build-qemu-fully-static">Why not just build QEMU fully static?
&lt;/h2>&lt;p>On Linux, the natural thought is “just build a static binary”.&lt;/p>
&lt;p>On macOS, that is not usually how things work. macOS does not support the same style of fully static userland binary that Linux users often expect. You can try to statically link some third-party libraries, but the final program will still dynamically link to Apple system libraries.&lt;/p>
&lt;p>So the practical goal is:&lt;/p>
&lt;p>Bundle third-party &lt;code>.dylib&lt;/code> dependencies next to the program, and rewrite Mach-O load commands so the binary loads those local copies.&lt;/p>
&lt;p>This is similar in spirit to what many &lt;code>.app&lt;/code> bundles do, but here I wanted a plain CLI directory layout.&lt;/p>
&lt;h2 id="the-tools-involved">The tools involved
&lt;/h2>&lt;p>macOS Mach-O binaries can be inspected and patched with these tools:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">otool -L &amp;lt;file&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">install_name_tool -change &amp;lt;old&amp;gt; &amp;lt;new&amp;gt; &amp;lt;file&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">install_name_tool -id &amp;lt;new-id&amp;gt; &amp;lt;dylib&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">install_name_tool -add_rpath &amp;lt;rpath&amp;gt; &amp;lt;file&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>otool -L&lt;/code> shows dynamic library load commands.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">otool -L &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">/bin/qemu-img&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>might show:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">qemu-img:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib (compatibility version 8801.0.0, current version 8801.0.0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /opt/homebrew/opt/zstd/lib/libzstd.1.dylib (compatibility version 1.0.0, current version 1.5.7)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Those can be rewritten with:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">install_name_tool -change &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> /opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s1">&amp;#39;@executable_path/../lib/libglib-2.0.0.dylib&amp;#39;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">/bin/qemu-img&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>@executable_path&lt;/code> means “the directory containing the executable being launched”.&lt;/p>
&lt;p>So it will try to find &lt;code>libglib-2.0.0.dylib&lt;/code> in &lt;code>../lib/&lt;/code> relative to the executable, no longer looking in &lt;code>/opt/homebrew&lt;/code>.&lt;/p>
&lt;h2 id="first-attempt-patch-direct-dependencies-only">First attempt: patch direct dependencies only
&lt;/h2>&lt;p>My first attempt was simple:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">otool -L &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>/bin/* &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="p">|&lt;/span> grep /opt/homebrew &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="p">|&lt;/span> sort &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="p">|&lt;/span> uniq &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{print $1}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This found direct Homebrew dependencies used by QEMU binaries, such as:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/opt/homebrew/opt/glib/lib/libgio-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/opt/homebrew/opt/glib/lib/libgmodule-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/opt/homebrew/opt/glib/lib/libgobject-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/opt/homebrew/opt/zstd/lib/libzstd.1.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Then I copied those libraries into &lt;code>$PREFIX/lib&lt;/code> and patched the binaries.&lt;/p>
&lt;p>This fixed some errors, but not all of them.&lt;/p>
&lt;p>Running &lt;code>qemu-img&lt;/code> then failed with a different error:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">dyld[97126]: Library not loaded: /opt/homebrew/opt/gettext/lib/libintl.8.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Referenced from: /Users/charlie/bin/_qemu-11.0.1/lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>So I realized that it is not enough to patch only the binaries. The copied &lt;code>.dylib&lt;/code> files have their own dependencies too.&lt;/p>
&lt;p>In this case:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">qemu-img
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; libintl.8.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>libintl.8.dylib&lt;/code> was not directly referenced by &lt;code>qemu-img&lt;/code>. It was a transitive dependency of &lt;code>libglib&lt;/code>.&lt;/p>
&lt;h2 id="the-correct-model">The correct model
&lt;/h2>&lt;p>The dependency graph looks like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">bin/qemu-img
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; /opt/homebrew/opt/glib/lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; /opt/homebrew/opt/zstd/lib/libzstd.1.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; /opt/homebrew/opt/gettext/lib/libintl.8.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; /opt/homebrew/opt/pcre2/lib/libpcre2-8.0.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>So the bundling process must be recursive:&lt;/p>
&lt;ol>
&lt;li>Scan Mach-O files in &lt;code>bin/&lt;/code>.&lt;/li>
&lt;li>Find its &lt;code>/opt/homebrew&lt;/code> dependencies.&lt;/li>
&lt;li>Copy those &lt;code>.dylib&lt;/code> files into &lt;code>lib/&lt;/code>.&lt;/li>
&lt;li>Patch the binaries to refer to the bundled copies (&lt;code>../lib&lt;/code>).&lt;/li>
&lt;li>Scan the copied &lt;code>.dylib&lt;/code> files.&lt;/li>
&lt;li>Copy and patch their dependencies too.&lt;/li>
&lt;li>Repeat until no &lt;code>/opt/homebrew&lt;/code> dependencies remain.&lt;/li>
&lt;/ol>
&lt;h2 id="which-paths-should-be-used">Which paths should be used?
&lt;/h2>&lt;p>For executables in &lt;code>bin/&lt;/code>, I use:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">@executable_path/../lib/libfoo.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>For dylibs inside &lt;code>lib/&lt;/code>, I use:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">@loader_path/libfoo.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>
&lt;p>&lt;code>@executable_path&lt;/code> is relative to the main executable being launched.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>@loader_path&lt;/code> is relative to the Mach-O file that is doing the loading. For dependencies between dylibs, this is what we want:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">lib/libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; @loader_path/libintl.8.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>Since both files are in the same &lt;code>lib/&lt;/code> directory, this resolves correctly.&lt;/p>
&lt;h2 id="the-final-bundling-script">The final bundling script
&lt;/h2>&lt;p>I ended up writing a small Python script called &lt;code>macho-bundle-deps&lt;/code>. You can find it here: &lt;a class="link" href="https://github.com/charlie0129/dotfiles/blob/f15791054e517f6b5afc892312f1db73f331d475/bin/darwin/macho-bundle-deps" target="_blank" rel="noopener"
>https://github.com/charlie0129/dotfiles/blob/f15791054e517f6b5afc892312f1db73f331d475/bin/darwin/macho-bundle-deps&lt;/a>. This is a permanent link to a specific commit, so you may want to check the latest version in the repository.&lt;/p>
&lt;p>Usage:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PREFIX&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/bin/_qemu-11.0.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">macho-bundle-deps &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --lib-dir &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">/lib&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --prefix /opt/homebrew &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>/bin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Or, from inside the QEMU prefix:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/bin/_qemu-11.0.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">macho-bundle-deps --lib-dir lib bin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The script does the following:&lt;/p>
&lt;ul>
&lt;li>finds Mach-O files from the input paths&lt;/li>
&lt;li>scans their dependencies with &lt;code>otool -L&lt;/code>&lt;/li>
&lt;li>copies matching dependencies into &lt;code>--lib-dir&lt;/code>&lt;/li>
&lt;li>patches executable dependencies to &lt;code>@executable_path/../lib/...&lt;/code>&lt;/li>
&lt;li>patches bundled dylib dependencies to &lt;code>@loader_path/...&lt;/code>&lt;/li>
&lt;li>patches copied dylib IDs with &lt;code>install_name_tool -id&lt;/code>&lt;/li>
&lt;li>recursively processes newly copied dylibs&lt;/li>
&lt;li>reports an error if matching absolute dependency paths remain&lt;/li>
&lt;/ul>
&lt;h2 id="verifying-the-result">Verifying the result
&lt;/h2>&lt;p>After running the bundler, I verify that no Homebrew paths remain:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">otool -L &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>/bin/* &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>/lib/*.dylib &lt;span class="p">|&lt;/span> grep /opt/homebrew
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now I can uninstall the dependencies from Homebrew, and the bundled QEMU still works:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">brew uninstall libffi gettext glib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">qemu-img --help
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="notes-and-limitations">Notes and limitations
&lt;/h2>&lt;p>This is not the same as producing a fully static binary.&lt;/p>
&lt;p>The result still depends on macOS system libraries, which is normal:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/usr/lib/libSystem.B.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/System/Library/Frameworks/...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>It also does not magically make a binary portable across all macOS versions or CPU architectures. A binary built on Apple Silicon is still an arm64 Mach-O binary unless built otherwise.&lt;/p>
&lt;p>This approach is mainly useful for making a local CLI tool directory self-contained with respect to Homebrew dependencies.&lt;/p>
&lt;h2 id="why-copying-symlinks-should-be-avoided">Why copying symlinks should be avoided
&lt;/h2>&lt;p>Homebrew often has dylib symlinks like:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">libintl.dylib -&amp;gt; libintl.8.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>When bundling, prefer copying the symlink target, not the symlink itself.&lt;/p>
&lt;p>In shell, that means:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">cp -L source.dylib &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PREFIX&lt;/span>&lt;span class="s2">/lib/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In Python, &lt;code>shutil.copy2(..., follow_symlinks=True)&lt;/code> does the same thing.&lt;/p>
&lt;p>This avoids ending up with a bundled symlink pointing to a non-existent file.&lt;/p>
&lt;h2 id="final-layout">Final layout
&lt;/h2>&lt;p>After bundling, the QEMU directory looks like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">_qemu-11.0.1/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── bin/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-img
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-io
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-nbd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-system-aarch64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── qemu-system-x86_64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libffi.8.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libgio-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libglib-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libgmodule-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libgobject-2.0.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libintl.8.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── libpcre2-8.0.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── libzstd.1.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now &lt;code>qemu-img&lt;/code> no longer cares whether Homebrew’s &lt;code>glib&lt;/code>, &lt;code>gettext&lt;/code>, &lt;code>pcre2&lt;/code>, or &lt;code>zstd&lt;/code> packages are installed.&lt;/p>
&lt;h2 id="conclusion">Conclusion
&lt;/h2>&lt;p>The main lesson is that macOS dependency bundling is graph traversal, not a one-shot patch.&lt;/p>
&lt;p>Patching only the top-level binaries is not enough. You also need to patch the dylibs that you copied, and then patch the dylibs that those dylibs depend on.&lt;/p>
&lt;p>The final strategy is:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">scan -&amp;gt; copy -&amp;gt; patch -&amp;gt; scan copied dylibs -&amp;gt; repeat
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>For executables:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/opt/homebrew/.../libfoo.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; @executable_path/../lib/libfoo.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>For bundled dylibs:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/opt/homebrew/.../libbar.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; @loader_path/libbar.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>And for the dylib’s own install name:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/opt/homebrew/.../libfoo.dylib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; @loader_path/libfoo.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div></description></item><item><title>Fake macOS Version Detected by Homebrew</title><link>https://charlie0129.github.io/blog/p/homebrew-fake-macos-version/</link><pubDate>Tue, 08 Oct 2024 22:08:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/homebrew-fake-macos-version/</guid><description>&lt;h2 id="background">Background
&lt;/h2>&lt;p>One particular issue that I encountered when I tried to install some software using Homebrew on my Mac is that Homebrew deprecates support for older macOS versions just like Apple do. Once a macOS version is deprecated, Homebrew will not provide bottles (precompiled binaries) for that version. This means that you have to compile the software from source, which is extremely time-consuming and sometimes error-prone. Imagine installing &lt;code>shell-check&lt;/code> will require to build &lt;code>ghc&lt;/code> from source, which takes hours, and then use &lt;code>ghc&lt;/code> to build &lt;code>shell-check&lt;/code> from source. This is not a good experience.&lt;/p>
&lt;h2 id="solution">Solution
&lt;/h2>&lt;h3 id="pin-homebrewcore-to-an-older-version">Pin homebrew/core to an older version
&lt;/h3>&lt;p>One way to work around this issue is to use a older version of the &lt;code>homebrew/core&lt;/code> tap, such that the bottles are still available for the macOS version you are using because they are built when your macOS version is supported. The downside is that you will be using outdated software. If this is not an issue for you then congratulations. You can achieve this by running the following command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> brew tap homebrew/core
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> &lt;span class="nb">cd&lt;/span> &lt;span class="k">$(&lt;/span>brew --repo homebrew/core&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> git reset --hard &amp;lt;commit-hash&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Make sure you disable the auto-update of Homebrew so that it does not update the &lt;code>homebrew/core&lt;/code> tap to the latest version. You can do this by running the following command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;export HOMEBREW_NO_AUTO_UPDATE=1&amp;#39;&lt;/span> &amp;gt;&amp;gt; ~/.zshrc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="fake-macos-version">Fake macOS version
&lt;/h3>&lt;p>Another way is to fake the macOS version that Homebrew detects. This way, Homebrew will provide bottles for the macOS version you are faking. Most software will work and you will be using the latest software. However, this may not work for all software as some software may have dependencies that are not available for the macOS version you are faking. You can achieve this by running the following command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> &lt;span class="nb">export&lt;/span> &lt;span class="nv">HOMEBREW_FAKE_MACOS&lt;/span>&lt;span class="o">=&lt;/span>13.0 &lt;span class="c1"># Choose a macOS version that is supported by Homebrew and close to your macOS version&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> brew install xxx
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="explanation">Explanation
&lt;/h2>&lt;p>Homebrew actually has an private undocumented API that allows you to fake the macOS version. &lt;a class="link" href="https://github.com/Homebrew/brew/blob/a3d8f4e0e4a22da9990d59cca70bec1e7be726cf/Library/Homebrew/os/mac.rb#L41" target="_blank" rel="noopener"
>https://github.com/Homebrew/brew/blob/a3d8f4e0e4a22da9990d59cca70bec1e7be726cf/Library/Homebrew/os/mac.rb#L41&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># This can be compared to numerics, strings, or symbols&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># using the standard Ruby Comparable methods.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># @api internal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sig&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">returns&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">MacOSVersion&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">full_version&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@full_version&lt;/span> &lt;span class="o">||=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fake_macos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ENV&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;HOMEBREW_FAKE_MACOS&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kp">nil&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># for Portable Ruby building&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">MacOSVersion&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fake_macos&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">MacOSVersion&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">VERSION&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="example">Example
&lt;/h2>&lt;p>I have macOS Monterey (12.0) running, which is just deprecated by Homebrew. I want to install &lt;code>batt&lt;/code> which is not available as a bottle for macOS Monterey.&lt;/p>
&lt;p>If I run &lt;code>brew install batt&lt;/code>, I will get the following error:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> brew install batt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Warning: You are using macOS 12.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">We (and Apple) do not provide support for this old version.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">It is expected behaviour that some formulae will fail to build in this old version.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">It is expected behaviour that Homebrew will be buggy and slow.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Do not create any issues about this on Homebrew&amp;#39;s GitHub repositories.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Do not create any issues even if you think this message is unrelated.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Any opened issues will be immediately closed without response.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Do not ask for help from Homebrew or its maintainers on social media.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">You may ask for help in Homebrew&amp;#39;s discussions but are unlikely to receive a response.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Try to figure out the problem yourself and submit a fix as a pull request.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">We will review it but may or may not accept it.
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>It&amp;rsquo;s Oct 8, 2024 and the latest macOS version is macOS Sequoia (15.0). So the last 3 supported macOS versions are:&lt;/p>
&lt;ul>
&lt;li>macOS Sequoia (15.0)&lt;/li>
&lt;li>macOS Sonoma (14.0)&lt;/li>
&lt;li>macOS Ventura (13.0)&lt;/li>
&lt;/ul>
&lt;p>You can confirm that by checking out &lt;code>batt&lt;/code>&amp;rsquo;s formula:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> brew edit batt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">sha256 cellar: :any_skip_relocation, arm64_sequoia: &amp;#34;61bd7790a82f2269b9a0ce1585c57564d03be4e4ac89b8f0d8843c0073a688e6&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">sha256 cellar: :any_skip_relocation, arm64_sonoma: &amp;#34;198bd7bb9a808f0a9e4cb1a31b7e9c2a72d690ba1d07ebddf78b6d0ce0b6dd03&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">sha256 cellar: :any_skip_relocation, arm64_ventura: &amp;#34;6eff598159b263327b8b562ab32f5e5e7157c20f25cbecfa08b48eda794c4c43&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">...
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Showing only the bottles for macOS Sequoia (15.0), macOS Sonoma (14.0), and macOS Ventura (13.0) are available.&lt;/p>
&lt;p>Sadly my macOS version (Monterey 12.0) is not in the list, but macOS Ventura (13.0) is the closest to macOS Monterey (12.0). So I can fake the macOS version to macOS Ventura (13.0) by running the following command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> &lt;span class="nb">export&lt;/span> &lt;span class="nv">HOMEBREW_FAKE_MACOS&lt;/span>&lt;span class="o">=&lt;/span>13.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> brew config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">macOS: 13.0-arm64 # YES! I&amp;#39;m now faking macOS Ventura (13.0)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="gp">$&lt;/span> brew install batt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/batt/manifests/0.3.1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Already downloaded: /Users/charlie/Library/Caches/Homebrew/downloads/86da7d77f0bacb44475e0280eef162148e3b51df8ea44ff9bc16a5a7bba6d39f--batt-0.3.1.bottle_manifest.json
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">==&amp;gt; Fetching batt
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/batt/blobs/sha256:6eff598159b263327b8b562ab32f5e5e7157c20f25cbecfa08b48eda794c4c43
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Already downloaded: /Users/charlie/Library/Caches/Homebrew/downloads/24fc002de2ee11096a544dd84ff1033262a5cb627c2b824c915a552bb6a588dd--batt--0.3.1.arm64_ventura.bottle.tar.gz
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>As you can see, Homebrew is downloading the bottle for macOS Ventura (13.0) successfully! (Of course, the log is showing that I have already downloaded earlier, but you get the idea.)&lt;/p>
&lt;p>&lt;code>batt&lt;/code> itself does not rely on any APIs that are not available in my current macOS version, so it works perfectly fine.&lt;/p></description></item><item><title>macOS Nix First Taste</title><link>https://charlie0129.github.io/blog/p/macos-nix-first-taste/</link><pubDate>Thu, 27 Jun 2024 15:13:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/macos-nix-first-taste/</guid><description>&lt;h2 id="note">Note
&lt;/h2>&lt;p>Note that this blog currently acts as a reference for me. It is not a comprehensive guide on why and how to use Nix on macOS.&lt;/p>
&lt;p>I chose nix because I am tired of Homebrew&amp;rsquo;s slowness and NOT PROVIDING BOTTLES (BINARY) FOR OLD MACOS VERSIONS.&lt;/p>
&lt;h2 id="install">Install
&lt;/h2>&lt;p>I am not using the official installer because it cannot survive macOS updates. Instead, I am using the Determinate System&amp;rsquo;s Nix Installer.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ARCH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>uname -m &lt;span class="p">|&lt;/span> sed &lt;span class="s1">&amp;#39;s/arm64/aarch64/&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">OS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>uname -s &lt;span class="p">|&lt;/span> tr &lt;span class="s1">&amp;#39;[:upper:]&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;[:lower:]&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -L -o nix-installer https://github.com/DeterminateSystems/nix-installer/releases/latest/download/nix-installer-&lt;span class="nv">$ARCH&lt;/span>-&lt;span class="nv">$OS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod +x nix-installer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Optionally, move nix-installer to your PATH&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># sudo install nix-installer /usr/local/bin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./nix-installer install --explain
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="pin-nixpkgs">Pin Nixpkgs
&lt;/h2>&lt;p>I don&amp;rsquo;t want to download a tarball and extract it every now and then. I know you can set tarball-ttl to a higher value, but I don&amp;rsquo;t want to do that either. Just pin nixpkgs.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nix registry pin nixpkgs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If you are like me in China, you may want to use a mirror.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;substituters = https://mirrors.sjtug.sjtu.edu.cn/nix-channels/store/ https://cache.nixos.org/&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> sudo tee -a /etc/nix/nix.conf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="using">Using
&lt;/h2>&lt;p>I wrote some scripts just to make my life easier.&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/charlie0129/dotfiles/blob/master/bin/common/nix-install" target="_blank" rel="noopener"
>nix-install&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="htts://github.com/charlie0129/dotfiles/blob/master/bin/common/nix-list" >nix-list&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="htts://github.com/charlie0129/dotfiles/blob/master/bin/common/nix-search" >nix-search&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="htts://github.com/charlie0129/dotfiles/blob/master/bin/common/nix-uninstall" >nix-uninstall&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="later-steps">Later Steps
&lt;/h2>&lt;p>There is &lt;a class="link" href="https://github.com/LnL7/nix-darwin" target="_blank" rel="noopener"
>nix-darwin&lt;/a> to make declarative configuration easier. I don&amp;rsquo;t have time to try it yet but I will definitely give it a try when I have time.&lt;/p></description></item></channel></rss>