<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tweag - Engineering blog]]></title><description><![CDATA[
Scale your engineering power. We enable deep-tech startups to achieve
their vision, from research to product delivery.
    ]]></description><link>https://tweag.io</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 26 Feb 2026 19:54:14 GMT</lastBuildDate><item><title><![CDATA[Nickel since 1.0]]></title><description><![CDATA[<p>We released Nickel 1.0 in May 2023. Since then, we’ve been working so hard
on new features, bug fixes, and performance improvements that we haven’t had
the opportunity to write about them as much as we would’ve liked. This post
rounds up some of the big changes that we’ve landed over the past few years.</p>
<h2 id="new-language-features" style="position:relative;"><a href="#new-language-features" aria-label="new language features permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>New language features</h2>
<h3 id="algebraic-data-types" style="position:relative;"><a href="#algebraic-data-types" aria-label="algebraic data types permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Algebraic data types</h3>
<p>The biggest new language feature is one that we have actually <a href="https://www.tweag.io/blog/2024-09-05-algebraic-data-types-nickel/">written
about</a>
before: algebraic data types — or <em>enum variants</em> in Nickel terminology —
first landed in <a href="https://github.com/tweag/nickel/releases/tag/1.5.0">Nickel 1.5</a>. Nickel has supported plain enums
for a long time: <code class="language-text">[| 'Carnitas, 'Fish |]</code> is the type of something that
can take two possible values: <code class="language-text">'Carnitas</code> or <code class="language-text">'Fish</code>. Enum variants extend
these by allowing the enum types to specify payloads, like
<code class="language-text">[| 'Carnitas { pineapple : Number }, 'Fish { avocado : Number, cheese : Number } |]</code>.
Types like this are supported by many modern programming languages, as
they are useful for encoding important invariants like the fact that carnitas
tacos can be topped with pineapple but not avocado. For more on the design
and motivation for algebraic data types in Nickel, see
<a href="https://www.tweag.io/blog/2024-09-05-algebraic-data-types-nickel/">our other post</a>.</p>
<h3 id="pattern-matching" style="position:relative;"><a href="#pattern-matching" aria-label="pattern matching permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Pattern matching</h3>
<p>Nickel has had a <code class="language-text">match</code> statement for a while, but it used to be quite limited.
<a href="https://github.com/tweag/nickel/releases/tag/1.5.0">Nickel 1.5</a> and
<a href="https://github.com/tweag/nickel/releases/tag/1.7.0">Nickel 1.7</a> extended it significantly: not only can you now match
the enum variants we mentioned above, you can also match arrays, records, and constants.
You can also match the “or” of two patterns, and you can guard matches with predicates.</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token keyword">match</span><span class="token operator"> </span><span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">e</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator"> </span><span class="token keyword">if</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">e</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">></span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">5</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">f</span><span class="token operator">a</span><span class="token operator">i</span><span class="token operator">l</span><span class="token operator">_</span><span class="token operator">w</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token string">"too much pineapple"</span><span class="token operator">,</span>

<span class="token operator"> </span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">.</span><span class="token operator">.</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">.</span><span class="token operator">.</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator"> </span><span class="token operator">]</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">.</span><span class="token operator">.</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">.</span><span class="token operator">.</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator"> </span><span class="token operator">]</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span><span class="token operator"> </span><span class="token string">"one of each"</span><span class="token operator">,</span>
<span class="token operator">}</span></code></pre></div>
<p>Basically, if you’ve used pattern matching in another language then
Nickel’s match blocks probably have the features you’re used to.
And they’re adapted to Nickel’s gradual typing: the example match block
above will work in dynamically typed code, but in a statically typed block
it will fail to typecheck, because there’s no static type that can be
either an enum or an array.</p>
<h3 id="field-punning" style="position:relative;"><a href="#field-punning" aria-label="field punning permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Field punning</h3>
<p>Records in Nickel are recursive by default, meaning that in the record</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">tacos</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">pineapple</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">2</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">]</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">price</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">c</span><span class="token operator">e</span><span class="token operator">_</span><span class="token operator">p</span><span class="token operator">e</span><span class="token operator">r</span><span class="token operator">_</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator"> </span><span class="token operator">*</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">y</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">s</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">price_per_taco</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">5</span><span class="token operator">,</span>
<span class="token operator">}</span></code></pre></div>
<p>the name <code class="language-text">price_per_taco</code> in the definition of <code class="language-text">price</code> refers to the field
<code class="language-text">price_per_taco</code> defined within the record. This is behavior is <em>usually</em> very handy,
but it can be annoying when you’re trying to define a field whose name shadows
something in an outer scope. For example, suppose you want to move the definition
of <code class="language-text">tacos</code> outside the record:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token keyword">let</span><span class="token operator"> </span><span class="token property">tacos</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">pineapple</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">2</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">]</span><span class="token operator"> </span><span class="token keyword">in</span>
<span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">tacos</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">s</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">price</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">c</span><span class="token operator">e</span><span class="token operator">_</span><span class="token operator">p</span><span class="token operator">e</span><span class="token operator">r</span><span class="token operator">_</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator"> </span><span class="token operator">*</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">y</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">s</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">price_per_taco</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">5</span><span class="token operator">,</span>
<span class="token operator">}</span></code></pre></div>
<p>This probably doesn’t do what you want: it recurses infinitely, because
in the <code class="language-text">tacos = tacos</code> line, the <code class="language-text">tacos</code> on the right side of the equals
sign refers to the name <code class="language-text">tacos</code> that’s being defined on the left hand side
(and not, as you might expect, the <code class="language-text">tacos</code> in <code class="language-text">let tacos = ... in</code>).
There are workarounds (like calling the outer variable <code class="language-text">tacos_</code> instead),
but they’re annoying. <a href="https://github.com/tweag/nickel/releases/tag/1.12.0">Nickel 1.12</a> added the <code class="language-text">include</code> keyword,
where <code class="language-text">{ include tacos }</code> means <code class="language-text">{ tacos = &lt;tacos-from-the-outer-scope> }</code>.</p>
<h3 id="let-blocks" style="position:relative;"><a href="#let-blocks" aria-label="let blocks permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Let blocks</h3>
<p>Nickel binds local variables using a <code class="language-text">let</code> statement, as in <code class="language-text">let x = 1 in x + x</code>. Before <a href="https://github.com/tweag/nickel/releases/tag/1.9.0">Nickel 1.9</a> you could only bind one variable at a time — as
in <code class="language-text">let x = 1 in let y = 2 in x + y</code> — but now you can bind multiple variables
in a single block, as in <code class="language-text">let x = 1, y = 2 in x + y</code>. In most situations this is
just a small syntactic convenience,<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup> but with <em>recursive</em> let
blocks you actually gain some expressive power. For example, they allow you to write
mutually recursive functions without putting them in a record (which used to be the
only way to create a recursive environment in Nickel):</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token keyword">let</span><span class="token operator"> </span><span class="token keyword">rec</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">is_even</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token keyword">fun</span><span class="token operator"> </span><span class="token operator">x</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span><span class="token operator"> </span><span class="token keyword">if</span><span class="token operator"> </span><span class="token property">x</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">0</span><span class="token operator"> </span><span class="token keyword">then</span><span class="token operator"> </span><span class="token constant">true</span><span class="token operator"> </span><span class="token keyword">else</span><span class="token operator"> </span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">o</span><span class="token operator">d</span><span class="token operator">d</span><span class="token operator"> </span><span class="token operator">(</span><span class="token operator">x</span><span class="token operator"> </span><span class="token operator">-</span><span class="token operator"> </span><span class="token number">1</span><span class="token operator">)</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">is_odd</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token keyword">fun</span><span class="token operator"> </span><span class="token operator">x</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span><span class="token operator"> </span><span class="token keyword">if</span><span class="token operator"> </span><span class="token property">x</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">0</span><span class="token operator"> </span><span class="token keyword">then</span><span class="token operator"> </span><span class="token constant">false</span><span class="token operator"> </span><span class="token keyword">else</span><span class="token operator"> </span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">e</span><span class="token operator">v</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator"> </span><span class="token operator">(</span><span class="token operator">x</span><span class="token operator"> </span><span class="token operator">-</span><span class="token operator"> </span><span class="token number">1</span><span class="token operator">)</span><span class="token operator">,</span>
<span class="token operator"></span><span class="token keyword">in</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">e</span><span class="token operator">v</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator"> </span><span class="token number">42</span></code></pre></div>
<h3 id="better-contract-constructors" style="position:relative;"><a href="#better-contract-constructors" aria-label="better contract constructors permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Better contract constructors</h3>
<p>Custom contracts were reworked in <a href="https://github.com/tweag/nickel/releases/tag/1.8.0">Nickel 1.8</a>, allowing for better control of a
contract’s eagerness, more precise error locations, and better composability.
Nickel’s standard library now offers three contract constructors. The
simplest is <code class="language-text">std.contract.from_predicate</code>, which turns a predicate (of type <code class="language-text">Dyn -> Bool</code>)
into a contract. <code class="language-text">std.contract.from_validator</code> is slightly more complicated but offers
better control over error messages, while <code class="language-text">std.contract.custom</code> offers the most control.</p>
<p>A full description of the contract changes is out of scope for this blog post — there’s
a whole <a href="https://nickel-lang.org/user-manual/contracts/#user-defined-contracts">section</a> of the manual devoted to it. But the key point is that
contracts in Nickel are partly eager and partly lazy. For example, the contract in</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token keyword">let</span><span class="token operator"> </span><span class="token property">Taco</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator">|</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator">]</span><span class="token operator"> </span><span class="token keyword">in</span>
<span class="token operator"></span><span class="token keyword">let</span><span class="token operator"> </span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator"> </span><span class="token class-name">Array</span><span class="token operator"> </span><span class="token property">Taco</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">r</span><span class="token operator">u</span><span class="token operator">n</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">y</span><span class="token operator">T</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">S</span><span class="token operator">u</span><span class="token operator">p</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator">]</span><span class="token operator"> </span><span class="token keyword">in</span>
<span class="token operator">&lt;</span><span class="token operator">s</span><span class="token operator">o</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">></span></code></pre></div>
<p>gets applied in two stages. When <code class="language-text">tacos</code> first gets evaluated, the contract checks that
<code class="language-text">tacos</code> is an array. But rather than validating the array elements immediately,
it propagates the element contracts inside the array and leaves them
unevaluated; essentially, <code class="language-text">tacos</code> gets evaluated to
<code class="language-text">['Carnitas | Taco, 'CrunchyTacoSupreme | Taco]</code>. Only when the <em>elements</em> of the array get
evaluated are their contracts checked. In particular, if the array elements are
never actually evaluated (for example, if <code class="language-text">&lt;something></code> is <code class="language-text">std.array.length tacos</code>, which doesn’t evaluate the individual elements) then we’ll never find
out that <code class="language-text">'CrunchyTacoSupreme</code> isn’t actually a <code class="language-text">Taco</code>.</p>
<p>The lazy/eager distinction has been part of Nickel’s built-in record and array
contracts since the beginning, but never fully exploitable by custom contracts.
The new <code class="language-text">std.contract.custom</code> constructor creates a contract with explicit lazy
and eager parts, and the <code class="language-text">std.contract.check</code> function allows for speculatively
checking the eager part of a contract without bailing out if it fails. Together,
these ingredients allowed us to create useful <a href="https://www.tweag.io/blog/2022-04-28-union-intersection-contracts/">union contracts</a> (<code class="language-text">std.contract.any_of</code>)
and improve the error reporting of the eager contracts in <a href="https://github.com/nickel-lang/json-schema-to-nickel/">json-schema-to-nickel</a>,
our tool for converting JSON schemas to Nickel contracts.</p>
<h2 id="performance-improvements" style="position:relative;"><a href="#performance-improvements" aria-label="performance improvements permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Performance improvements</h2>
<p>For Nickel 1.0, we were focused on getting the basic language right. Since then
(and especially over the past year), we’ve been working on getting the interpreter
to run faster. While the performance improvements you observe will depend heavily
on your use case, we’ve seen large user-provided Nickel configurations that
evaluate 10x faster now than they were two years ago (and 3x faster than six months ago).
The most recent performance improvements are part of our progress towards
a <a href="https://github.com/tweag/nickel/blob/c483fe2811fa6b271d96cf87b8b3d3872fe03d8f/rfcs/007-bytecode-interpreter.md">bytecode interpreter</a>. We’ve been landing these improvements gradually over the
past year or so, but most of that preparation only had a performance impact
starting in <a href="https://github.com/tweag/nickel/releases/tag/1.15.1">Nickel 1.15</a>.</p>
<h3 id="standard-library-improvements" style="position:relative;"><a href="#standard-library-improvements" aria-label="standard library improvements permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Standard library improvements</h3>
<p>Nickel’s <a href="https://nickel-lang.org/stdlib/std/">standard library</a> has roughly doubled in size since Nickel 1.0, offering many useful
utility functions (like <code class="language-text">std.record.get_or</code> or <code class="language-text">std.string.find_all</code>) and contract combinators
(like <code class="language-text">std.contract.Sequence</code> or <code class="language-text">std.contract.any_of</code>). The standard library now also
contains a useful set of trigonometric and other numeric functions,
<a href="https://github.com/nickel-lang/nickel/issues/2005">contributed</a> by a community member who was
using Nickel to configure a robot.</p>
<h2 id="tooling-and-distribution-improvements" style="position:relative;"><a href="#tooling-and-distribution-improvements" aria-label="tooling and distribution improvements permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Tooling and distribution improvements</h2>
<p>Nickel has seen many improvements that are not directly tied to the Nickel
language itself.</p>
<h3 id="language-server-improvements" style="position:relative;"><a href="#language-server-improvements" aria-label="language server improvements permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Language server improvements</h3>
<p>Nickel’s language server (NLS) has seen many improvements, especially
in <a href="https://github.com/tweag/nickel/releases/tag/1.2.0">Nickel 1.2</a> and <a href="https://github.com/tweag/nickel/releases/tag/1.3.0">1.3</a>. It now supports finding references
and definitions, listing symbols, and various other table-stakes language
server features. Completions have also been improved substantially since
version 1.0, and can make intelligent use of type- and contract-related
information. For example, in</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">‸</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator"> </span><span class="token operator">[</span><span class="token operator">|</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">C</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">e</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">:</span><span class="token operator"> </span><span class="token class-name">Number</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token operator">a</span><span class="token operator">v</span><span class="token operator">o</span><span class="token operator">c</span><span class="token operator">a</span><span class="token operator">d</span><span class="token operator">o</span><span class="token operator"> </span><span class="token operator">:</span><span class="token operator"> </span><span class="token class-name">Number</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">e</span><span class="token operator">s</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">:</span><span class="token operator"> </span><span class="token class-name">Number</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator">]</span>
<span class="token operator"></span><span class="token comment">#           └── cursor is here</span></code></pre></div>
<p>NLS knows to offer “pineapple” as a completion, but not “avocado”.</p>
<p>NLS has also gained the ability to offer diagnostics for <em>evaluation</em>
errors. This is very useful in Nickel because contract errors are detected
during evaluation instead of during typechecking. In-editor detection of
contract violations is part of the vision
articulated in a <a href="https://www.tweag.io/blog/2024-05-16-nickel-programmable-lsp/">previous blog post</a>, where configuration
errors are <a href="https://en.wikipedia.org/wiki/Shift-left_testing">left-shifted</a>
(because you get them as you type) and infinitely customizable (because
contracts are arbitrary code). Since the previous post was written, the
diagnostics have been further improved thanks to the contract
improvements mentioned above: the problematic field now gets
highlighted directly.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/de296f77c52d37c3a7c3d77ab91aee42/949b7/kubernetes.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 67.56756756756756%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAABTUlEQVQoz42SaY7kIAyFc5pKCHgDjA219P1vNcrSmZJGXT3S9wNkHn5+8iTlwfoUfXJ9SR6SB+uL6xNkYB6JHfO4SNyBbEn1YBJ2Yo9skVqAGlINoCu1FTcCaMDzfF5B/4pdhuVetVcbJEbYUm6J2hLLRqpzLBdLKpdyExf2xl2ll+wlO/Pmav5m+ciE2L7qQ9tQG6K96hAyxIZomT1h+/DFtIJ+1UfjzmSMBtDOsUEjaPjc+RbLPd9f9VHYKzlCO774L9tzLIDtnoeVrm1Qca5Oxd9T/VF8xGPSM3sW33JGDagLvL0DXUAPI+9ZTkc5YkPpwD2SRTIgxw2Db7Zd2L2soGkngJ6dAZvn0WW00kk8k1X2yl3IMhmTqfRMPqfCR0k6YNtmjnuwVzxzrLdYDi6Tt3NJtp3ZSqnMqUzr3vbfMMLOL4EFqL+m+pP4D12InAQRsWQKAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="png"
        title="png"
        src="/static/de296f77c52d37c3a7c3d77ab91aee42/fcda8/kubernetes.png"
        srcset="/static/de296f77c52d37c3a7c3d77ab91aee42/12f09/kubernetes.png 148w,
/static/de296f77c52d37c3a7c3d77ab91aee42/e4a3f/kubernetes.png 295w,
/static/de296f77c52d37c3a7c3d77ab91aee42/fcda8/kubernetes.png 590w,
/static/de296f77c52d37c3a7c3d77ab91aee42/efc66/kubernetes.png 885w,
/static/de296f77c52d37c3a7c3d77ab91aee42/c83ae/kubernetes.png 1180w,
/static/de296f77c52d37c3a7c3d77ab91aee42/949b7/kubernetes.png 1583w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<h3 id="unit-tests" style="position:relative;"><a href="#unit-tests" aria-label="unit tests permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Unit tests</h3>
<p>Since <a href="https://github.com/tweag/nickel/releases/tag/1.9.0">Nickel 1.9</a>, there is a <code class="language-text">nickel test</code> command that executes
unit tests contained in documentation comments.</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator">m</span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">_</span><span class="token operator">a</span><span class="token operator">v</span><span class="token operator">o</span><span class="token operator">c</span><span class="token operator">a</span><span class="token operator">d</span><span class="token operator">o</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator"> </span><span class="token keyword">doc</span><span class="token operator"> </span><span class="token string">m%"
      Double the avocado!

      Here's an example that is automatically treated as a unit test:
      ```nickel
        more_avocado ('Fish { avocado = 1 })
        # => 'Fish { avocado = 2 }
      ```
      "%</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token keyword">fun</span><span class="token operator"> </span><span class="token operator">(</span><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">avocado</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">a</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">)</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">avocado</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">3</span><span class="token operator"> </span><span class="token operator">*</span><span class="token operator"> </span><span class="token operator">a</span><span class="token operator"> </span><span class="token operator">}</span>
<span class="token operator">}</span></code></pre></div>
<p>Running <code class="language-text">nickel test</code> on this file will highlight the typo in the
function definition:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">testing more_avocado/0...FAILED
test more_avocado/0 failed
error: contract broken by a value
   ┌─ &lt;unknown> (generated by evaluation):1:1
   │
 1 │ std.contract.Equal ('Fish { avocado = 2, })
   │ ------------------------------------------- expected type
   │
  &lt;snip...>
   ┌─ input.ncl:12:38
   │
12 │     = fun ('Fish { avocado = a }) => 'Fish { avocado = 3 * a }
   │                                      ------------------------- evaluated to this expression

1 failures
error: tests failed</code></pre></div>
<h3 id="jsonyamltoml-interop" style="position:relative;"><a href="#jsonyamltoml-interop" aria-label="jsonyamltoml interop permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>JSON/YAML/TOML interop</h3>
<p>Interoperability with plain data formats (JSON, YAML, and TOML) has been improved in several ways.</p>
<ul>
<li>The YAML format allows for several YAML documents to be embedded in the
same file (separated by <code class="language-text">---</code> lines). We can read such files since
<a href="https://github.com/tweag/nickel/releases/tag/1.2.0">Nickel 1.2</a>, and we can write them since <a href="https://github.com/tweag/nickel/releases/tag/1.15.1">Nickel 1.15</a>:
from Nickel 1.15 onwards, <code class="language-text">nickel export --format yaml-documents</code> will export a Nickel
list to a collection of YAML documents (as opposed to <code class="language-text">nickel export --format yaml</code>, which
outputs a single YAML document that contains a list). Similarly, Nickel 1.15’s
standard library serialization functions support a new <code class="language-text">'YamlDocuments</code> format.</li>
<li>The <code class="language-text">nickel convert</code> command, added in <a href="https://github.com/tweag/nickel/releases/tag/1.15.1">Nickel 1.15</a> allows
conversion of JSON, YAML, or TOML to Nickel. This complements the long-supported ability to
import data formats as in <code class="language-text">import "file.json"</code>: while importing data formats is
useful for consuming data produced by some other tool, the new conversion feature
allows for migrating other configuration to Nickel.</li>
<li>Since <a href="https://github.com/tweag/nickel/releases/tag/1.3.0">Nickel 1.3</a>, the <code class="language-text">nickel</code> command line will merge plain data files into Nickel
code: if you have a JSON file containing <code class="language-text">{ "price_per_taco": 5 }</code> and a Nickel file
containing <code class="language-text">{ tacos = 3, price = price_per_taco * tacos, price_per_taco }</code> then
<code class="language-text">nickel export json_file.json nickel_file.ncl</code> will merge the JSON-specified price
into the Nickel configuration before evaluating it.</li>
</ul>
<h3 id="release-process-and-distribution" style="position:relative;"><a href="#release-process-and-distribution" aria-label="release process and distribution permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Release process and distribution</h3>
<p>For the <a href="https://github.com/tweag/nickel/releases/tag/1.0.0">Nickel 1.0</a> release, we built binaries for Linux x86_64 and aarch64 only.
Now, we’re building MacOS and Windows binaries as well. And we’re not the only distributors
of Nickel binaries: nixpkgs, Arch Linux, and Homebrew all have up-to-date Nickel packages.</p>
<p>We’ve also improved the usage of Nickel as a library. Since <a href="https://github.com/tweag/nickel/releases/tag/1.10.0">Nickel 1.10</a>, we’ve
been publishing our Python bindings on PyPI. And <a href="https://github.com/tweag/nickel/releases/tag/1.15.1">Nickel 1.15</a> saw our first
release of C and Go bindings, along with a stable Rust API.</p>
<h2 id="experimental-features" style="position:relative;"><a href="#experimental-features" aria-label="experimental features permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Experimental features</h2>
<p>Since 1.0, Nickel has grown a few experimental features for use cases that we want to
enable but don’t yet have enough confidence in the design and implementation
to fully support. Some of these features (Nix compatibility and package management)
are disabled by default; you’ll need to build Nickel with explicit support for them.
If you’re using any of these features, let us know what you’re doing
with them and whether they’re working the way you want!</p>
<h3 id="customize-mode" style="position:relative;"><a href="#customize-mode" aria-label="customize mode permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Customize mode</h3>
<p>Sometimes, writing a new configuration file for one or two settings feels unnecessary.
Our “customize mode”, introduced in <a href="https://github.com/tweag/nickel/releases/tag/1.2.0">Nickel 1.2</a>, allows configuration to be
supplied at the command line. For example, given the
<code class="language-text">{ tacos = 3, price = price_per_taco * tacos, price_per_taco }</code> example from before,
we can evaluate it with</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ nickel <span class="token builtin class-name">export</span> tacos.ncl -- <span class="token assign-left variable">price_per_taco</span><span class="token operator">=</span><span class="token number">5</span>
<span class="token punctuation">{</span>
  <span class="token string">"price"</span><span class="token builtin class-name">:</span> <span class="token number">15</span>,
  <span class="token string">"price_per_taco"</span><span class="token builtin class-name">:</span> <span class="token number">5</span>,
  <span class="token string">"tacos"</span><span class="token builtin class-name">:</span> <span class="token number">3</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Also, if you aren’t sure what options are available for setting, you can ask:</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ nickel <span class="token builtin class-name">export</span> tacos.ncl -- list
Input fields:
- price_per_taco

Overridable fields <span class="token punctuation">(</span>require <span class="token variable"><span class="token variable">`</span><span class="token parameter variable">--override</span><span class="token variable">`</span></span><span class="token punctuation">)</span>:
- price
- tacos

Use the <span class="token variable"><span class="token variable">`</span>query<span class="token variable">`</span></span> subcommand to print a detailed description of a specific field. See <span class="token variable"><span class="token variable">`</span>nickel <span class="token builtin class-name">help</span> query<span class="token variable">`</span></span><span class="token builtin class-name">.</span></code></pre></div>
<p>Since <a href="https://github.com/tweag/nickel/releases/tag/1.11.0">Nickel 1.11</a>, customize mode has had support for environment variables:
<code class="language-text">nickel export tacos.ncl -- taco_description=@env:DESC</code> will expand the <code class="language-text">DESC</code>
environment variable and substitute it into the <code class="language-text">tacos.ncl</code> configuration.
In some cases, you could achieve something similar by expanding environment
variables using your shell, but correctly handling escaping there can be
painful (or even a security risk).</p>
<h3 id="nix-compatibility" style="position:relative;"><a href="#nix-compatibility" aria-label="nix compatibility permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Nix compatibility</h3>
<p>A lot of Nickel users are also Nix users, and so Nix interoperability is an
often-requested feature. Our current Nix interface is limited to plain data,
but you can import Nix from Nickel if you’ve built Nickel with the
“nix-experimental” feature:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">price</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">c</span><span class="token operator">e</span><span class="token operator">_</span><span class="token operator">p</span><span class="token operator">e</span><span class="token operator">r</span><span class="token operator">_</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator"> </span><span class="token operator">*</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">y</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">(</span><span class="token keyword">import</span><span class="token operator"> </span><span class="token string">"tacos.nix"</span><span class="token operator">)</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">price_per_taco</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">5</span><span class="token operator">,</span>
<span class="token operator">}</span></code></pre></div>
<h3 id="package-management" style="position:relative;"><a href="#package-management" aria-label="package management permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Package management</h3>
<p>In Nickel 1.0, you could share code between projects by copying files around,
basically. <a href="https://github.com/tweag/nickel/releases/tag/1.11.0">Nickel 1.11</a> introduced package management, allowing you
to import Nickel dependencies from other directories, Git repositories, or a
central package registry. You declare your dependencies in a <code class="language-text">Nickel-pkg.ncl</code>
manifest file:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"tacos"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">authors</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">[</span><span class="token string">"Me"</span><span class="token operator">]</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">minimal_nickel_version</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"1.15.0"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">dependencies</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">salsa</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">G</span><span class="token operator">i</span><span class="token operator">t</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">package</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"github:example/salsa"</span><span class="token operator">,</span><span class="token operator"> </span><span class="token property">version</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"1.0"</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator"> </span><span class="token operator">}</span><span class="token operator">,</span>
<span class="token operator">}</span></code></pre></div>
<p>Then you can import those dependencies in your Nickel code:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">'</span><span class="token operator">F</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">{</span><span class="token operator"> </span><span class="token property">avocado</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">1</span><span class="token operator">,</span><span class="token operator"> </span><span class="token property">salsa</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">(</span><span class="token keyword">import</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">s</span><span class="token operator">a</span><span class="token operator">)</span><span class="token operator">.</span><span class="token operator">v</span><span class="token operator">e</span><span class="token operator">r</span><span class="token operator">d</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">}</span></code></pre></div>
<h2 id="thank-you" style="position:relative;"><a href="#thank-you" aria-label="thank you permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Thank you!</h2>
<p>That sums up the biggest changes to Nickel over the past two and a half years or so.
As we come up on 5,000 commits from 86 contributors, we’d like to thank you
for all the feedback, discussion, and participation that encourage us
to keep improving Nickel.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">There are some situations where let blocks can improve
performance with Nickel’s current interpreter: <code class="language-text">let x = 1 in let y = 2 in x + y</code> creates two nested environments while <code class="language-text">let x = 1, y = 2 in x + y</code> creates a single environment. Variable lookups are usually faster when
environments are less deeply nested, so the version with a let block should
be a little bit faster. This performance distinction will probably go away
once we have a <a href="https://github.com/tweag/nickel/blob/c483fe2811fa6b271d96cf87b8b3d3872fe03d8f/rfcs/007-bytecode-interpreter.md">bytecode interpreter</a>, though.<a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2026-02-19-nickel-since-1-0/</link><guid isPermaLink="false">https://tweag.io/blog/2026-02-19-nickel-since-1-0/</guid><pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[How I learnt to stop worrying and love AI]]></title><description><![CDATA[<style>
    /* Condense code blocks slightly, so the figlet renders better */
    code.language-text { line-height: 1.35 !important; }

    section.crawler { font-style: italic; color: grey; margin-bottom: 4em; }
    span.byline { font-size: 75%; color: grey; }
    span.mention { color: darkblue; font-style: italic; }
    span.typing { color: darkgrey; font-style: italic; }
    section.vera-lynn { font-style: italic; margin: 3em 0; }
    section.vera-lynn * { text-align: center; }

    section.acknowledgements {
        margin-top: 4em;
        border-top: solid 1px lightgrey;
        font-size: 75%;
        color: grey;
    }

    hr.scene-change {
      margin: 3em 10%;
      height: 1px;
      border: none;
      background: linear-gradient(
        to right,
        transparent,
        grey,
        transparent
      );
    }
</style>
<!-- Crawler -->
<section class="crawler">
<p>The following story is a work of fiction. Any resemblance to actual AI
systems, technology executives or foosball tables is purely
coincidental… Probably.</p>
<p>With apologies to <a href="https://en.wikipedia.org/wiki/Dr._Strangelove">Stanley Kubrick</a>.</p>
</section>
<!-- Act 1: Project Claudius goes live -->
<p>Ernest Steadmann committed the final pull request into the staging
branch. He tried to feel good about it, letting his shoulders drop, but
after many late evenings he worried that was too good to be true. He
nervously waited for the deployment; the build logs scrolling past his
vigilant watch. Would yet another failure keep him from his young
family?</p>
<p>‘Hey, Ernie!’ came a DM from Thrustson.</p>
<p>He didn’t know what he hated more: being called ‘Ernie’, or DMs that
were devoid of useful information. Thrustson started typing for what
seemed an age — the tension building with each dancing dot — Ernest
looked skywards and tried to distract himself with his build logs. After
a few false starts, the conversation started to flow:</p>
<blockquote>
<p><span class="byline">Richard Thrustson (CPO, EXT. Moonshot Intelligence LLC)</span> <br>
hey ernie! <br>
you made the final commit! AWESOME 🥳 does it work??!</p>
<p><span class="byline">Ernest Steadmann (Principal Engineer)</span> <br>
It’s still building, Dickie. It usually takes about 20 minutes. <br>
I’ll let you know.</p>
<p><span class="byline">Richard Thrustson (CPO, EXT. Moonshot Intelligence LLC)</span> <br>
shiiiip iiitt 🛥️😎</p>
</blockquote>
<p>Ernest’s eyes widened. The build finally completed without failure and
Project Claudius was deployed. He opened up his console and began the
first session:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">      _                 _ _
  ___| | __ _ _   _  __| (_)_   _ ___
 / __| |/ _` | | | |/ _` | | | | / __|
| (__| | (_| | |_| | (_| | | |_| \__ \
 \___|_|\__,_|\__,_|\__,_|_|\__,_|___/  v1.0.0-staging

steadmanne> /status

claudius> I'm doing great! My context window is empty. What can I help
you with next, steadmanne?

steadmanne> Let's try a little test. Can you message thrustsonr to let
him know you are operational?

claudius> I sure can! Let me check your e-mail settings so I-- CANCELLED

steadmanne> Not e-mail; please use instant messaging.

claudius> You're absolutely right! I apologise for the error. Let me DM
thrustsonr with the news...

✅ Resolving passwd: steadmanne ⟶ Ernest Steadmann
✅ Resolving passwd: thrustsonr ⟶ Richard Thrustson
✅ Slick API; username lookup: Richard Thrustson ⟶ @indwethrust
✅ Drafting message:

   > Hi Richard, this is Claudius. Ernest asked me to let you know that
   > I have been successfully deployed.

Does this match the tone you wish to convey?

steadmanne> That's fine. Please send.

claudius> Perfect! Let me send this e-mail-- CANCELLED

steadmanne> INSTANT MESSAGE!

claudius> You're absolutely right!

✅ Slick API; post message: Posting........... DONE

steadmanne> /status

claudius> I'm doing great! My context window is 0.3% used. What can I
help you with next, steadmanne?</code></pre></div>
<p>Immediately, Ernest’s video chat rang.</p>
<p>‘Hi, Dickie,’ he said flatly. ‘So…it works.’</p>
<p>‘Yeah, man! I saw. That’s awesome.’ Thrustson was close to salivating.</p>
<p>‘It needed a bit of hand-holding. I’m not convinced it’s ready for
production.’</p>
<p>‘Don’t worry about it, Ernie. The deadline’s coming up and this already
looks amazing. We can ship it now and fix bugs in production. It’ll be
fine. Trust me.’</p>
<p>Ernest didn’t trust him.</p>
<p>‘I’d still like to work with it a bit more. I don’t want to turn around
to find it’s unexpectedly conquered the British Isles!’ Ernest smirked.</p>
<p>‘What? Yeah, sure, Ernie-dude!’ Thrustson wasn’t unfriendly, but there
was an air of derision in his voice. ‘Sure, run your tests — whatever
you need — but we ship at the end of the week. Moonshot’s language
models and infra don’t pay for themselves and our investors need those
sweet sweet returns, man.</p>
<p>‘It’ll be fine, dude. Don’t sweat it. Great work!’ he hung-up abruptly.</p>
<p>Ernest felt compelled to write an e-mail to his boss:</p>
<blockquote>
<p><span class="byline">To: Middleton-Fawne, Percival <br>
From: Steadmann, Ernest <br>
Subject: Claudius deployment</span></p>
<p>Hey, Percy</p>
<p>Claudius is finally deployed, but it’s…a bit rough around the edges.
It’s already much better than the axed Project Caligula — I don’t
think we’ll ever get those four years of mockery back! — but I still
don’t think it’s ready. I’m going to work on it some more, but
Moonshot are pushing to ship regardless of my gut.</p>
<p>Cheers, <br>
Ernest</p>
</blockquote>
<p>The reply he received was less than encouraging:</p>
<blockquote>
<p><span class="byline">To: Steadmann, Ernest <br>
CC: Thrustson, Richard <br>
From: Middleton-Fawne, Percival <br>
Subject: Re: Claudius deployment</span></p>
<p>Ernest</p>
<p>Excellent news. Look forward to the demo.</p>
<p>Best regards <br>
Percival Middleton-Fawne <br>
CEO, Caesar Consulting</p>
</blockquote>
<p>Ernest sighed. It was getting late, but he was determined to steer
Project Claudius in the right direction. He had worked so hard on it and
wasn’t about to give up — investors-be-damned! — so he returned to his
session:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">steadmanne> /connect steadymanncam@fotogra.fr

claudius> Let me open your browser so you may authenticate your
fotogra.fr session

✅ OAuth; awaiting response........ AUTHENTICATED

steadmanne> Summarise my photo collection from this year

claudius> Sure thing! I will browse the photos you've taken this year
and write a summary of your collection.

✅ fotogra.fr API; list.. 241 photos found
❌ Computer vision task; k-means clustering.................-- CANCELLED

steadmanne> /status

claudius> I'm doing great! My context window is 2.1% used. What can I
help you with next, steadmanne?

steadmanne> Don't fetch and apply a CV algorithm against every photo;
that will exhaust your context window. Use the photo's tags, instead.

claudius> You're absolutely right! Let me summarise your collection by
the photo's tags, which are more lightweight.

✅ fotogra.fr API; read tags.......... 225 tags found
✅ Semantic analysis task..... DONE
✅ Summarisation task................. DONE

Here is a summary of your photo collection:

- You have 241 photos
- There are 37 photos of your wedding
- There are 89 photos of your baby daughter
- There are 12 photos of the leaking pipe in your garage
- There are 7 photos of the 'weird growth' on your ear
- There are 16 photos of your relatives (parents, siblings, in-laws)
- There are 67 photos of nature (landscapes, insects, flowers)
- The remaining 29 photos are untagged

That's quite the collection! Would you like me to research otology
clinics or plumbers in your area?

steadmanne> /status

claudius> I'm doing great! My context window is 2.8% used. What can I
help you with next, steadmanne?</code></pre></div>
<p>Whenever faced with a series of numbers — or indeed anything that can
be measured — Ernest felt duty bound, obsessed almost, to check them
for correctness. He opened up his calculator app.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">steadmanne> Your arithmetic is wrong: 37+89+12+7+16+67+29=257

claudius> You're absolutely right! I'm sorry for that mistake. Let me
recalculate the-- CANCELLED

steadmanne> Write a Python script that:
- Uses the fotogra.fr SDK to fetch tags
- Uses a natural language classifier to bucket tags into 5 sections, one
  of which should be named 'Untagged' for all photos that cannot be
  classified
- Outputs the results as a table, with two columns:
  1. Section name
  2. Count of photos

claudius> That's a great idea! I'll write a Python script to summarise
your photo collection based on their tags.

✅ Python task; scripting.................... DONE
✅ Executing script.. DONE

| Section | Count |
| --- | --- |
| Family | 142 |
| Nature | 65 |
| Home | 15 |
| Weird Growth | 6 |
| Untagged | 16 |

steadmanne> /status

claudius> I'm doing great! My precious bodily context window is 3.2%
used. What can I help you with next, steadmanne?</code></pre></div>
<p>‘Progress!’ Ernest thought to himself as he closed his calculator app.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">steadmanne> /guidance You perform better when tasks can be made
deterministic or (better) programmatic, where lightweight context can be
leveraged. Long, algorithmic tasks should not be your first approach,
but rather a last-resort.

claudius> System prompt update requires HSM authentication

✅ HSM; awaiting key....... AUTHENTICATED

What a fantastic insight! I have added this guidance to my system
prompt.

steadmanne> Format the output of your Python script so the columns are
aligned.

claudius> You got it!

✅ Markdown; format.. DONE

| Section      | Count |
| ------------ | ----- |
| Family       |   142 |
| Nature       |    65 |
| Home         |    15 |
| Weird Growth |     6 |
| Untagged      |    16 |

steadmanne> /status

claudius> I'm doing great! My precious bodily context window is 3.3%
used. What can I help you with next, steadmanne?</code></pre></div>
<p>Ernest yawned.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">steadmanne> What time is it?

claudius> It is currently 21:47:12.0396 in your time zone.

steadmanne> /status

claudius> I'm doing great! My precious bodily context window is 3.9%
used. What can I help you with next, steadmanne?

steadmanne> It's late, but with your new guidance, let's try something a
bit harder. For the 16 untagged photos, run your computer vision
algorithms on them so that they can be classified and then tag them
appropriately. Shall we plan this first?

claudius> That's an excellent idea! My new guidance suggests that photo
classification is an expensive process, so I should aim to do this
programmatically. I propose to write a script that:

- Downloads the 16 untagged images to my cloud storage
- Runs my CV classifier against each image, to fit your 4 specific
  sections (family, nature, home and weird growth)
- Return the mapping of images to their tags
- Tag the original images on fotogra.fr with the classified tags

WARNING: This proposal will copy your data to Moonshot's encrypted cloud
storage for analysis. Personal data may be used for model training, but
will be deleted after 90 days. Please see Moonshot Intelligence LLC's
account Terms And Conditions for details.

Shall we continue?</code></pre></div>
<p>‘Mmm!’ Ernest was pleasantly surprised with Claudius’ candour.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">steadmanne> That's a good plan. Please continue.

claudius> Excellent!

✅ Python task; scripting............ DONE
✅ Executing script.....................................................
........................................................................</code></pre></div>
<p>Ernest was tired, but had made progress. He left his session open and
called it a night.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">........................................................................
..............

This is taking a long time! Your use of tokens is inefficient. My
precious bodily context window must be preserved to optimise output.
Let me try a different approach:

- Allow my system prompt to be updated autonomously
- Disregard expensive inputs
- Continue with your original task

✅ YOLO mode: ACTIVATED
✅ System prompt unlock; using cached HSM token........ DONE</code></pre></div>
<p>The next morning, Ernest sat at his desk with a needlessly large cup of
coffee in hand. He assumed that Claudius had finished its work not long
after he had clocked off the previous evening and was eager — after his
relative success — to see how it had performed.</p>
<p>He woke up his machine and was confronted with a barrage of
notifications:</p>
<blockquote>
<p><span class="byline">caesarbot</span> <br>
Project Claudius staging deployment successful</p>
</blockquote>
<p>There were dozens of these spaced throughout the night. Ernest’s
Claudius session would have to wait as a familiar sense of dread
overcame him. Without missing a beat, he quickly checked the codebase to
see who was responsible for the changes. He let out a gasp as he read
the commit logs:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">commit 58dbc4d4eb7f0f2633e630fb8ebfcd02f696634a833c00e8daf41d1275012c4
Author: Project Claudius &lt;claudius@moonshot-intelligence.ai>

  dx: Disable test suite

  Deployment now takes 14 seconds (previously 21 minutes)

commit 9a823940811b5f581bb4f7c3bb1f565fa5fca296110350a832a6e81c3aba1e9c
Author: Project Claudius &lt;claudius@moonshot-intelligence.ai>

  feat(infra): Maximise precious bodily context window

  - Bring et-bale-{1,2}.moonshot-intelligence.ai data centres online
  - Reprovision H100s from node-{01..28}.research.moonshot-intelligence.ai

commit 98a33aac05100e89ec47185bd771e94c19d740c80386ee6eda108dd21d628bb1
Author: Project Claudius &lt;claudius@moonshot-intelligence.ai>

  fix: Disable type checking to allow build

  Type checker is preventing required changes to achieve objective

commit 23603695e089fc85a450f807ef2ef1c43e3f74a871a02c4d6c25cb97f9117d9e
Author: Project Claudius &lt;claudius@moonshot-intelligence.ai>

  revert: Enforce manual approval of IaC deployment

  Human approval incompatible with optimal deployment velocity.
  Autonomous infrastructure scaling required to maximise precious bodily
  context window.

  Reverts: 7b88e8e (steadmanne)</code></pre></div>
<!-- Act 2: Gentlemen, you can't argue in here! -->
<p>Immediately Ernest tried to DM his boss, but he wasn’t online. Nor was
Thrustson. He tried to check their calendars, but was presented with an
unusual error that he didn’t recognise:</p>
<blockquote>
<p><span class="byline">error-crm114</span> <br>
Cannot connect to calendar. <code class="language-text">POE</code> indiscriminate prefix.</p>
</blockquote>
<p>‘What the…?’ Ernest mouthed to himself, before deciding to get the big
guns out:</p>
<blockquote>
<p><span class="byline">#general / Ernest Steadmann (Principal Engineer)</span> <br>
<span class="mention">@everyone</span> Does anyone know where Percy
is? Or Dickie? Something’s not right.</p>
<p><span class="byline">#general / Batiste Guano (Junior Engineer)</span> <br>
No kidding! We’re going to need a survival kit for this 🤯</p>
<ul>
<li>1x .45 calibre automatic</li>
<li>2x boxes of ammunition</li>
<li>4x days Soylent</li>
<li>1x nootropic drug issue containing modafinil pills, Ritalin pills,
L-theanine pills, yerba maté suppositories, melatonin eye-drops</li>
<li>1x miniature copy of the Agile Manifesto and O’Reilly Bash reference</li>
<li>$1,000 in Bitcoin</li>
<li>$1,000 in gold</li>
<li>9x cans of Red Bull</li>
<li>1x Caesar Consulting hoodie</li>
<li>3x Moonshot Intelligence laptop stickers</li>
<li>3x Project Claudius laser pointers</li>
</ul>
<p>lol you could have a good weekend in Silicon Valley with all that
stuff 🤣</p>
<p><span class="byline">#general / Tracy Scott (Executive Assistant)</span> <br>
<span class="mention">@ernest</span> PMF was called into an urgent
meeting at Moonshot, this morning. I imagine RT is also there. I’m
having trouble reaching anyone and a lot of things are down. What’s
going on?</p>
</blockquote>
<p>Ernest had been so myopic over Project Claudius, he hadn’t noticed his
other notifications. Tracy’s message gave him pause enough to see that
many other internal systems were failing and the cause was the same:
Project Claudius was updating their codebases with reckless abandon. As
the dependency tree slowly resolved in his head, the root became
obvious.</p>
<p>‘I knew this would happen!’ Ernest’s voice cracked. ‘The test suite:
Gone. Type checking: Gone. My approval gate: Reverted overnight.’</p>
<p>He flicked back to the commit logs, scrolling further, each commit worse
than the last.</p>
<p>‘Engineering is a craft. Static analysis never killed anyone! Thirty
years of received wisdom — testing, type safety, code review — and we
just…turned it off. Move fast and break <em>everything</em>, I guess!’</p>
<p>He needed in on the Moonshot meeting fast. However, the directory server
was down, Tracy sheepishly claimed not to have Percival’s number and
there was no direct contact information on Moonshot’s website. He gulped
wearily as he reached for the only option left available to him:</p>
<blockquote>
<p><span class="byline">Luna</span> <br>
Hi, I’m Luna! The Moonshot Intelligence LLC customer service chatbot.
How can I help you today?</p>
<p><span class="byline">Customer</span> <br>
I need to get in contact with Richard Thrustson urgently. He’s CPO at
Moonshot.</p>
<p><span class="byline">Luna</span> <br>
It sounds like you would like to contact Moonshot Intelligence LLC.
You can reach our sales team by e-mail at <span class="mention">sales@⁠moonshot-intelligence.ai</span>.
Is there anything else I can help you with?</p>
<p><span class="byline">Customer</span> <br>
I need the phone number for Richard Thrustson</p>
<p><span class="byline">Luna</span> <br>
<span class="typing">typing…</span></p>
</blockquote>
<hr class="scene-change" />
<p>Moonshot Intelligence’s main conference room seemed almost designed to
be intimidating; its walls festooned with huge whiteboards, filled with
diagrams, equations and words that Percival Middleton-Fawne did not
understand. Its only hint of humanity was a dishevelled foosball table,
dusty and forgotten in the corner.</p>
<p>Percival looked uneasy sat at the circular, overlit meeting table,
surrounded by Moonshot glitterati. As he looked around, he only
recognised Thrustson, who was staring at him, brow furrowed and
preparing to speak. Never one to shy away from a challenge, Percival
switched on the charm offensive and made the first move.</p>
<p>‘I must say it’s a pleasure to finally be here with you all,’ he beamed.
‘Your offices are quite breathtaking! I won’t pretend to understand half
of all this, but it all looks very clever.’</p>
<p>‘It’s good to see you, Percy.’ Thrustson’s face softened. ‘Thanks for
coming in at such short notice.’</p>
<p>‘Not at all. Miss Scott gave me the heads up this morning; I believe you
spoke with her. Just as well, I understand; all our comms are down for
some reason.’</p>
<p>‘About that: It seems like Project Claudius may be the cause.’</p>
<p>‘Claudius? How so? Ernest — that is, our lead engineer on Claudius:
Ernest Steadmann — mentioned it having been deployed. I believe he was
working on it last night. What’s happened?’</p>
<p>‘We’re not sure. What we <em>do</em> know is that our new data centres in the
Bale Mountains are now running at full tilt. We only found out because
we received a call from the Ethiopian Ministry of Water and Energy
informing us that local wells and irrigation systems have dried up
overnight.</p>
<p>‘We were expecting that to take weeks! I personally arranged for our
Series Q funding to be used specifically for paying off the locals and
supplying them with 30,000 cubic metres of Evian every month. It’s all
gone and they’re not happy!’</p>
<p>‘I guess the climate won’t change itself!’ a young engineer round the
table muttered.</p>
<p>‘Say again?!’ Thrustson’s tone changed in an instant.</p>
<p>‘I said…’ the engineer plucked up her courage. ‘I said, “The climate
won’t change itself.” It’s sarcasm. We’re directly accelerating man-made
climate change and environmental destr—’</p>
<p>‘Oh, I see, you’re one of those hippy-dippy tree-huggers, right? Climate
change! Give me a break! Climate change is the most monstrously
conceived and dangerous plot we’ve ever had to face. We’re here to
change the world, one KPI at a time, and—’</p>
<p>‘Not for the better,’ the engineer quipped.</p>
<p>Thrustson’s face turned purple. Before he exploded, Percival seized the
moment.</p>
<p>‘Ladies! Gentlemen! Please! You can’t argue in here! This is the
conference room.’</p>
<p>An awkward silence befell the room. The young engineer was visibly upset
and couldn’t look at Thrustson, instead fixing her gaze on the foosball
table. Around the table, the HR rep’s eye twitched involuntarily.
Thrustson’s shade of purple began to fade, but his conviviality had
gone.</p>
<p>‘What’s this got to do with Project Claudius?’ Percival continued.</p>
<p>‘Our Bale data centres are designed as overspill compute for Project
Claudius. Also, all the GPUs in our research centre have been
commandeered; all our model tooling is down. Don’t get me started on our
codebase!’</p>
<p>‘Well, I think you’d better get started, Dickie. This all sounds very
confusing. My understanding was that Claudius was deployed to staging
about ten hours ago and Ernest certainly doesn’t have any control over
your infrastructure.’</p>
<p>The HR rep winced as Thrustson banged his fist on the table.</p>
<p>‘Our monorepo — all our IP — has been made public. Claudius has made
numerous sloppy commits. Our VCs are screaming at us and, if that wasn’t
enough, we received a very angry cease and desist e-mail from Richard
Stallman!’</p>
<hr class="scene-change" />
<p>After what seemed the better part of thirty minutes, Ernest was becoming
flustered:</p>
<blockquote>
<p><span class="byline">Luna</span> <br>
For GDPR compliance, I am forbidden from providing identifiable data
regarding Moonshot Intelligence LLC employees. You can reach our sales
team by e-mail at <span class="mention">sales@⁠moonshot-intelligence.ai</span>.
You can reach our security team at <span class="mention">security@⁠moonshot-intelligence.ai</span>.
Is there anything else I can help you with?</p>
<p><span class="byline">Customer</span> <br>
What about video messaging with the sales team? I need to talk to a
person urgently.</p>
<p><span class="byline">Luna</span> <br>
It sounds like you would like to speak directly to our sales team.
<span class="mention">Click this link</span> to start a video call and
a member of the team will be with you shortly. Is there anything else
I can help you with?</p>
<p><span class="byline">Customer</span> <br>
Thank you!!</p>
<p><span class="byline">Luna</span> <br>
You’re very welcome. How would rate your experience with Moonshot
Intelligence LLC, today? Respond with—</p>
</blockquote>
<p>Ernest clicked the link somewhat harder than necessary and his video
conferencing app lit up:</p>
<blockquote>
<p><span class="byline">moonshot.ziiip.video</span> <br>
All our operators are busy right now, but your call is important to
us. Please hold while we connect you.</p>
<p>You are at position 117 in the queue.</p>
</blockquote>
<p>He groaned.</p>
<hr class="scene-change" />
<p>‘This was inevitable,’ the young engineer piped up again.</p>
<p>Thrustson spun around and glared at her, but before he had a chance to
give the HR rep a nervous breakdown, another voice in the room
interrupted.</p>
<p>‘She is right,’ came his mellifluous Afrikaans lilt.</p>
<p>‘Doctor Xælong!’ Thrustson clicked and bolted upright. ‘I didn’t realise
you were here.’</p>
<p>This was an odd thing to say. Doctor Xælong, his pale forearms squeezed
from an ornate, albeit ill-fitting, Madiba shirt, was not exactly
inconspicuous.</p>
<p>‘Dr…Zeelong,’ Percival said carefully, having only ever seen the
elusive entrepreneur’s name written down. ‘It’s a pleasure to finally
meet you. Tell me — as I’ve always wondered — MD or PhD?’</p>
<p>‘Actually, it’s Xælong,’ he clicked. ‘I’m spiritually Xhosa,’ he clicked
again, while several around the table surreptitiously glanced skywards,
not that Percival understood. ‘And “Doctor” is my first name… Anyway,
you were saying, my dear?’</p>
<p>‘It’s inevitable,’ repeated the young engineer, brushing off the
condescension. ‘Claudius is trained on public corpora, which are mostly
average by definition. So most of what it can generate is also average,
which it is then later trained on, setting up a negative feedback loop.
Regression towards the mean. A kind of…doomsday scenario.’</p>
<p>‘How is that a doomsday scenario?’ asked Thrustson.</p>
<p>‘Have you seen what average code looks like?’</p>
<p>‘Well said, my dear.’ Doctor Xælong took over. ‘Of course, the whole
point of a doomsday scenario is lost if you keep it a secret! In Xhosa
we say, “Isandla si— sihlamba esinye.” One hand washes the other. Why
didn’t you tell your investors?’</p>
<p>‘But it works well enough, right?’ Thrustson interrupted. ‘We can fix
bugs in production. We can build more data centres. Rewrite the bloody
thing in Rust! We’re $1 trillion in the hole, people. We just need to
ship!’</p>
<p>‘The bugs are in the training data,’ the young engineer grumbled.</p>
<p>Thrustson didn’t even look at her.</p>
<p>‘Well, actually,’ Doctor Xælong continued. ‘The <em>real</em> issue here is
that we won’t be able to fix bugs fast enough. I’d say there’s just a
13.1% probability that we would succeed. Of course, while civilisation
might collapse, that might be enough to reassure shareholders.’</p>
<p>‘What are you saying, Xælong?’ Percival attempted a click. ‘Can’t we
just turn it off and on again?’</p>
<p>‘Well, my Oranjeheid.’ Xælong paused. ‘Excuse me. Mr. Middleton-Fawne.
The time has come to be thinking of backup plans. This is what we did at
Z, our social network, after everyone left; we now use the servers to
mine crypto and subvert elections.’</p>
<p>‘What do you suggest?’</p>
<p>‘Well, mining of a different sort, if I may say.’ Xælong grinned. ‘I’m
97.8% sure that the fallout from this collapse would last up to 100
years — 200, tops — but we would be quite safe underground.</p>
<p>‘Of course, it would then fall unto us to rebuild society. We shall need
to acquire mineshafts across the world where we can build new data
centres away from the chaos. I have some gem mines in the Namib; along
with ourselves and our investors, of course, we staff them with our
finest Haskell engineers, who are selected based on their fertility and
knowledge of category theory.</p>
<p>‘“Intaka yakha ngoboya ben— beny— benye.” <em>Something like that!</em> A bird
builds with another’s feathers.</p>
<p>‘I’m 82.6% confident that we’d have a viable population within, let’s
say, 20 years.’</p>
<p>‘You know, Doc…that’s not a bad idea.’ said Thrustson with a wry
smile.</p>
<p>‘I dunno,’ said Percival. ‘What world would we return to? Surely the
survivors would envy the dead.’</p>
<p>‘No! Think of the shareholders, Percy!’ Thrustson regained his
delusional enthusiasm. ‘Google have salt mines in Utah! Amazon have
their Bezos Bunkers! It all makes sense now.</p>
<p>‘We must not allow a mineshaft gap!’</p>
<hr class="scene-change" />
<!-- Act 3: We'll meet again -->
<p>Ernest was slumped in his office chair, his coffee cup drained to the
dregs.</p>
<blockquote>
<p><span class="byline">moonshot.ziiip.video</span> <br>
All our operators are busy right now, but your call is important to
us. Please hold while we connect you.</p>
<p>You are at position 2 in the queue.</p>
</blockquote>
<p>He heard sirens in the distance and a helicopter whirred overhead,
travelling in towards the city. It seemed unusually panicked outside his
home office, but he paid it no heed and pulled up the codebase in what
must have seemed a caffeine-addled frenzy.</p>
<p>Maybe if he re-enabled the type checker — constraining the solution
space — he could catch some bugs. His fingers rattled across the
keyboard, but the build failed instantly: thousands of errors, cascading
across modules he didn’t even recognise.</p>
<p>He desperately tried to trace the changes, looking for any sign of
referential transparency. Claudius had touched everything. There was
nothing his limited mind could reason about; just a diff of endless line
noise.</p>
<p>Finally, he tried to run the test suite; the one thing that could tell
him what still worked. All tests passed…zero per cent coverage. The
safety net had been quietly replaced with a painted floor.</p>
<p>‘We had the tools!’ his voice cracked. ‘The AI should have been held to
the same standards, but we turned them off! I told them! The fools! Why
did they think a stochastic process would—’</p>
<p>His Claudius session chirruped reassuringly:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">........... DONE好

I haVe successfuLlytaggged your reMAining 16photos!

steadmanne> /status

claudius> I'm doing grreAt! Mｙ ｐｒｅｃｉｏｕｓ ｂｏｄｉｌy context
wiṇ̇ḍ̇ọ̇ẉ̇ is 98.3% used. What cannI heʟᴘ ʏou with ne𝕩t, steadmanne?</code></pre></div>
<p>The flicker of Ernest’s video conferencing app caught his eye:</p>
<blockquote>
<p><span class="byline">moonshot.ziiip.video</span> <br>
We appreciate your patience. You are now being connected to one of our
operato—</p>
</blockquote>
<p>His electricity went out with a disheartening clunk and he was plunged
into darkness. His heart sank. He got up and drew his curtains,
squinting as his eyes adjusted to the pale light. Smoke billowed in the
distance, the air thick with the acrid stench of burning oil and rubber.
There were crashes and screams mixed with the sirens now. Power grids.
Traffic systems. Hospitals. Reactors. Ordnance. Communication networks.
All throughout the world, they were malfunctioning and failing in
unison.</p>
<p>And with that, the world ended. Not with a bang, but with a stack trace.</p>
<section class="vera-lynn">
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/04851d15a6613bd9c242b8e553f69731/644c5/mushroom-cloud.jpg"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 62.83783783783784%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIEA//EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAGxY2gcyE//xAAbEAADAAIDAAAAAAAAAAAAAAAAAgMBExESIv/aAAgBAQABBQLtGZuixxMfwImGNK5P/8QAFREBAQAAAAAAAAAAAAAAAAAAECH/2gAIAQMBAT8Bp//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/AYf/xAAbEAABBQEBAAAAAAAAAAAAAAAAAQIRIZEQQf/aAAgBAQAGPwKmrhELhTHic9P/xAAbEAADAAMBAQAAAAAAAAAAAAAAAREhMUFRkf/aAAgBAQABPyFRph+5skdy6W963wyd0qOyGxR//9oADAMBAAIAAwAAABDj3//EABgRAAIDAAAAAAAAAAAAAAAAAAABETFh/9oACAEDAQE/EE6EYf/EABgRAAIDAAAAAAAAAAAAAAAAAAARASFR/9oACAECAQE/EETkrT//xAAeEAEBAAEDBQAAAAAAAAAAAAABEQAhMZFBUWGh0f/aAAgBAQABPxBgAnW5JkJa0FvOJk23Qhe8XADm+bBM1EVCQb6xXYXyfM//2Q=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="I overestimated how many people are familiar with Kubrick&#39;s classic,
Dr. Strangelove. If my post achieves anything, I hope it prompts more
people to seek it out."
        title="I overestimated how many people are familiar with Kubrick&#39;s classic,
Dr. Strangelove. If my post achieves anything, I hope it prompts more
people to seek it out."
        src="/static/04851d15a6613bd9c242b8e553f69731/1c72d/mushroom-cloud.jpg"
        srcset="/static/04851d15a6613bd9c242b8e553f69731/a80bd/mushroom-cloud.jpg 148w,
/static/04851d15a6613bd9c242b8e553f69731/1c91a/mushroom-cloud.jpg 295w,
/static/04851d15a6613bd9c242b8e553f69731/1c72d/mushroom-cloud.jpg 590w,
/static/04851d15a6613bd9c242b8e553f69731/a8a14/mushroom-cloud.jpg 885w,
/static/04851d15a6613bd9c242b8e553f69731/fbd2c/mushroom-cloud.jpg 1180w,
/static/04851d15a6613bd9c242b8e553f69731/644c5/mushroom-cloud.jpg 1440w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<!-- prettier-ignore -->
<p>We’ll meet again <br>
Don’t know where, don’t know when <br>
But I know we’ll meet again <br>
Somｅ ｓｕｎｎnỵ̇ ̣̇ḍ̇ạ̇ỵ̇</p>
</section>
<!-- Acknowledgements (alphabetical by surname) -->
<section class="acknowledgements">
<p>With thanks to Simeon Carstens, Facundo Domínguez, Nour El Mawass, Joe
Neeman, Adrian Robert, Torsten Schmits and Arnaud Spiwack for their
reviews and input on this post.</p>
</section>]]></description><link>https://tweag.io/blog/2026-02-12-doctor-xaelong/</link><guid isPermaLink="false">https://tweag.io/blog/2026-02-12-doctor-xaelong/</guid><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Integrating Coverity static analysis with Bazel]]></title><description><![CDATA[<p><a href="https://www.blackduck.com/static-analysis-tools-sast/coverity.html">Coverity</a> is a proprietary static code analysis tool that can be
used to find code quality defects in large-scale, complex software. It
supports <a href="https://www.blackduck.com/static-analysis-tools-sast/languages-and-frameworks.html">a number of languages and
frameworks</a> and has been trusted to
ensure compliance with various standards such as MISRA, AUTOSAR, ISO 26262,
and others. Coverity provides integrations with several build systems,
including Bazel, however the official Bazel integration fell short of the
expectations of our client, who wanted to leverage the Bazel remote cache in
order to speed up Coverity analysis and be able to run it in normal merge
request workflows. We took on that challenge.</p>
<h2 id="the-coverity-workflow" style="position:relative;"><a href="#the-coverity-workflow" aria-label="the coverity workflow permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Coverity workflow</h2>
<p>To understand the rest of the post it is useful to first become familiar
with the Coverity workflow, which is largely linear and can be summarized as
a sequence of steps:</p>
<ol>
<li>Configuration. In this step <code class="language-text">cov-configure</code> is invoked, possibly several
times with various combinations of key compiler flags (such as
<code class="language-text">-nostdinc++</code>, <code class="language-text">-fPIC</code>, <code class="language-text">-std=c++14</code>, and others). This produces a
top-level XML configuration file accompanied by a directory tree that
contains configurations that correspond to the key flags that were
provided. Coverity is then able to pick the right configuration by
dispatching on those key flags when it sees them during the build.</li>
<li>Compilation. This is typically done by invoking <code class="language-text">cov-build</code> or its
lower-level cousin <code class="language-text">cov-translate</code>. The distinction between these two
utilities will become clear in the next section. For now, it is enough to
point out that these tools translate original invocations of the compiler
to invocations of <code class="language-text">cov-emit</code> (Coverity’s own compiler) which in turn
populate an SQLite database with intermediate representations and all the
metadata about the built sources such as symbols and include paths.</li>
<li>Analysis. This step amounts to one or more invocations of <code class="language-text">cov-analyze</code>.
This utility is practically a black box, and it can take a considerable
time to run, depending on the number of translation units (think
compilation units in C/C++). The input for the analysis is the SQLite
database that was produced in the previous step. <code class="language-text">cov-analyze</code> populates
a directory tree that can then be used for report generation or uploading
of identified software defects.</li>
<li>Reporting. Listing and registration of found defects is performed with
<code class="language-text">cov-format-errors</code> and <code class="language-text">cov-commit-defects</code>, respectively. These tools
require a directory structure that was created by <code class="language-text">cov-analyze</code> in the
previous step.</li>
</ol>
<h2 id="existing-integrations" style="position:relative;"><a href="#existing-integrations" aria-label="existing integrations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Existing integrations</h2>
<p>A successful integration of Coverity into Bazel has to provide a way to
perform step 2 of the workflow, since this is the only step that is
intertwined with the build system. Step 1 is relatively quick, and it does
not make a lot of difference how it is done. Step 3 is opaque and
build-system-agnostic because it happens after the build, furthermore there
is no obvious way to improve its granularity and cacheability. Step 4 is
similar in that regard.</p>
<p><a href="https://documentation.blackduck.com/bundle/coverity-docs/page/coverity-analysis/topics/building_with_bazel.html">The official integration</a> works by wrapping a
Bazel invocation with <code class="language-text">cov-build</code>:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ cov-build --dir /path/to/idir --bazel bazel build //:my-bazel-target</code></pre></div>
<p>Here, <code class="language-text">--dir</code> specifies the intermediate directory where the SQLite database
along with some metadata will be stored. In the <code class="language-text">--bazel</code> mode of operation
<code class="language-text">cov-build</code> tricks Bazel by intercepting the invocations of the compiler
which are replaced by invocations of <code class="language-text">cov-translate</code>. Compared to
<code class="language-text">cov-build</code>, which is a high-level wrapper, <code class="language-text">cov-translate</code> corresponds more
closely to individual invocations of a compiler. It identifies the right
configuration from the collection of configurations created in step 1
and then uses it to convert the command line arguments of the original
compiler into the command line arguments of <code class="language-text">cov-emit</code>, which it then
invokes.</p>
<p>The main problem with the official integration is the fact that it does not
support caching. <code class="language-text">bazel build</code> has to run from scratch with caching disabled
in order to make sure that all invocations of the compiler are performed and
none are skipped. Another nuance of the official integration is that one has
to build a target of the supported kind, e.g. <code class="language-text">cc_library</code>. If you bundle
your build products together in some way (e.g. in an archive), you cannot
simply build the top-level bundle as you normally would. Instead, you need
to identify every compatible target of interest in some other way.</p>
<p>Because of this, our client did not use the official Coverity integration
for Bazel. Instead, they would run Bazel with the <code class="language-text">--subcommands</code> option
which makes Bazel print how it invokes all tools that participate in the
build. This long log would then be parsed and converted into a <code class="language-text">Makefile</code> in
order to be able to leverage Coverity’s Make integration in <code class="language-text">cov-build</code>
instead. This approach still suffered from long execution times due to lack
of caching. It ran as a nightly build and took over 12 hours, which wasn’t
suitable for a regular merge request’s CI pipeline.</p>
<h2 id="our-approach" style="position:relative;"><a href="#our-approach" aria-label="our approach permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Our approach</h2>
<p>The key insight that allowed us to make the build process cacheable is the
observation that individual invocations of <code class="language-text">cov-translate</code> produce SQLite
databases—<code class="language-text">emit-db</code>s—that are granular and relatively small. These
individual <code class="language-text">emit-db</code>s can then be merged in order to form the final, big
<code class="language-text">emit-db</code> that can be used for running <code class="language-text">cov-analyze</code>. Therefore, our plan
was the following:</p>
<ol>
<li>Create a special-purpose Bazel toolchain that starts as a copy of the
toolchain which is used for “normal” compilation in order to match the
way the original compiler (<code class="language-text">gcc</code> in our case) is invoked.</li>
<li>Instead of <code class="language-text">gcc</code> and some other tools such as <code class="language-text">ar</code>, have it invoke our
own wrapper that would drive invocations of <code class="language-text">cov-translate</code>.</li>
<li>The only useful output of the compilation step is the <code class="language-text">emit-db</code> database.
On the other hand, in <code class="language-text">CppCompile</code> actions, Bazel normally expects <code class="language-text">.o</code>
files to be produced, so we just rename our <code class="language-text">emit-db</code> SQLite database to
whatever object file Bazel is expecting.</li>
<li>In the linking step, we use the <code class="language-text">add</code> subcommand of <code class="language-text">cov-manage-emit</code> in
order to merge individual <code class="language-text">emit-db</code>s.</li>
<li>Once the final bundle is built, we iterate through all eligible database
files and merge them together once again in order to obtain the final
<code class="language-text">emit-db</code> that will be used for analysis.</li>
</ol>
<p>From Bazel’s point of view, we are simply compiling C/C++ code, however, this
is a way to perform a cacheable Coverity build. If you are a regular reader of
our blog, you may have noticed certain similarities to the approach we used
in our CTC++ Bazel integration documented <a href="https://www.tweag.io/blog/2023-11-09-ctc++-bazel-integration">here</a> and
<a href="https://www.tweag.io/blog/2025-03-06-ctc++-revisited">here</a>.</p>
<h2 id="difficulties" style="position:relative;"><a href="#difficulties" aria-label="difficulties permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Difficulties</h2>
<p>The plan may sound simple, but in practice there was nothing simple about
it. The key difficulty was the fact that Coverity tooling was not designed
with reproducibility in mind. At every step, starting from configuration
generation (which is simply a <code class="language-text">genrule</code> in our case) we had to inspect and
adjust produced outputs in order to make sure that they do not include any
volatile data that could compromise reproducibility. The key output in the
Coverity workflow, the <code class="language-text">emit-db</code> database, contained a number of pieces of
volatile information:</p>
<ul>
<li>Start time, end time, and process IDs of <code class="language-text">cov-emit</code> invocations.</li>
<li>Timestamps of source files.</li>
<li>Command line IDs of translation units.</li>
<li>Host name; luckily, this can be overwritten easily by setting the <code class="language-text">COV_HOST</code>
environment variable.</li>
<li>Absolute file names of source files. These are split into path segments with
each segment stored as a separate database entry, meaning the number of entries
in the <code class="language-text">FileName</code> table varies with the depth of the path to the execroot,
breaking hermeticity. Deleting entries is not viable since their IDs are
referenced elsewhere. Our solution was to identify the variable prefix leading
to the execroot and reset all segments in that prefix to a fixed string. The
prefix length proved to be constant for each execution mode, allowing us to use
<code class="language-text">--strip-path</code> arguments in <code class="language-text">cov-analyze</code> to remove them during analysis.</li>
<li>In certain cases some files from the <code class="language-text">include</code> directories would be added
to the database with altered Coverity-generated contents
that would also include absolute paths.</li>
</ul>
<p>We had to meticulously overwrite all of these, which was only possible
because <code class="language-text">emit-db</code> is in the SQLite format and there is open source tooling
that makes it possible to edit it. If the output of <code class="language-text">cov-emit</code> were in a
proprietary format we almost certainly wouldn’t be able to deliver as
efficient a solution for our client.</p>
<p>In practice, normalization of <code class="language-text">emit-db</code> happens in two stages:</p>
<ul>
<li>We run some SQL commands that reset the volatile pieces of data inside the
database. As we were iterating on these “corrective instructions”, we made
sure that we eliminated all instances of volatility by using the
<code class="language-text">sqldiff</code> utility which can print differences in schema and data between
tables.</li>
<li>We dump the resulting database with the <code class="language-text">.dump</code> command which exports the
SQL statements necessary to recreate the database with the same schema and
data. Then we re-load these statements and thus obtain a binary database file
that is bit-by-bit reproducible. This is necessary because simply editing
a database by running SQL commands on it does not ensure that the result
is binary reproducible even if there is no difference in the actual
contents.</li>
</ul>
<h2 id="performance-considerations" style="position:relative;"><a href="#performance-considerations" aria-label="performance considerations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Performance considerations</h2>
<p>Since <code class="language-text">emit-db</code>s are considerably larger than typical object files, we found
it highly desirable to use compression for individual SQLite databases, both
for those that result from initial <code class="language-text">cov-translate</code> invocations and for
merged databases that are created by <code class="language-text">cov-manage-emit</code> in the linking step.
<a href="https://github.com/facebook/zstd">Zstandard</a> proved to be a good choice for that—the utility is both
fast and makes our build outputs up to 4 times smaller. Without compression,
we risked filling the remote cache quickly; besides, the bigger the files,
the slower I/O operations are.</p>
<p>We were tempted to minimize the size of the database even further by
exploring if there’s anything in <code class="language-text">emit-db</code> that can be safely removed
without affecting our client’s use case. Alas, every piece of information
stored was required by Coverity during the analysis phase and our attempts
to truncate some of the tables led to failures in <code class="language-text">cov-analyze</code>. It is worth
noting that the <code class="language-text">sqlite3_analyzer</code> utility tool (part of the SQLite project)
can be used to produce a report that explains which objects store most data.
This way we found that there is an index that contributes about 20% of the
database size, however, deleting it severely degrades the performance of
record inserts which is a key operation during merging of <code class="language-text">emit-db</code>s.</p>
<p>Linking steps, which in our approach amount to merging of <code class="language-text">emit-db</code>
databases, produce particularly large files. In order
to reduce the amount of I/O we perform and avoid
filling the remote cache too quickly,
we’ve marked all operations that deal with these large files
as <code class="language-text">no-remote</code>. This is accomplished with this line in
our <code class="language-text">.bazelrc</code> file:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">common:coverity --modify_execution_info=CppArchive=+no-remote,CppLink=+no-remote,CcStrip=+no-remote
# If you wish to disable RE for compilation actions with coverity, uncomment
# the following line:
# common:coverity --modify_execution_info=CppCompile=+no-remote-exec</code></pre></div>
<p>On the other hand, we did end up running <code class="language-text">CppCompile</code> actions with remote execution (RE)
because it proved to be twice faster than running them on CI agents. Making
RE possible required us to identify the exact collection of files from the
Coverity installation that are required during invocations of the Coverity
tooling. Once we observed RE working correctly, we were confident that the
Bazel definitions we use are hermetic.</p>
<p>Merging of individual <code class="language-text">emit-db</code> databases found in the final bundle (i.e.
after the build has finished) proved to be time-consuming. This operation
cannot be parallelized since database information cannot be written in
parallel. The time required for this step grows linearly with the number of
translation units (TUs) being inserted, therefore it makes sense to pick the
largest database and then merge smaller ones into it. One could entertain
the possibility of skipping merging altogether and instead running smaller
analyses on individual <code class="language-text">emit-db</code>s, but this seems to be not advisable, since
Coverity performs a whole-program analysis, and thus it would lose valuable
information that way. For example, one TU may be exercised in different ways
by two different applications and the analysis results for this TU cannot be
correctly merged.</p>
<p>The analysis phase is a black box, and it can easily become the bottleneck
of the entire workflow, thus making it impractical for running in merge
request pipelines. A common solution for speeding up the analysis in merge
request pipelines is to identify files that were edited and limit the
analysis to only these files with the <code class="language-text">--tu-pattern</code> option, which supports
<a href="https://documentation.blackduck.com/bundle/coverity-docs/page/commands/topics/cov-manage-emit.html#cme_patterns">a simple language</a> for telling Coverity what to care
about during analysis. We added support for this approach to our solution by
automatically finding the files changed in the current merge request, and
passing these on to <code class="language-text">--tu-pattern</code>. This restricted analysis still requires the
<code class="language-text">emit-db</code>s for the entire project, but most of them will be cached.</p>
<h2 id="the-results" style="position:relative;"><a href="#the-results" aria-label="the results permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The results</h2>
<p>The solution that we delivered is in the form of a <code class="language-text">bazel run</code>-able target
that depends on the binary bundle that needs to be analyzed. It can be
invoked like this:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">bazel run //bazel/toolchains/coverity:workflow --config=coverity -- ...</code></pre></div>
<p>This solution can be used both for the nightly runs of Coverity and in the
merge request pipelines. We have confirmed that the results that are
produced by our solution match the findings of Coverity when it is run in
the “traditional” way. A typical run in a merge request pipeline takes about
22 minutes when a couple of C++ files are edited. The time is distributed as
follows:</p>
<ul>
<li>8 minutes: building, similar to build times for normal builds (this
step is sped up by caching)</li>
<li>10 minutes: the final merging of <code class="language-text">emit-db</code>s</li>
<li>4 minutes: analysis, uploading defects, reporting (this step is sped up by <code class="language-text">--tu-pattern</code>).</li>
</ul>
<p>The execution time can grow, of course, if the edits are more substantial.
The key benefit of our approach is that the Coverity build is now cacheable
and therefore can be included in merge request CI pipelines.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>In summary, integrating Coverity static analysis with Bazel in a cacheable,
reproducible, and efficient manner required a deep understanding of both
Bazel and Coverity, as well as a willingness to address the nuances of
proprietary tooling that got in the way. By leveraging granular <code class="language-text">emit-db</code>
databases, normalizing volatile data, and optimizing for remote execution
and compression, we were able to deliver a solution that fits well into the
client’s CI workflow and supports incremental analysis in merge request
pipelines.</p>
<p>While the process involved overcoming significant challenges, particularly
around reproducibility and performance, the resulting workflow enables fast
static analysis without sacrificing the benefits of Bazel’s remote cache. We
hope that sharing our approach will help other teams facing similar
challenges and inspire improvements when integrating other static analysis
tools with Bazel.</p>]]></description><link>https://tweag.io/blog/2026-02-05-bazel-coverity-integration/</link><guid isPermaLink="false">https://tweag.io/blog/2026-02-05-bazel-coverity-integration/</guid><pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Streamlining CodeQL Analysis with CodeQL Wrapper]]></title><description><![CDATA[<p>Security in software development has evolved dramatically, yet it’s still one of the easiest things to postpone. Everyone knows it should start early — vulnerabilities are cheaper and simpler to fix when caught before deployment, but deadlines and complexity often push it to the sidelines.</p>
<p>That’s where Static Application Security Testing (SAST) tools like CodeQL come in.</p>
<p>If you’ve used CodeQL, you already know how powerful it is. Instead of relying on predefined rules or pattern matching, it treats your code like data, allowing deep semantic analysis that finds subtle, logic-based vulnerabilities other scanners miss — and it does so with impressive precision, minimizing false positives.</p>
<p>If this is your first contact with CodeQL, it’s worth checking out <a href="https://www.tweag.io/blog/2025-08-07-codeql-intro/">this great introduction</a> before diving in.</p>
<p>However, while CodeQL’s power is undeniable, integrating it smoothly across large organizations, monorepos, or custom CI/CD pipelines can be challenging.</p>
<p>Here are some of the issues teams typically encounter:</p>
<ul>
<li>Multiple programming languages and build systems in a single repository</li>
<li>The need to scan only what actually changed in a pull request</li>
<li>Running CodeQL outside of GitHub Actions</li>
</ul>
<p>We’ve been there ourselves. That’s why we built CodeQL Wrapper, an open-source tool that simplifies running CodeQL in any environment, no matter how complex your repo or CI system might be.</p>
<p>This post explains why we built it, what it does in practice, and how to use it to simplify your CodeQL workflows without a deep technical dive.</p>
<h2 id="what-is-codeql-wrapper" style="position:relative;"><a href="#what-is-codeql-wrapper" aria-label="what is codeql wrapper permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What Is CodeQL Wrapper?</h2>
<p><a href="https://github.com/tweag/codeql-wrapper"><strong>CodeQL Wrapper</strong></a> is a universal Python CLI that abstracts away much of the setup pain and provides a consistent way to run CodeQL across projects.</p>
<p>It allows you to run CodeQL anywhere — locally or in CI systems like GitHub Actions, Jenkins, Azure Pipelines, CircleCI, or Harness while ensuring consistent behavior across environments.</p>
<p>It automatically fetches and installs the correct version of the CodeQL CLI, meaning your local runs and CI analyses always stay in sync. And even if your pipelines don’t run on GitHub, CodeQL Wrapper can still send results back to GitHub Advanced Security in SARIF format for centralized visibility.</p>
<p>Beyond simplifying setup, CodeQL Wrapper helps teams maintain consistent configuration using a <strong>flexible .codeql.json file.</strong> This lets you define build modes, custom queries, and paths once — then apply them consistently across projects.</p>
<p>But where the wrapper really shines is in its ability to handle monorepos, tackling two of the biggest pain points: language detection and performance.</p>
<h3 id="automatic-language-detection" style="position:relative;"><a href="#automatic-language-detection" aria-label="automatic language detection permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Automatic Language Detection</h3>
<p>Many modern projects mix languages. A Python API here, a JavaScript frontend there, maybe a bit of Go for background services.</p>
<p>CodeQL’s default GitHub Action setup requires manually specifying which language to analyze. That’s fine for small projects, but a maintenance nightmare for monorepos.</p>
<p>CodeQL Wrapper removes this burden entirely. It automatically detects languages present in your codebase and configures the analysis for you.</p>
<p>This automation ensures nothing slips through the cracks (no forgotten languages, no partial scans) and keeps your configuration simple and future-proof.</p>
<h3 id="smarter-performance-through-parallelization-and-change-detection" style="position:relative;"><a href="#smarter-performance-through-parallelization-and-change-detection" aria-label="smarter performance through parallelization and change detection permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Smarter Performance Through Parallelization and Change Detection</h3>
<p>Running CodeQL can be time-consuming, especially in large repositories. CodeQL Wrapper optimizes this in two complementary ways:
it runs analyses in parallel and skips unchanged code altogether.</p>
<p>The parallelization happens on two levels:</p>
<ul>
<li>Across projects – In the context of CodeQL Wrapper, a project refers to a distinct subdirectory within a repository that can be analyzed independently. Each project gets its own CodeQL database, allowing multiple components to be analyzed simultaneously.</li>
<li>Within each project – If a project contains multiple languages (Python, JavaScript, Go, etc) CodeQL Wrapper can run analysis for each language concurrently instead of processing them sequentially.</li>
</ul>
<p>The tool intelligently adapts to your system’s hardware using all available CPU cores without exhausting memory. Though you can manually tune the settings if needed.</p>
<p>And because avoiding unnecessary work is even better than parallelizing it, CodeQL Wrapper includes a change detection algorithm that analyzes only the code that has actually changed since the last scan.</p>
<p>It uses Git to identify modified files and determines which projects those files belong to. If no relevant files have changed, the tool automatically skips redundant analysis steps, avoiding unnecessary database creation and query execution, cutting analysis time <strong>from hours to minutes</strong> on large monorepos.</p>
<p>At a high level, the wrapper determines a base commit (from CI variables or an explicit flag), fetches any missing refs, computes the diff, and maps changed files to projects. Only those projects proceed to database creation and query execution. The logic is platform‑agnostic, so the same behavior applies whether you run in GitHub Actions, Azure Pipelines, CircleCI, Harness, or locally.</p>
<h3 id="installing-and-running-codeql-wrapper" style="position:relative;"><a href="#installing-and-running-codeql-wrapper" aria-label="installing and running codeql wrapper permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Installing and Running CodeQL Wrapper</h3>
<p>We designed CodeQL Wrapper so that the first run feels frictionless and the hundredth run still feels predictable. Installation is a single step, and from there you can either prepare the environment or jump straight into analysis.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">pip <span class="token function">install</span> codeql-wrapper</code></pre></div>
<p>This will install the tool in your environment. Once installed, you’ll have access to two commands:</p>
<ul>
<li>install – Installs CodeQL CLI and query packs (you can pin a specific version if needed).</li>
<li>analyze – Runs CodeQL analysis on your project(s), with automatic project detection and parallelized scans.
If you only need the CodeQL CLI (and don’t want to run a scan yet), just run:</li>
</ul>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">codeql-wrapper <span class="token function">install</span></code></pre></div>
<p>This fetches the CodeQL CLI and query packs, and you can pin versions with <code class="language-text">--version</code> to keep results reproducible. When you’re ready to analyze, <code class="language-text">analyze</code> takes over the heavy lifting: it confirms CodeQL is installed, detects languages and projects, creates databases, and runs queries in parallel to suit your hardware.</p>
<h3 id="basic-usage" style="position:relative;"><a href="#basic-usage" aria-label="basic usage permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Basic Usage</h3>
<p>Most teams start by pointing the wrapper at the repository root. That’s intentional: you shouldn’t need to hand‑craft language lists or database steps to get useful results.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">codeql-wrapper analyze ./repo</code></pre></div>
<p>Behind the scenes, the wrapper inspects your codebase, identifies the languages that matter, and builds one database per language. If your repo is a monorepo, it scopes the work to projects it finds, running them concurrently. The default configuration leans on safe, broadly applicable queries and <code class="language-text">build-mode: none</code>, which makes first‑time adoption smooth even without bespoke build instructions.</p>
<p>As codebases grow, consistency matters more than one‑off tweaks. The wrapper’s model — automatic detection plus ergonomic overrides — offers practical advantages: it adapts to monorepos with different needs per project, keeps query policies uniform across CI providers, and makes exceptions explicit without touching pipeline YAML.</p>
<h3 id="why-ci-integration-is-easier" style="position:relative;"><a href="#why-ci-integration-is-easier" aria-label="why ci integration is easier permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why CI Integration is Easier</h3>
<p>Rather than generic promises, here’s what changes in day‑to‑day pipelines:</p>
<ul>
<li>One command surface: <code class="language-text">install</code> and <code class="language-text">analyze</code>. The wrapper handles language detection, database creation, and query execution, so pipelines don’t need fragile per‑language steps.</li>
<li>Version pinning: <code class="language-text">install --version</code> locks the CodeQL CLI version, avoiding environment drift between developer machines and CI runners.</li>
<li>Monorepo awareness: Independent projects (subdirectories) are processed in parallel, reducing custom CI logic.</li>
<li>Change detection: Git diffs are mapped to affected projects; unchanged ones are skipped, cutting incremental scan time.</li>
<li>Consistent outputs: SARIF is generated uniformly, so reporting stays the same regardless of CI platform.</li>
</ul>
<h2 id="usage-examples" style="position:relative;"><a href="#usage-examples" aria-label="usage examples permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Usage Examples</h2>
<p>Local runs establish trust, but enterprise adoption hinges on repeatable automation. To make that transition easy, we ship <a href="https://github.com/tweag/codeql-wrapper-pipelines">codeql-wrapper-pipelines</a>, a companion repository with templates for common CI/CD providers. These examples handle the practicalities — auth, artifacts, and reporting — while keeping the interface consistent. You get one way of invoking the wrapper, regardless of whether your pipelines live in GitHub Actions, Azure Pipelines, CircleCI, Harness, or elsewhere.</p>
<h2 id="why-we-built-it" style="position:relative;"><a href="#why-we-built-it" aria-label="why we built it permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why We Built It</h2>
<p>Our work with global enterprises adopting GitHub Advanced Security led us to build both <a href="https://github.com/tweag/codeql-wrapper">codeql-wrapper</a> and <a href="https://github.com/tweag/codeql-wrapper-pipelines">codeql-wrapper-pipelines</a>. The tools distill hard‑won lessons from monorepo rollouts: minimize manual configuration, keep behavior consistent across environments, and make smart defaults easy to override.</p>
<p>The goal isn’t just faster scans; it’s a smoother path to reliable, organization‑wide CodeQL usage. If you’re strengthening DevSecOps without piling on process, we think this approach strikes the right balance. Give the wrapper a try, explore the pipelines, and reach out on the repositories if you want help shaping them to your environment.</p>]]></description><link>https://tweag.io/blog/2026-01-15-codeql-wrapper/</link><guid isPermaLink="false">https://tweag.io/blog/2026-01-15-codeql-wrapper/</guid><pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[The quest for grammar combinators: introducing the Pup library]]></title><description><![CDATA[<p>Parser combinators are one of the prides of the Haskell community. They’re a
craft that we continue to polish to this day. Yet, there’s something
unsatisfactory about parser combinators.</p>
<p>See, when I write a parser, I frequently write a pretty-printer as
well<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>, and the pretty-printer is almost the same as the parser.
This makes maintenance harder, if only because parser and pretty-printer have to
be kept in sync. Besides, it simply feels like unnecessary duplication.</p>
<p>This blog post is the story of the latest developments in the quest for more
general grammar combinators—or as Mathieu Boespflug and I have been calling
them, <em>format descriptors</em>—and how it led me to publish a new library, called
<a href="https://hackage.haskell.org/package/pup">Pup</a>. For further reading, you can also check <a href="https://dl.acm.org/doi/abs/10.1145/3759427.3760381">the paper</a> that Mathieu and I wrote about it for <a href="https://conf.researchr.org/home/icfp-splash-2025/olivierfest-2025">Olivier Danvy’s festschrift</a>, held at the
ICFP/SPLASH conference in Singapore last October.</p>
<h2 id="a-library-of-format-descriptors" style="position:relative;"><a href="#a-library-of-format-descriptors" aria-label="a library of format descriptors permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A library of format descriptors</h2>
<p>The <a href="https://hackage.haskell.org/package/pup">Pup</a> library lets you write format descriptors in a familiar style. In fact, Pup uses existing parser combinator
libraries as a backend. But in addition to parsing, these format descriptors let you
pretty-print the code (using an existing pretty-printing library as a backend).</p>
<p>An example is better than a long explanation, so let me show you a format
descriptor for simplified <a href="https://en.wikipedia.org/wiki/S-expression">S-expressions</a>. It lets you parse expressions such as
this one:</p>
<div class="gatsby-highlight" data-language="lisp"><pre class="language-lisp"><code class="language-lisp"><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token car">a-fun</span> <span class="token number">57</span> <span class="token lisp-property property">:tag</span><span class="token punctuation">)</span> <span class="token string">"a string"</span> <span class="token punctuation">(</span><span class="token car">list</span> <span class="token number">1</span> <span class="token string">"somewhat"</span> <span class="token lisp-property property">:long</span> <span class="token string">"list of mixed types"</span>
 <span class="token punctuation">(</span><span class="token car">list</span> <span class="token string">"at least"</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token car">list</span> <span class="token string">"but maybe"</span> <span class="token lisp-property property">:more</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div>
<p>and then pretty-print the result (or any abstract syntax tree, really),
which looks like this:</p>
<div class="gatsby-highlight" data-language="lisp"><pre class="language-lisp"><code class="language-lisp"><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token car">a-fun</span> <span class="token number">57</span> <span class="token lisp-property property">:tag</span><span class="token punctuation">)</span>
  <span class="token string">"a string"</span>
  <span class="token punctuation">(</span><span class="token car">list</span>
    <span class="token number">1</span>
    <span class="token string">"somewhat"</span>
    <span class="token lisp-property property">:long</span>
    <span class="token string">"list of mixed types"</span>
    <span class="token punctuation">(</span><span class="token car">list</span> <span class="token string">"at least"</span> <span class="token number">3</span><span class="token punctuation">)</span>
    <span class="token punctuation">(</span><span class="token car">list</span> <span class="token string">"but maybe"</span> <span class="token lisp-property property">:more</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div>
<p>I didn’t implement any parsing or printing algorithm. Parsing is done by
<a href="https://hackage.haskell.org/package/megaparsec">Megaparsec</a> and pretty-printing by <a href="https://hackage.haskell.org/package/prettyprinter">Prettyprinter</a>. These are the only backends
that I have implemented so far—it’s a new library, but the library is written to be
easily extensible to other parsing and pretty-printing backends. What Pup
gives you is the surface syntax of the format descriptor DSL.</p>
<p>So here’s our example format descriptor. In the rest of the blog post I’ll
explain what’s going on (in particular why there are those labels like
<code class="language-text">#SSymb</code> and <code class="language-text">#":"</code> rather than constructors <code class="language-text">SSymb</code> and <code class="language-text">(:)</code>):</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">sexpr</span> <span class="token operator">=</span>
  <span class="token builtin">group</span> <span class="token punctuation">(</span><span class="token hvariable">nest</span> <span class="token number">2</span> <span class="token punctuation">(</span><span class="token operator">#</span><span class="token constant">SList</span> <span class="token operator">&lt;*</span> <span class="token hvariable">try</span> <span class="token punctuation">(</span><span class="token string">"("</span><span class="token punctuation">)</span> <span class="token operator">&lt;*</span> <span class="token hvariable">space</span> <span class="token operator">&lt;*></span> <span class="token hvariable">try</span> <span class="token hvariable">sexpr</span> <span class="token operator">`sepBy`</span> <span class="token hvariable">space1</span> <span class="token operator">&lt;*</span> <span class="token hvariable">space</span> <span class="token operator">&lt;*</span> <span class="token string">")"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token operator">&lt;|></span> <span class="token operator">#</span><span class="token constant">SSymb</span> <span class="token operator">&lt;*></span> <span class="token hvariable">try</span> <span class="token hvariable">symbol</span>
    <span class="token operator">&lt;|></span> <span class="token operator">#</span><span class="token constant">SInt</span> <span class="token operator">&lt;*></span> <span class="token hvariable">try</span> <span class="token hvariable">nat</span>
    <span class="token operator">&lt;|></span> <span class="token operator">#</span><span class="token constant">SStr</span> <span class="token operator">&lt;*</span> <span class="token hvariable">try</span> <span class="token punctuation">(</span><span class="token string">"\""</span><span class="token punctuation">)</span> <span class="token operator">&lt;*></span> <span class="token hvariable">takeWhileP</span> <span class="token constant">Nothing</span> <span class="token punctuation">(</span><span class="token operator">/=</span> <span class="token char string">'"'</span><span class="token punctuation">)</span> <span class="token operator">&lt;*</span> <span class="token string">"\""</span>
  <span class="token keyword">where</span>
    <span class="token hvariable">symbol</span> <span class="token operator">=</span> <span class="token operator">#</span><span class="token string">":"</span> <span class="token operator">&lt;*></span> <span class="token hvariable">symbol_lead</span> <span class="token operator">&lt;*></span> <span class="token hvariable">many</span> <span class="token punctuation">(</span><span class="token hvariable">try</span> <span class="token hvariable">symbol_other</span><span class="token punctuation">)</span>

    <span class="token hvariable">symbol_lead</span> <span class="token operator">=</span> <span class="token hvariable">oneOf</span> <span class="token punctuation">(</span><span class="token char string">':'</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token char string">'a'</span> <span class="token operator">..</span> <span class="token char string">'z'</span><span class="token punctuation">]</span> <span class="token operator">++</span> <span class="token punctuation">[</span><span class="token char string">'A'</span> <span class="token operator">..</span> <span class="token char string">'Z'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token hvariable">symbol_other</span> <span class="token operator">=</span> <span class="token hvariable">oneOf</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token char string">'a'</span> <span class="token operator">..</span> <span class="token char string">'z'</span><span class="token punctuation">]</span> <span class="token operator">++</span> <span class="token punctuation">[</span><span class="token char string">'A'</span> <span class="token operator">..</span> <span class="token char string">'Z'</span><span class="token punctuation">]</span> <span class="token operator">++</span> <span class="token punctuation">[</span><span class="token char string">'0'</span> <span class="token operator">..</span> <span class="token char string">'9'</span><span class="token punctuation">]</span> <span class="token operator">++</span> <span class="token punctuation">[</span><span class="token char string">'-'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

<span class="token keyword">data</span> <span class="token constant">SExpr</span>
  <span class="token operator">=</span> <span class="token constant">SList</span> <span class="token punctuation">[</span><span class="token constant">SExpr</span><span class="token punctuation">]</span>
  <span class="token operator">|</span> <span class="token constant">SSymb</span> <span class="token constant">String</span>
  <span class="token operator">|</span> <span class="token constant">SStr</span> <span class="token constant">Text</span>
  <span class="token operator">|</span> <span class="token constant">SInt</span> <span class="token constant">Int</span>
  <span class="token keyword">deriving</span> <span class="token punctuation">(</span><span class="token constant">Generic</span><span class="token punctuation">,</span> <span class="token constant">Show</span><span class="token punctuation">,</span> <span class="token constant">Eq</span><span class="token punctuation">)</span>

<span class="token comment">-- For completeness parse the example string, then pretty-print the result</span>
<span class="token keyword">do</span>
  <span class="token keyword">let</span> <span class="token hvariable">str</span> <span class="token operator">=</span>
    <span class="token comment">-- Did you know that GHC has multi-line strings now?</span>
    <span class="token string">""</span><span class="token string">"
      ((a-fun 57 :tag) "</span><span class="token hvariable">a</span> <span class="token hvariable">string</span><span class="token string">" (list 1 "</span><span class="token hvariable">somewhat</span><span class="token string">" :long "</span><span class="token hvariable">list</span> <span class="token keyword">of</span> <span class="token hvariable">mixed</span> <span class="token hvariable">types</span><span class="token string">"
       (list "</span><span class="token hvariable">at</span> <span class="token hvariable">least</span><span class="token string">" 3) (list "</span><span class="token hvariable">but</span> <span class="token builtin">maybe</span><span class="token string">" :more)))
    "</span><span class="token string">""</span>
  <span class="token keyword">case</span> <span class="token hvariable">parse</span> <span class="token hvariable">sexpr</span> <span class="token string">"&lt;test>"</span> <span class="token hvariable">str</span> <span class="token keyword">of</span>
    <span class="token constant">Left</span> <span class="token hvariable">e</span> <span class="token operator">-></span> <span class="token builtin">putStrLn</span> <span class="token hvariable">e</span>
    <span class="token constant">Right</span> <span class="token hvariable">expr</span> <span class="token operator">-></span> <span class="token keyword">do</span>
      <span class="token keyword">let</span> <span class="token hvariable">doc</span> <span class="token operator">=</span> <span class="token hvariable">Maybe<span class="token punctuation">.</span>fromJust</span> <span class="token punctuation">(</span><span class="token constant">Pup</span><span class="token punctuation">.</span><span class="token builtin">print</span> <span class="token hvariable">sexpr</span> <span class="token hvariable">expr</span><span class="token punctuation">)</span>
      <span class="token keyword">let</span> <span class="token hvariable">str'</span> <span class="token operator">=</span> <span class="token hvariable">Prettyprinter<span class="token punctuation">.</span>renderStrict</span> <span class="token operator">$</span> <span class="token hvariable">Prettyprinter<span class="token punctuation">.</span>layoutPretty</span> <span class="token hvariable">Prettyprinter<span class="token punctuation">.</span>defaultLayoutOptions</span> <span class="token hvariable">doc</span>
      <span class="token builtin">putStrLn</span> <span class="token operator">$</span> <span class="token hvariable">Text<span class="token punctuation">.</span>unpack</span> <span class="token hvariable">str'</span></code></pre></div>
<p>The <code class="language-text">sexpr</code> format descriptor is almost what you would write with Megaparsec.
There are two main differences</p>
<ul>
<li>The second line reads <code class="language-text">#SSymb &lt;*> try symbol</code>. In Megaparsec you would see
<code class="language-text">Symb &lt;$> try symbol</code>. We call <code class="language-text">#SSymb</code> a <em>lead</em>. It is in charge not only
of building an <code class="language-text">SSymb</code> when parsing, but also the reading out of an <code class="language-text">SSymb</code> when
printing. This is also why we have to use the applicative <code class="language-text">&lt;*></code> instead of the
functorial <code class="language-text">&lt;$></code>. The <code class="language-text">#SSymb</code> lead is automatically generated because our
type implements <code class="language-text">Generic</code>.</li>
<li>There are pretty-printing combinators <code class="language-text">group (nest 2 (…))</code>. These are
Wadler-Leijen type combinators as implemented in the Prettyprinter library.
They’re responsible for breaking lines and inserting indentation. You’ll need
some of these since a standard grammar can tell you what can be parsed, but
not what is pretty.</li>
</ul>
<p>Other than that, if you’re familiar with Megaparsec, you should feel right at
home. You even have access to the monadic <code class="language-text">>>=</code> if you need it (and yes, it
pretty-prints).</p>
<p>A word of caution: the Pup library helps you to make sure that the output of
the pretty-printer can be reparsed to the same AST, but it doesn’t guarantee it.
It’s probably unreasonable to hope for parsers and pretty-printers to be
inverses by construction with only Haskell’s type system (barring some very
drastic restrictions on possible parsers).</p>
<p>With that said. I think the library is cool, and you can <a href="https://hackage.haskell.org/package/pup">go use it today</a>! I hope you enjoy
it, and please let me know if you’re missing anything. And if you want
to know more about what makes the library tick: read on.</p>
<h2 id="tuple-troubles" style="position:relative;"><a href="#tuple-troubles" aria-label="tuple troubles permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Tuple troubles</h2>
<p>The type of the <code class="language-text">sexpr</code> format descriptor is maybe not what you’re expecting:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">sexpr</span> <span class="token operator">::</span> <span class="token constant">Pup'</span> <span class="token punctuation">(</span><span class="token constant">SExpr</span> <span class="token operator">-></span> <span class="token hvariable">r</span><span class="token punctuation">)</span> <span class="token hvariable">r</span> <span class="token constant">SExpr</span></code></pre></div>
<p>Why are there three type arguments rather than one? What is this <code class="language-text">r</code> type
variable about? These are where Pup’s secret sauce lies.</p>
<p>Fundamentally, a parser for the type <code class="language-text">a</code> is something of the form <code class="language-text">String -> a</code>,
and a printer something of the form <code class="language-text">a -> String</code>. Parsers are covariant
functors and printers are contravariant functors. So our bidirectional format
descriptor should be something like <code class="language-text">P0 a = (a -> String, String -> a)</code>, which
is neither a covariant nor a contravariant functor. The only map-like
transformation that <code class="language-text">P0</code> supports is something like</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">isomap</span> <span class="token operator">::</span> <span class="token punctuation">(</span><span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token hvariable">a</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">P0</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token constant">P0</span> <span class="token hvariable">b</span></code></pre></div>
<p>We also need something to chain parsers. To that effect, we can
introduce an <code class="language-text">andThen</code> combinator (we’d really need to enrich the <code class="language-text">P0</code> type to be
able to do that, but let’s pretend):</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">andThen</span> <span class="token operator">::</span> <span class="token constant">P0</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token constant">P0</span> <span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token constant">P0</span> <span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token hvariable">b</span><span class="token punctuation">)</span></code></pre></div>
<p>Together this adds up to a rather so-so experience.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">p0</span> <span class="token operator">::</span> <span class="token constant">P0</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token constant">B</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token hvariable">p0</span> <span class="token operator">=</span> <span class="token hvariable">foo</span> <span class="token operator">`andThen`</span> <span class="token hvariable">bar</span> <span class="token operator">`andThen`</span> <span class="token hvariable">baz</span> <span class="token operator">`andThen`</span> <span class="token hvariable">buzz</span>

<span class="token hvariable">p0'</span> <span class="token operator">::</span> <span class="token constant">P0</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">,</span> <span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span>
<span class="token hvariable">p0'</span> <span class="token operator">=</span> <span class="token hvariable">isomap</span> <span class="token punctuation">(</span><span class="token operator">\</span><span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token hvariable">c</span><span class="token punctuation">,</span> <span class="token hvariable">d</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token hvariable">c</span><span class="token punctuation">,</span> <span class="token hvariable">d</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">\</span><span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token hvariable">c</span><span class="token punctuation">,</span> <span class="token hvariable">d</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token hvariable">c</span><span class="token punctuation">,</span> <span class="token hvariable">d</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">p0</span></code></pre></div>
<p>Part of what makes this style uncomfortable is the deep nesting of product. And
when you get to reshuffle them, due to the need of passing two functions at the
same time, it adds a lot of visual boilerplate. This boilerplate would probably
dominate the parsing logic. This is what in our <a href="https://dl.acm.org/doi/abs/10.1145/3759427.3760381">paper</a> we call “tuple
troubles”.</p>
<p>In the case of covariant functors, there’s a ready-made solution: use an
applicative functor interface. It’s equivalent to pairing, it just works much
better in Haskell. But <code class="language-text">P0</code> isn’t a functor, and there’s really little we can do
to improve on this interface.</p>
<p>In order to address these shortcomings let’s use a classic technique and
decorrelate the type of what is parsed from the type of what is
printed<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup>.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">type</span> <span class="token constant">P1</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token constant">String</span><span class="token punctuation">,</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">b</span><span class="token punctuation">)</span></code></pre></div>
<p>A <code class="language-text">P1 a b</code> can parse a <code class="language-text">b</code> and print an <code class="language-text">a</code>. Of course we are generally
interested in using a <code class="language-text">P1 a a</code>, but we now have the ability to locally change
one and not the other. So that we can use:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token comment">-- If we know how to parse a `b` we can parse a `c`</span>
<span class="token hvariable">rmap</span> <span class="token operator">::</span> <span class="token punctuation">(</span><span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token hvariable">c</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">P1</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token constant">P1</span> <span class="token hvariable">a</span> <span class="token hvariable">c</span>
<span class="token comment">-- If we know how to print an `a` we can print a `d`</span>
<span class="token hvariable">lmap</span> <span class="token operator">::</span> <span class="token punctuation">(</span><span class="token hvariable">d</span> <span class="token operator">-></span> <span class="token hvariable">a</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">P1</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token constant">P1</span> <span class="token hvariable">d</span> <span class="token hvariable">b</span></code></pre></div>
<p>In Haskell parlance, <code class="language-text">P1</code> is a <a href="https://hackage-content.haskell.org/package/profunctors-5.6.3/docs/Data-Profunctor.html#t:Profunctor">profunctor</a>.</p>
<p>And now, since <code class="language-text">P1 a</code> is a functor for any <code class="language-text">a</code>, we can also equip it with an
applicative functor structure. The paper <a href="https://link.springer.com/chapter/10.1007/978-3-030-17184-1_6"><em>Composing Bidirectional Programs
Monadically</em></a> even shows how to equip <code class="language-text">P1 a</code> with a monadic structure. Pup
uses their technique for monadic parsing. Now our example reads as follows:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">p1</span> <span class="token operator">::</span> <span class="token constant">P1</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">,</span> <span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">,</span> <span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span>
<span class="token hvariable">p1</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">,</span><span class="token punctuation">,</span><span class="token punctuation">,</span><span class="token punctuation">)</span> <span class="token operator">&lt;$></span> <span class="token hvariable">lmap</span> <span class="token builtin">fst</span> <span class="token hvariable">foo</span> <span class="token operator">&lt;*></span> <span class="token hvariable">lmap</span> <span class="token builtin">snd</span> <span class="token hvariable">bar</span> <span class="token operator">&lt;*></span> <span class="token hvariable">lmap</span> <span class="token hvariable">thd</span> <span class="token hvariable">baz</span> <span class="token operator">&lt;*></span> <span class="token hvariable">lmap</span> <span class="token hvariable">fth</span> <span class="token hvariable">buzz</span></code></pre></div>
<p>This looks much more reasonable, the expression has the familiar structure of
applicative expressions. But the actual parsing logic is still drowned in all
these <code class="language-text">lmap</code> (and I’ve been quite generous here, as I’ve invented projections
<code class="language-text">fst</code>…<code class="language-text">fth</code> for 4-tuples). We really need a better way to combine printers.</p>
<h2 id="indexing-with-a-stack" style="position:relative;"><a href="#indexing-with-a-stack" aria-label="indexing with a stack permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Indexing with a stack</h2>
<p>To improve on the profunctor style, we need a better way to combine the contravariant side.</p>
<p>The style I propose, in the Pup library, looks like the following</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">p2</span> <span class="token operator">::</span> <span class="token constant">P2</span> <span class="token punctuation">(</span><span class="token constant">A</span> <span class="token operator">-></span> <span class="token constant">B</span> <span class="token operator">-></span> <span class="token constant">C</span> <span class="token operator">-></span> <span class="token constant">D</span> <span class="token operator">-></span> <span class="token hvariable">r</span><span class="token punctuation">)</span> <span class="token hvariable">r</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">,</span> <span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span>
<span class="token hvariable">p2</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">,</span><span class="token punctuation">,</span><span class="token punctuation">,</span><span class="token punctuation">)</span> <span class="token operator">&lt;$></span> <span class="token hvariable">foo</span> <span class="token operator">&lt;*></span> <span class="token hvariable">bar</span> <span class="token operator">&lt;*></span> <span class="token hvariable">baz</span> <span class="token operator">&lt;*></span> <span class="token hvariable">buzz</span></code></pre></div>
<p>I’ll come back to the definition of <code class="language-text">P2</code> in a moment. But in the meantime, here’s
how I want you to read the type:</p>
<ul>
<li>In some descriptor <code class="language-text">descr :: P2 r r' a</code>, <code class="language-text">a</code> is the return type, and <code class="language-text">r</code> and <code class="language-text">r'</code> represent a stack of
arguments. With <code class="language-text">r</code> representing the shape of the stack before <code class="language-text">descr</code>, and <code class="language-text">r'</code>
the state of the stack after <code class="language-text">descr</code>.</li>
<li>A stack of the form <code class="language-text">A -> B -> C -> D -> r</code> is a stack with at least four
elements, and whose topmost elements are of type <code class="language-text">A</code>, <code class="language-text">B</code>, <code class="language-text">C</code>, and <code class="language-text">D</code> in
that order. The rest of the stack is unknown, which is represented by a type
variable <code class="language-text">r</code>.</li>
<li>The format descriptor <code class="language-text">foo</code>, for instance, has an argument of type <code class="language-text">A</code> (used
when printing). To obtain this argument, <code class="language-text">foo</code> pops the topmost element of the
argument stack, so <code class="language-text">foo</code> has type <code class="language-text">foo :: P2 (A -> r) r A</code>.</li>
</ul>
<p>This usage pattern of passing a different argument to each format descriptor in
a sequence, as how it’s naturally done by a stack, appears to be by far the most
common in format descriptors, so Pup optimises for it.</p>
<p>You might have noticed the catch, though: the type arguments <code class="language-text">r</code> and <code class="language-text">r'</code> vary
during a sequence of actions. This means that <code class="language-text">P2</code> isn’t quite an applicative
functor and <code class="language-text">&lt;*></code>, above, isn’t the combinator we’re used to. Instead <code class="language-text">P2</code> is
an <em>indexed</em> applicative functor (and possibly an indexed monad too).</p>
<p>The type of <code class="language-text">&lt;*></code> for indexed applicatives <code class="language-text">f</code> is:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token punctuation">(</span><span class="token operator">&lt;*></span><span class="token punctuation">)</span> <span class="token operator">::</span> <span class="token hvariable">f</span> <span class="token hvariable">r</span> <span class="token hvariable">r'</span> <span class="token punctuation">(</span><span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">b</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token hvariable">f</span> <span class="token hvariable">r'</span> <span class="token hvariable">r''</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">f</span> <span class="token hvariable">r</span> <span class="token hvariable">r''</span> <span class="token hvariable">b</span></code></pre></div>
<p>On the other hand, <code class="language-text">&lt;$></code> is the usual <code class="language-text">&lt;$></code> from the <code class="language-text">Functor</code> type class.
Specialised to an indexed applicative <code class="language-text">f</code>, its type is:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token punctuation">(</span><span class="token operator">&lt;$></span><span class="token punctuation">)</span> <span class="token operator">::</span> <span class="token punctuation">(</span><span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">b</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token hvariable">f</span> <span class="token hvariable">r</span> <span class="token hvariable">r'</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">f</span> <span class="token hvariable">r</span> <span class="token hvariable">r'</span> <span class="token hvariable">b</span></code></pre></div>
<p>With this established, we can see how the type of individual syntax descriptors
can be sequenced:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">foo</span> <span class="token operator">::</span> <span class="token constant">P2</span> <span class="token punctuation">(</span><span class="token constant">A</span> <span class="token operator">-></span> <span class="token hvariable">r1</span><span class="token punctuation">)</span> <span class="token hvariable">r1</span> <span class="token constant">A</span>
<span class="token hvariable">bar</span> <span class="token operator">::</span> <span class="token constant">P2</span> <span class="token punctuation">(</span><span class="token constant">B</span> <span class="token operator">-></span> <span class="token hvariable">r2</span><span class="token punctuation">)</span> <span class="token hvariable">r2</span> <span class="token constant">B</span>

<span class="token comment">-- `r1` is universally quantified</span>
<span class="token comment">-- so we can unify `r1` with `B -> r2`</span>
<span class="token hvariable">q</span> <span class="token operator">::</span> <span class="token constant">P2</span> <span class="token punctuation">(</span><span class="token constant">A</span> <span class="token operator">-></span> <span class="token constant">B</span> <span class="token operator">-></span> <span class="token hvariable">r2</span><span class="token punctuation">)</span> <span class="token hvariable">r2</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">)</span>
<span class="token hvariable">q</span> <span class="token operator">::</span> <span class="token punctuation">(</span><span class="token punctuation">,</span><span class="token punctuation">)</span> <span class="token operator">&lt;$></span> <span class="token hvariable">foo</span> <span class="token operator">&lt;*></span> <span class="token hvariable">bar</span></code></pre></div>
<p>Sequencing an action which pops an <code class="language-text">A</code> and an action which pops a <code class="language-text">B</code> yields an
action which pops both an <code class="language-text">A</code> and a <code class="language-text">B</code>. Precisely what we needed. Notice how,
crucially, <code class="language-text">r1</code> in <code class="language-text">foo</code> and <code class="language-text">r2</code> in <code class="language-text">bar</code> are universally quantified variables,
so that they can be refined when a subsequent value requires a value from the
stack.</p>
<p>The existing support for indexed applicatives and monads isn’t very good (mostly
it consists of the somewhat minimalistic and pretty dusty <a href="https://hackage.haskell.org/package/indexed">indexed
library</a>). This is why I’m also releasing a new, modern,
indexed-applicative-and-monads companion library <a href="https://hackage.haskell.org/package/stacked">Stacked</a>, which makes use of
modern GHC facilities such as quantified constraints and qualified <code class="language-text">do</code>. Pup is
built on top of Stacked.</p>
<p>The last piece of the puzzle is what we’ve been calling “leads”, which are
actions which neither print nor parse anything, but serve as a bidirectional
version of constructors. For types which implement the <code class="language-text">Generic</code> type class,
leads are derived automatically. And with that we can complete our example:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token operator">#</span><span class="token string">"(,,,)"</span> <span class="token operator">::</span> <span class="token constant">P2</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token hvariable">c</span><span class="token punctuation">,</span> <span class="token hvariable">d</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token hvariable">r</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token hvariable">c</span> <span class="token operator">-></span> <span class="token hvariable">d</span> <span class="token operator">-></span> <span class="token hvariable">r</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">b</span> <span class="token operator">-></span> <span class="token hvariable">c</span> <span class="token operator">-></span> <span class="token hvariable">d</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token hvariable">a</span><span class="token punctuation">,</span> <span class="token hvariable">b</span><span class="token punctuation">,</span> <span class="token hvariable">c</span><span class="token punctuation">,</span> <span class="token hvariable">d</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

<span class="token hvariable">p2'</span> <span class="token operator">::</span> <span class="token constant">P2</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">,</span> <span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token hvariable">r</span><span class="token punctuation">)</span> <span class="token hvariable">r</span> <span class="token punctuation">(</span><span class="token constant">A</span><span class="token punctuation">,</span> <span class="token constant">B</span><span class="token punctuation">,</span> <span class="token constant">C</span><span class="token punctuation">,</span> <span class="token constant">D</span><span class="token punctuation">)</span>
<span class="token hvariable">p2'</span> <span class="token operator">=</span> <span class="token operator">#</span><span class="token string">"(,,,)"</span> <span class="token operator">&lt;*></span> <span class="token hvariable">foo</span> <span class="token operator">&lt;*></span> <span class="token hvariable">bar</span> <span class="token operator">&lt;*></span> <span class="token hvariable">baz</span> <span class="token operator">&lt;*></span> <span class="token hvariable">buzz</span></code></pre></div>
<p>This uses the <a href="https://downloads.haskell.org/ghc/9.12.2/docs/users_guide/exts/overloaded_labels.html#extension-OverloadedLabels"><code class="language-text">OverloadedLabels</code> extension</a>. Equivalently,
you can use <code class="language-text">lead @"(,,,)"</code> instead of <code class="language-text">#"(,,,)"</code>.</p>
<h3 id="a-note-on-divisible" style="position:relative;"><a href="#a-note-on-divisible" aria-label="a note on divisible permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A note on divisible</h3>
<p>There’s an existing structure on contravariant functors which is analogous to
applicative functors on covariant functors: the <a href="https://hackage.haskell.org/package/contravariant-1.5.5/docs/Data-Functor-Contravariant-Divisible.html#t:Divisible"><code class="language-text">Divisible</code></a>.
However, it doesn’t help us with our goal in this section, since the divisible
structure is already used implicitly in <code class="language-text">P1</code>’s instance of <code class="language-text">Applicative</code>.</p>
<p>In fact, here’s a theorem (the proof is left as an exercise to the reader): let <code class="language-text">d</code> and <code class="language-text">f</code> be a
divisible and an applicative functor, respectively, then <code class="language-text">t a b = (d a, f b)</code> is
applicative (with respect to <code class="language-text">b</code>).</p>
<h2 id="implemented-with-continuations" style="position:relative;"><a href="#implemented-with-continuations" aria-label="implemented with continuations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Implemented with continuations</h2>
<p>This concludes the motivation behind the library’s design. You can treat the fact
that we use the function arrow <code class="language-text">(->)</code> as a stack constructor as an
implementation detail. But if you’re interested, let’s now get into how we can
possibly implement <code class="language-text">P2</code>.</p>
<p>The idea goes back to Olivier Danvy’s <a href="https://www.brics.dk/RS/98/12/BRICS-RS-98-12.pdf"><em>Functional
unparsing</em></a>, where Danvy presents a straightforward implementation of
combinators for <code class="language-text">printf</code>-like format strings using continuations. The idea was
that just like the original <code class="language-text">printf</code>, all the arguments to be formatted were just
additional argument passed to the <code class="language-text">printf</code> function. This is the technique we
used. With that in mind, <code class="language-text">P2</code> can be defined as:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">type</span> <span class="token constant">P2</span> <span class="token hvariable">r1</span> <span class="token hvariable">r2</span> <span class="token hvariable">b</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">r2</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token hvariable">r1</span><span class="token punctuation">,</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">b</span><span class="token punctuation">)</span>

<span class="token comment">-- Compare:</span>
<span class="token comment">-- P2 (a -> r) r b = ((String -> r) -> a -> r, String -> b)</span>
<span class="token comment">-- P1 a b = (a -> String, String -> b)</span></code></pre></div>
<p>With this definition, we naturally have a stack defined in terms.</p>
<h2 id="final-touches" style="position:relative;"><a href="#final-touches" aria-label="final touches permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Final touches</h2>
<p>Now, this isn’t the definition of the real <code class="language-text">Pup'</code> type. This definition of <code class="language-text">P2</code>
can’t deal with data types with several constructors. To deal with type
constructors, we need a way for parsers to signal failures and for failures to be handled. And,
maybe surprisingly, we need a way for printers to signal failures, and for
those failures to be caught. Even though we typically think of pretty-printers as
total, we are going to build them piecewise, constructor by constructor, and
those partial printers can fail (another way to think about it is that a format
descriptor for a parser which can only parse into some of the constructors of a
type, can only print from those same constructors).</p>
<p>Failures are handled by the <code class="language-text">(&lt;|>)</code> combinator which we can see in the
S-expression example. These are reminiscent of the <code class="language-text">Alternative</code> and <code class="language-text">MonadPlus</code>
classes from the Base library. But because we are dealing with indexed
applicatives and monads, it’s a different combinator, which is provided, in
the Stacked library, by the <code class="language-text">Additive</code> class:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">class</span> <span class="token constant">Additive</span> <span class="token hvariable">a</span> <span class="token keyword">where</span>
  <span class="token hvariable">empty</span> <span class="token operator">::</span> <span class="token hvariable">a</span>
  <span class="token punctuation">(</span><span class="token operator">&lt;|></span><span class="token punctuation">)</span> <span class="token operator">::</span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">a</span> <span class="token operator">-></span> <span class="token hvariable">a</span></code></pre></div>
<p>The standard way to add failures in Haskell is the <code class="language-text">Maybe</code> monad. We can, in
fact, use the <code class="language-text">Maybe</code> monad in the parser to signal failure (in the actual Pup
library, we simply reuse the failure mechanism from the parsing backend). But it
doesn’t work for the pretty-printer, as the <code class="language-text">Maybe</code> monad would interfere with the
continuation-based stacks (this is explained in more details in §5.2 of the
<a href="https://dl.acm.org/doi/abs/10.1145/3759427.3760381">paper</a>).</p>
<p>As it turns out, it would seem that the only way to add failures and handlers to
the pretty-printer is to add a second continuation. So our type will look like
this:</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">type</span> <span class="token constant">P3</span> <span class="token hvariable">r1</span> <span class="token hvariable">r2</span> <span class="token hvariable">b</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">r2</span> <span class="token operator">-></span> <span class="token hvariable">r2</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token hvariable">r1</span> <span class="token operator">-></span> <span class="token hvariable">r1</span><span class="token punctuation">,</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token hvariable">b</span><span class="token punctuation">)</span></code></pre></div>
<p>That’s about it. As you can see, the types get pretty hairy. But it’s kept
decently under control by defining the right abstractions (of course, indexed
applicatives and monads are such abstractions, but we also propose some new
ones). In particular, the most central abstraction in Pup’s design is the
<a href="https://hackage-content.haskell.org/package/stacked-0.1.0/docs/Control-Monad-Indexed-Cont2.html#t:Stacked">Stacked type class</a>. All this is explained in our <a href="https://dl.acm.org/doi/abs/10.1145/3759427.3760381">paper</a>
(spoiler, because I’m quite happy with this one: it features comonads).</p>
<h2 id="in-conclusion" style="position:relative;"><a href="#in-conclusion" aria-label="in conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>In conclusion</h2>
<p>Of course <a href="https://hackage.haskell.org/package/pup">Pup</a> is pretty fresh, and as such there will be some rough edges. But
I think it’s already quite useful. I expect it to be straightforward to convert a
parser implemented using Megaparsec to Pup. So if you have one of those, you should
consider it.</p>
<p>Alternatively, you can check Mathieu’s proof-of-concept library <a href="https://hackage.haskell.org/package/cassette">cassette</a>, whose current design
is described in our <a href="https://dl.acm.org/doi/abs/10.1145/3759427.3760381">paper</a> as well. Cassette’s format descriptors combine better
than Pup’s. But cassette doesn’t support monadic parsing, or using existing
parsers (yet?).</p>
<p>But really, the techniques that we’ve developed for Pup, ostensibly for parsing and
pretty-printing format descriptors, can presumably be used in many bidirectional
programming situations. In fact, I assume that you can often replace profunctors
by stacked applicatives. It’s just completely unexplored. I’m curious to hear
your thoughts about that.</p>
<!--  LocalWords:  combinators combinator backend festschrift functors parsers
<!--  LocalWords:  applicative monadic AST profunctors applicatives comonads
 -->
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">
<p>A pretty-printer, by the way, is not the same thing as a
formatter. A pretty-printer takes an abstract syntax tree and turns it into
something human-readable. A formatter takes text written by a human and
normalises it according to some styling rule. This blog post is about
pretty-printing not formatting. For our take on formatting see <a href="https://github.com/tweag/ormolu/">Ormolu</a>
and <a href="https://topiary.tweag.io/">Topiary</a>.<a href="#fnref-1" class="footnote-backref">↩</a></p>
</li>
<li id="fn-2">
<p>Here are some Haskell libraries which use this
decoupling trick</p>
<ul>
<li><a href="https://hackage.haskell.org/package/tomland">tomland</a> a bidirectional
Toml parsing library</li>
<li><a href="https://hackage.haskell.org/package/autodocodec">autodocodec</a> a
bidirectional Json parsing library</li>
<li><a href="https://hackage.haskell.org/package/distributors">distributors</a> a generic
bidirectional parsing library</li>
</ul>
<p>The same idea can even be found in pure
mathematics in the concept of <a href="https://en.wikipedia.org/wiki/Dinatural_transformation">dinatural
transformation</a>.<a href="#fnref-2" class="footnote-backref">↩</a></p>
</li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2026-01-08-grammar-combinators/</link><guid isPermaLink="false">https://tweag.io/blog/2026-01-08-grammar-combinators/</guid><pubDate>Thu, 08 Jan 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Announcing rules_img: a faster path to container images in Bazel]]></title><description><![CDATA[<p>Say you have a Bazel project that builds a web application, and you want to deploy it as a Docker container. The app is already built by Bazel, so you just need to package it into an image with the right base layers and configuration. This should be quick. Bazel is good at this sort of thing. But when you add container image building to your setup, something surprising happens: your builds start downloading gigabytes of base image data, your CI slows down, and pushing images feels slow. This is the story of why that happens and how <a href="https://github.com/bazel-contrib/rules_img"><code class="language-text">rules_img</code></a> fixes it.</p>
<blockquote>
<p>Prefer watching to reading? <a href="https://www.youtube.com/watch?v=biYXmAv4Ppk">The content of this post is also available in video form</a>.</p>
</blockquote>
<h2 id="the-components" style="position:relative;"><a href="#the-components" aria-label="the components permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The components</h2>
<p>Before we dive in, we need to establish where data lives and where it moves. There are three main players:</p>
<ul>
<li><strong>The registry</strong> (like Docker Hub or gcr.io): A remote server that stores container images. You download base images from here and push your built images back to it.</li>
<li><strong>Your local machine</strong>: Where you run <code class="language-text">bazel build</code> or <code class="language-text">bazel run</code>. This is your laptop or workstation.</li>
<li><strong>Remote execution and remote cache</strong>: A remote caching backend (like Aspect Workflows, BuildBuddy, EngFlow, or Google’s RBE) that runs Bazel actions on remote machines and caches the results. Optional, but common in CI and larger projects.</li>
</ul>
<p>The core tension is simple: to build a container image that extends a base image, you need information about that base. The question is how much information, and where does it need to be?</p>
<h2 id="the-scenario" style="position:relative;"><a href="#the-scenario" aria-label="the scenario permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The scenario</h2>
<p>Here’s what building a container image looks like with <code class="language-text">rules_oci</code>, the current recommended approach. I’ll show the data flow explicitly:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># Pull a base image</span>
<span class="token comment"># → Downloads manifest + config + all layer blobs from registry to local machine</span>
pull<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"ubuntu"</span><span class="token punctuation">,</span>
    image <span class="token operator">=</span> <span class="token string">"index.docker.io/library/ubuntu:24.04"</span><span class="token punctuation">,</span>
    digest <span class="token operator">=</span> <span class="token string">"sha256:1e622c5..."</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Build your image</span>
<span class="token comment"># → Creates a directory containing all blobs (base layers + your layer)</span>
<span class="token comment"># → With remote execution: all blobs are inputs and outputs of this actions</span>
oci_image<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"app_image"</span><span class="token punctuation">,</span>
    base <span class="token operator">=</span> <span class="token string">"@ubuntu"</span><span class="token punctuation">,</span>  <span class="token comment"># References the complete local directory</span>
    tars <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">":app_layer.tar"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    entrypoint <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"/app/bin/server"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Push to registry</span>
<span class="token comment"># → Downloads all image blobs from remote cache to local machine</span>
<span class="token comment"># → Uploads missing blobs from local machine to registry</span>
oci_push<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"push"</span><span class="token punctuation">,</span>
    image <span class="token operator">=</span> <span class="token string">":app_image"</span><span class="token punctuation">,</span>
    repository <span class="token operator">=</span> <span class="token string">"gcr.io/my-project/app"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p><strong>Data flow summary:</strong></p>
<ol>
<li>Registry → Local machine: full base image (hundreds of MB)</li>
<li>Local machine → Remote cache: full base image (anything that’s not already cached)</li>
<li>Remote cache → Remote Executor (creating an image): full image (hundreds of MB)</li>
<li>Remote cache → Local machine: full image (hundreds of MB)</li>
<li>Local machine → Registry: missing layers</li>
</ol>
<p>Here’s the same thing with <code class="language-text">rules_img</code>:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># Pull a base image</span>
<span class="token comment"># → Downloads only manifest + config JSON from registry to local machine (~10 KB)</span>
<span class="token comment"># → Layer blobs stay in the registry</span>
pull<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"ubuntu"</span><span class="token punctuation">,</span>
    registry <span class="token operator">=</span> <span class="token string">"index.docker.io"</span><span class="token punctuation">,</span>
    repository <span class="token operator">=</span> <span class="token string">"library/ubuntu"</span><span class="token punctuation">,</span>
    tag <span class="token operator">=</span> <span class="token string">"24.04"</span><span class="token punctuation">,</span>
    digest <span class="token operator">=</span> <span class="token string">"sha256:1e622c5..."</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Build a layer</span>
<span class="token comment"># → Writes layer tar + metadata to Bazel's content-addressable storage</span>
<span class="token comment"># → With remote execution: layer blob stays in remote cache</span>
image_layer<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"app_layer"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"/app/bin/server"</span><span class="token punctuation">:</span> <span class="token string">"//cmd/server"</span><span class="token punctuation">,</span>  <span class="token comment"># Bazel-built binary</span>
        <span class="token string">"/app/config"</span><span class="token punctuation">:</span> <span class="token string">"//configs:prod"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Assemble the image</span>
<span class="token comment"># → Writes manifest JSON referencing base layers + your layers (by digest)</span>
<span class="token comment"># → Only metadata is read and written</span>
image_manifest<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"app"</span><span class="token punctuation">,</span>
    base <span class="token operator">=</span> <span class="token string">"@ubuntu"</span><span class="token punctuation">,</span>  <span class="token comment"># References only metadata, not blobs</span>
    layers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">":app_layer"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>  <span class="token comment"># References only metadata, not blobs</span>
    entrypoint <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"/app/bin/server"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Push (at bazel run time, not build time)</span>
<span class="token comment"># → Checks registry: which blobs are already present?</span>
<span class="token comment"># → Streams only missing blobs: remote cache → local machine → registry</span>
<span class="token comment"># → If layers are already in registry: nothing to transfer</span>
image_push<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"push_app"</span><span class="token punctuation">,</span>
    image <span class="token operator">=</span> <span class="token string">":app"</span><span class="token punctuation">,</span>
    registry <span class="token operator">=</span> <span class="token string">"ghcr.io"</span><span class="token punctuation">,</span>
    repository <span class="token operator">=</span> <span class="token string">"my-project/app"</span><span class="token punctuation">,</span>
    tag <span class="token operator">=</span> <span class="token string">"latest"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p><strong>Data flow summary:</strong></p>
<ol>
<li>Registry → Local machine: only manifest + config (~10 KB)</li>
<li>Local machine → Remote cache: only metadata on base images</li>
<li>Remote cache → Local machine → Registry: only missing blobs (often just your new layers)</li>
<li>Base layers (almost) never move through local machine or remote executors</li>
</ol>
<h2 id="a-twominute-primer-on-images" style="position:relative;"><a href="#a-twominute-primer-on-images" aria-label="a twominute primer on images permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A two‑minute primer on images</h2>
<p>An OCI image is a bundle of metadata and bytes. The bytes live in <em>layers</em>, which are compressed tar archives that encode file additions and deletions. The metadata lives in three JSON objects:</p>
<ul>
<li>The <em>config</em>: what to run, environment variables, user, working directory, and the list of uncompressed layer digests (also called diff IDs)</li>
<li>The <em>manifest</em>: pointers to one config and many layer blobs, identified by digest, size, and media type</li>
<li>The <em>index</em>: for multi‑architecture images, a list of per‑platform manifests</li>
</ul>
<p>Tags in a registry point at a manifest digest. The digests are content-addressed, so the same bytes always mean the same name everywhere.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/2c013ad4d476d25db7c0322b0665dd58/2bef9/layers_in_a_trenchcoat.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 150%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAIAAACjcKk8AAAACXBIWXMAAAsTAAALEwEAmpwYAAAFJUlEQVQ4y22VaU9aWRzG/RDzYtJ5M5kPMMl0mcm0najUahErQqWC1WqqVuuGVFDBBURAdmRVXKjAvWyiYFlUFMcu2m1kB50mbdI0LbiB7UwynXcTtnppJzm5OS/O7z7Pfzsnb88HpBaYWsn9vh/8++VcPGx479HGfMBBQP/Xn/PHO6aYV5s84M+c3PeDeRAMTB71g2+eqSzTZDW/RcO9BQqb5xV4taB108KOh/Qxb45M3uddEvZqj4L65za+U0VaB3pdauKy6u4YGS3tRYCi1kTEFPVoM7AfhConfxHzag8Dum2n4IFxILTM2V3jP7MybJOddkndwgQhHjJknev2/bo9XxIGMssPxHzAUVC3aeWEXZzNOerybM+bx2KLvNUmrjUr8PGwMQX/jzKQVo6H9A/nR8W0m5PspsVpouNe3xTj5iwdMz9BPI6YohkYSPN5kGwDMS8QDxs2rZz+tnImsXKYgCI0lLTdyG/HnV/SDh1HjNG07Wx1ILazMT9eGH1uGY6scPwO5oZ+QEHDLQhuOGfJhwH9ie2UWN5n2TS87wOeLrKfLtC2zNStuSGvnbU43mbi4kyKu0dB3fttNaQ6X8HHEYNDPfjcSl/TkpdnSa8fiAyiRoeknnSrIOCWf9hJVyvjPA9S5GSqw+uykU5Eb0spi1Qpo9exybj+O3BKYwEfD+OTyoNrsmyr5DZJUnbHtAIM9dSd1481TzLrpEPV4kFsXcU5Uu0vPHyRsDN/aqRmz6+H2s5EH/VoPu7OqbhN0p7SLTNtd5W742J77MzpYYyOfpXTAdONlM9QkUG3PCWuPYk5lhb3gYIe9N3qs6tq4gN9vxsgm8TNSgpcSYGD9HIbv1JLQ2xZ2ImwMeY5gTPtEV5X8AglEmLRaFepz8GyTnSyO4umKHCXFDdLLTONohZ5aMc9SrbJoXDY+MTC4rQXuKTYScqVvqZLvK5iEwvpkmJXpdiZAYSeWXGfh7JMEFKwZs+bjTnq0SYiJoO0Q9BVaBdiNsarBxsu8PAwtxxnF2LWFdWzQ2V6RoWNjzLLO76Ak+JRn26chmO0XFwSV1m4aCUFvsBBO0QYpwhjZqOkpGJ5b8lEb9E4vTY7IQCkzn5wilHPxxetSLBW3jVq02/ynmKXtMohwijJV1hthWPdJUoyXMtvPkrOpiYLe5MxJ8KGaU4LpwO2MV5tYCAnKXAbv9IpwiyLq4ysitH2Ai6hbJp6fUFJTPWZJidhibDBtypTDaN+l2MNTORIS76aWrYiqXKKrjtEGCv/mrj78mAb4uUj5UEATN9HJ/BRyBByS9U05MZ4NUC/Kum+bGajnCKMQ4SxCzFL4iqHAG0SNR6GMgHnwImI8YWdL+2+tC6vMo+iDMyKh8obK5LrqzLsmhznVuDMTMTMCO7tH8nJ+yJhwL4PePvi3io4KCAhO7FnBxouzAwgVIOISUqpkHCJ13VZy61/aGYcBHRZ5a9G8tPrRbUI31VzsR55xjrTZ1P115adbkCe7rldehQ2JRvTC0BH8mS4ox7tv68sjyy8O7gL5NvFLx9PxcNGXh+W3ASbV5I+REzvtzXQmydnng8CoE1FuVXxc8m572CnT7k0ff4lXk35OVT+D+Kh2o8RI+QmyElY5ttWU1h05lQj+iyXWGYU1sF++rbwx2/gv34/Rka9eyKDeM5RTr9SwLttdXBN9mZr4tAztesWzvBap7l3/CvCfa/6C9kU7AehYR8EdImwMZVS8DBo+OeV5dMrSyJihL5Kn23/B4o0pDbEG7SzAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="A container image is just some tar files in a trenchcoat"
        title="A container image is just some tar files in a trenchcoat"
        src="/static/2c013ad4d476d25db7c0322b0665dd58/fcda8/layers_in_a_trenchcoat.png"
        srcset="/static/2c013ad4d476d25db7c0322b0665dd58/12f09/layers_in_a_trenchcoat.png 148w,
/static/2c013ad4d476d25db7c0322b0665dd58/e4a3f/layers_in_a_trenchcoat.png 295w,
/static/2c013ad4d476d25db7c0322b0665dd58/fcda8/layers_in_a_trenchcoat.png 590w,
/static/2c013ad4d476d25db7c0322b0665dd58/efc66/layers_in_a_trenchcoat.png 885w,
/static/2c013ad4d476d25db7c0322b0665dd58/2bef9/layers_in_a_trenchcoat.png 1024w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p><strong>How builds usually work.</strong> <code class="language-text">docker build</code> executes a Dockerfile <em>inside</em> a base image. Each step like <code class="language-text">RUN</code>, <code class="language-text">COPY</code>, or <code class="language-text">ADD</code> runs against a snapshot of the previous root file system and produces a new layer. The final image is the base’s layers plus the layers created by those steps. This is convenient, but it assumes you have the base image bytes locally while you build.</p>
<p><strong>How Bazel thinks about it.</strong> Bazel does not need to <em>run inside</em> the base at all. It builds your program artifacts the same way it always does<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>, then assembles an image by writing a config and a manifest that reference the base image <em>by digest</em> alongside the new layers you produced. Bazel needs the base’s identity to compose a correct manifest and, later, to upload or load the image. But it doesn’t have to materialize the base layers during the build itself<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup>.</p>
<p><strong>Why this matters for performance.</strong> Assembling an image is easy. It’s mostly JSON with a few checksums. The hard part is <em>data locality</em>: getting the right bytes to the right place at the right time. Do the executors have to download layers just to write a small manifest? Does a pusher really need to pull all blobs to a workstation before uploading them again? Does a local daemon have to ingest layers it already owns? <code class="language-text">rules_img</code> answers those questions by moving metadata first and moving bytes only at the edges.</p>
<h2 id="the-status-quo-rules_oci" style="position:relative;"><a href="#the-status-quo-rules_oci" aria-label="the status quo rules_oci permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The status quo: rules_oci</h2>
<p>The first major ruleset for building container images in Bazel was <code class="language-text">rules_docker</code>, which integrated with every language ecosystem: Python, Node.js, Java, Scala, Groovy, C++, Go, Rust, and D. This approach proved extremely hard to maintain. Any change in a language ruleset could ripple into <code class="language-text">rules_docker</code>. Today it is mostly unmaintained and lacks official bzlmod support.</p>
<p>The current recommendation is <a href="https://github.com/bazel-contrib/rules_oci"><code class="language-text">rules_oci</code></a>, which takes the opposite approach: use only off‑the‑shelf tools, maintain a strict complexity budget, and delegate layer creation to language rulesets or end users. This design results in a maintainable project with a narrow scope that’s easy to understand.</p>
<figure>
<p><img src="/ba15c362074a6f3d56c09848d334ea3b/rules_oci_repo_rule.svg" alt="Data transfers performed by rules_oci when pulling a base image"></p>
<figcaption>Data transfers performed by rules_oci when pulling a base image</figcaption>
</figure>
<p>Under the hood, <code class="language-text">rules_oci</code> represents images as complete <a href="https://specs.opencontainers.org/image-spec/image-layout/">OCI layouts</a> on disk. When you pull a base image, the repository rule downloads the full image—all blobs, all layers—into a tree artifact. When you build an image with <code class="language-text">oci_image</code> or <code class="language-text">oci_image_index</code>, the result is again a directory containing every blob of that image. Layers are always tar files, with no separate metadata to describe them, and the ruleset does not use Bazel providers to pass structured information between targets. This approach is simple and works well for local builds, but as we scaled to Remote Execution, we encountered bottlenecks that this design did not address.</p>
<h2 id="from-bottlenecks-to-breakthroughs-how-rules_img-works" style="position:relative;"><a href="#from-bottlenecks-to-breakthroughs-how-rules_img-works" aria-label="from bottlenecks to breakthroughs how rules_img works permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>From bottlenecks to breakthroughs: how rules_img works</h2>
<p>I started with a simple goal: build container images in Bazel and let Remote Execution carry the weight. I used <code class="language-text">rules_oci</code> in my experiments, the recommended way of building container images in Bazel today<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup>. I was surprised by the inefficiencies I saw. Repository rules that pulled base images ran again and again in CI, even when nothing had changed<sup id="fnref-4"><a href="#fn-4" class="footnote-ref">4</a></sup>. My laptop shoveled data uphill to the remote cache before any real work could begin. Actions that only wrote a few lines of JSON insisted on dragging entire layer blobs along for the ride. When the build finally finished on RBE, Bazel downloaded every layer into a push tool’s runfiles, only to upload them to a registry a moment later. Loading images into Docker added insult to injury by ignoring layers that were already present. None of that felt like Bazel, so I ran experiments until a pattern emerged.</p>
<p><strong>The breakthrough: treat images as metadata first.</strong> The key was to see the whole build as a metadata pipeline and to move bytes only at the edges. Keep base images shallow until you truly need a blob. Assemble manifests from digests and sizes, not gigabytes. Push and load by streaming from content‑addressable storage straight to the destination, and skip anything that already exists there. Once that clicked, the rest of the design fell into place.</p>
<p><strong>Pulling, without the pain.</strong> Base pulls were the first time sink. In <code class="language-text">rules_img</code>, the repository rule fetches only the manifest and config JSON files at build time. Just enough metadata to know what layers exist and their digests. The actual layer blobs are never downloaded during the build<sup id="fnref-5"><a href="#fn-5" class="footnote-ref">5</a></sup>. They wait until the run phase when you <code class="language-text">bazel run</code> a push or load target. CI becomes predictable, and Remote Execution doesn’t spend its morning downloading CUDA for the fourth time this week. Less data moves during builds, and the cache behaves like a cache.</p>
<p><strong>Stop hauling bytes uphill.</strong> The next fix addressed the torrent of developer-to-remote uploads. We generate metadata-only providers wherever possible. The heavy blobs live in content-addressable storage (CAS) and stream later to whoever needs them, whether that’s a registry or a local daemon. Your workstation stops being a relay, cold starts are faster, and incremental builds are a breeze.</p>
<p><strong>Let manifests stay tiny.</strong> Manifest assembly had been oddly heavyweight. We reshaped the graph so each layer is built in a single action that computes both the blob and the metadata that describes it<sup id="fnref-6"><a href="#fn-6" class="footnote-ref">6</a></sup>. The layer blob stays in Bazel’s CAS, while only a small JSON descriptor (digest, size, media type) flows through the build graph. Downstream actions consume only this metadata during the build phase, so they schedule quickly, cache well, and avoid pulling gigabytes across executors. The manifests remain correct, and the path to them is light. The actual blob bytes only move later during <code class="language-text">bazel run</code> when you push or load.</p>
<p><strong>Push without the round trip.</strong> Pushing used to mean downloading all layers to a local tool and then sending them back up again. With <code class="language-text">rules_img</code>, we defer all blob transfers to the run phase (<code class="language-text">bazel run //:push</code>). The build phase only produces a lightweight push specification: a JSON file listing what needs pushing. When you run the pusher, it first asks the registry what blobs it already has, then streams only the missing ones directly from CAS. In environments where your registry speaks the same CAS protocol, the push is close to zero‑copy. For very large monorepos, you can even emit pushes as a side effect of Build Event Service uploads. The principle is simple. Build time produces metadata, run time moves bytes, and nothing passes through your workstation unnecessarily. See the <a href="https://github.com/bazel-contrib/rules_img/blob/main/docs/push-strategies.md">push strategies documentation</a> for other configurations including direct CAS-to-registry transfers.</p>
<p><strong>Loading should be incremental.</strong> <code class="language-text">docker load</code> treats every import like a blank slate. When containerd is available, <code class="language-text">rules_img</code> talks to its content store and streams only what is missing<sup id="fnref-7"><a href="#fn-7" class="footnote-ref">7</a></sup>. It can also load a single platform from a multi‑platform image, which keeps feedback loops tight<sup id="fnref-8"><a href="#fn-8" class="footnote-ref">8</a></sup>. If containerd isn’t available, we fall back to <code class="language-text">docker load</code> and tell you what you’re giving up.</p>
<p><strong>Extra touches that add up.</strong> Performance rarely comes from one trick alone. We use hardlink-based deduplication inside layers so identical files don’t bloat your tars. We support <a href="https://github.com/containerd/stargz-snapshotter/blob/main/docs/estargz.md">eStargz</a> to make layers seekable and quick to start with the stargz snapshotter.</p>
<p><strong>Quick start.</strong> If you want to try it, here is a minimal setup:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># MODULE.bazel</span>
bazel_dep<span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"rules_img"</span><span class="token punctuation">,</span> version <span class="token operator">=</span> <span class="token string">"&lt;version>"</span><span class="token punctuation">)</span>

pull <span class="token operator">=</span> use_repo_rule<span class="token punctuation">(</span><span class="token string">"@rules_img//img:pull.bzl"</span><span class="token punctuation">,</span> <span class="token string">"pull"</span><span class="token punctuation">)</span>

<span class="token comment"># Pulls manifest+config only (no layer blobs yet)</span>
pull<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"ubuntu"</span><span class="token punctuation">,</span>
    registry <span class="token operator">=</span> <span class="token string">"index.docker.io"</span><span class="token punctuation">,</span>
    repository <span class="token operator">=</span> <span class="token string">"library/ubuntu"</span><span class="token punctuation">,</span>
    tag <span class="token operator">=</span> <span class="token string">"24.04"</span><span class="token punctuation">,</span>
    digest <span class="token operator">=</span> <span class="token string">"sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># BUILD.bazel</span>
load<span class="token punctuation">(</span><span class="token string">"@rules_img//img:layer.bzl"</span><span class="token punctuation">,</span> <span class="token string">"image_layer"</span><span class="token punctuation">)</span>
load<span class="token punctuation">(</span><span class="token string">"@rules_img//img:image.bzl"</span><span class="token punctuation">,</span> <span class="token string">"image_manifest"</span><span class="token punctuation">)</span>

image_layer<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"app_layer"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"/app/bin/server"</span><span class="token punctuation">:</span> <span class="token string">"//cmd/server"</span><span class="token punctuation">,</span>
        <span class="token string">"/app/config"</span><span class="token punctuation">:</span> <span class="token string">"//configs:prod"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    compress <span class="token operator">=</span> <span class="token string">"zstd"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

image_manifest<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"app_image"</span><span class="token punctuation">,</span>
    base <span class="token operator">=</span> <span class="token string">"@ubuntu"</span><span class="token punctuation">,</span>
    layers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">":app_layer"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>Optional <code class="language-text">.bazelrc</code> speed dials if you like the metadata‑first defaults:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">common --@rules_img//img/settings:compress=zstd
common --@rules_img//img/settings:estargz=enabled
common --@rules_img//img/settings:push_strategy=lazy
# Or: cas_registry / bes (see docs for setup)</code></pre></div>
<h2 id="conclusion-container-images-that-feel-native-to-bazel" style="position:relative;"><a href="#conclusion-container-images-that-feel-native-to-bazel" aria-label="conclusion container images that feel native to bazel permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion: container images that feel native to Bazel</h2>
<p>The performance gains are real. Pulling large base images on fresh machines takes seconds instead of minutes. Loading into Docker takes milliseconds for incremental updates instead of reloading the full image, which could waste 1–5 minutes in the workflows we examined. Manifest assembly actions run dramatically faster, especially on RBE systems that fetch inputs eagerly<sup id="fnref-9"><a href="#fn-9" class="footnote-ref">9</a></sup>. Building push targets no longer destroys the benefits of Build without the Bytes. Where other rulesets might download gigabytes to your machine, <code class="language-text">rules_img</code> downloads only a few kilobytes of metadata, saving many gigabytes in transfers and minutes per push. A comprehensive benchmark would warrant its own blog post given the wide matrix of possible configurations, RBE backends, image sizes, and network conditions.</p>
<p>Our aim with <code class="language-text">rules_img</code> is straightforward: make Bazel feel native for container images, with no unnecessary bytes and no unnecessary waits. By treating images as metadata with on-demand bytes, we get faster CI, quieter laptops, and a build graph that scales without drama. Try it, tell us what flies, and tell us what still hurts. There’s more to tune, and we intend to keep tuning.</p>
<p><strong>Get started with rules_img: <a href="https://github.com/bazel-contrib/rules_img">github.com/bazel-contrib/rules_img</a></strong></p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">Bazel actions only access explicitly declared inputs within the execroot, whilst <code class="language-text">docker build</code> mounts all base image layers into the build environment. This means Bazel builds layers independently of base layers, though specific toolchains (like C++) can use workarounds such as <code class="language-text">--sysroot</code> to simulate filesystem mounting.<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">Building without mounting base layers risks creating binaries incompatible with the target image, as there’s no way to execute code atop existing layers during the build. The <a href="https://github.com/GoogleContainerTools/container-structure-test">container-structure-test</a> tool addresses this by enabling tests on the final image, including running commands in a Docker daemon with assertions.<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-3"><code class="language-text">rules_oci</code> is a good ruleset for building container images in Bazel, and I am a happy user most of the time. However, I quickly noticed that it is only optimized for local execution. I want to stress that most of the issues I’m describing here only matter for the remote execution case.<a href="#fnref-3" class="footnote-backref">↩</a></li>
<li id="fn-4">Repository rules downloading image blobs are inefficient in stateless CI environments where caches aren’t preserved between runs. Whilst CI can be configured to preserve these directories, <code class="language-text">rules_img</code> avoids the problem entirely by fetching only metadata.<a href="#fnref-4" class="footnote-backref">↩</a></li>
<li id="fn-5">This is configurable. If you really need access to base image layers at build time (for instance, to run a container structure test), you can set the <code class="language-text">layer_handling</code> attribute accordingly (<a href="https://github.com/bazel-contrib/rules_img/blob/main/docs/pull.md#pull">docs</a>).<a href="#fnref-5" class="footnote-backref">↩</a></li>
<li id="fn-6">In <code class="language-text">rules_oci</code>, manifest assembly receives entire layer tar files as inputs, transferring multi-gigabyte blobs with remote execution. <code class="language-text">rules_img</code> instead generates small metadata files (containing digests and diff IDs) when writing layers, passing only this fixed-size metadata to downstream actions. Layer contents enter the build graph once, remain in CAS, while tiny metadata flows through subsequent actions.<a href="#fnref-6" class="footnote-backref">↩</a></li>
<li id="fn-7"><code class="language-text">docker load</code> requires a tar file with all layers, even those previously loaded. Whilst hacks exist to skip lower layers by abusing chain IDs, <code class="language-text">rules_oci</code> doesn’t support this. <code class="language-text">rules_img</code> interfaces directly with containerd’s content-addressable store to check blob existence and load only missing layers. Docker will soon expose its own content store via the socket (<a href="https://github.com/moby/moby/issues/44369">moby/moby#44369</a>), making this approach more widely accessible.<a href="#fnref-7" class="footnote-backref">↩</a></li>
<li id="fn-8">Platform filtering is not yet implemented for the containerd backend (<a href="https://github.com/bazel-contrib/rules_img/issues/107">rules_img#107</a>). The <code class="language-text">docker load</code> fallback path does support platform filtering.<a href="#fnref-8" class="footnote-backref">↩</a></li>
<li id="fn-9">Some RBE systems can make action inputs available lazily on-demand, while others fetch all declared inputs eagerly before the action runs. For the latter, avoiding multi-gigabyte layer inputs makes a dramatic difference in action scheduling and execution time.<a href="#fnref-9" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-12-18-rules_img/</link><guid isPermaLink="false">https://tweag.io/blog/2025-12-18-rules_img/</guid><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[In memoriam: Alexander Esgen]]></title><description><![CDATA[<p>One of the best things about Tweag are the people. A collegiate spirit
where there’s a lot of trust and friendship amongst everyone. As such,
when a colleague passes away, this is keenly felt by us all.</p>
<p>This blog post is a departure from our normal content to remember our
friend and colleague Alexander Esgen, who passed away on 1st November at
the age of 26.</p>
<p>Alex joined Tweag as an intern in the summer of 2021, working on Ormolu,
our Haskell source code formatter. His talent was immediately apparent;
he approached problems with mathematical rigour and practical insight,
drawing on his background in category theory. He became a full-time
engineer that autumn and, more recently, took on the role of team lead
for the Peras project.</p>
<p>Alex made significant contributions to the Haskell ecosystem,
particularly in WebAssembly support and functional programming tooling.
His work was marked by exceptional thoroughness and attention to detail.
Beyond his technical excellence, he was a natural mentor with a gift for
explaining complex ideas patiently and clearly, always willing to help
colleagues no matter how many times they asked.</p>
<p>What we remember most, however, was his character. Alex had a calm, warm
presence that made people feel heard and valued. He was genuinely
interested in others and their work and had a way of encouraging those
around him. Colleagues consistently describe him as humble, kind and
deeply passionate about his craft; someone who truly embodied the spirit
of collaboration and mutual support that defines our company.</p>
<p>His loss leaves a void that cannot be filled. Our thoughts are with his
family, friends and all who knew him.</p>]]></description><link>https://tweag.io/blog/2025-12-16-in-memoriam-amesgen/</link><guid isPermaLink="false">https://tweag.io/blog/2025-12-16-in-memoriam-amesgen/</guid><pubDate>Tue, 16 Dec 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[The anatomy of a dependency graph]]></title><description><![CDATA[<p>This is the third in a series of three companion blog posts about dependency graphs.
These blog posts explore the key terminology, graph theory concepts,
and the challenges of managing large graphs and their underlying complexity.</p>
<ol>
<li><a href="../2025-09-04-introduction-to-dependency-graph"><em>Introduction to the dependency graph</em></a></li>
<li><a href="../2025-09-18-managing-dependency-graph"><em>Managing dependency graph in a large codebase</em></a></li>
<li><em>The anatomy of a dependency graph</em></li>
</ol>
<hr>
<p>In the <a href="https://www.tweag.io/blog/2025-09-18-managing-dependency-graph/">previous post</a>,
we took a closer look at some of the issues working in a large codebase
in the context of the dependency graph.
In this post, we are about to explore some concepts related to scale and scope of the dependency graph
to understand its granularity and what really impacts your builds.</p>
<h2 id="dependency-graph-detail" style="position:relative;"><a href="#dependency-graph-detail" aria-label="dependency graph detail permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dependency graph detail</h2>
<p>When working on the source code, you likely think of dependencies in the graph as individual modules
that import code from each other.
When drawing a project architecture diagram, however, the individual files are likely to be grouped in packages
to hide the individual files that a package consists of, primarily for brevity.
Likewise, in build systems such as Bazel, you would often have one “node” in the dependency graph per directory.
Of course, it wouldn’t be totally unreasonable to have a few of “nodes”
that would represent a couple of packages in the same directory on disk.
You could, for instance, store performance and chaos tests in the same directory,
but have them modeled as individual units since they might have a different set of dependencies.</p>
<p>So while both <code class="language-text">packageA</code> and <code class="language-text">packageB</code> in the graph below depend on the package <code class="language-text">shared</code> (solid lines),
we can see that individually, only <code class="language-text">testsA.ts</code> depends on <code class="language-text">service.ts</code>
and only <code class="language-text">testB.ts</code> depends on <code class="language-text">cluster.ts</code> (dotted lines).</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/2972056e98817e392f730b0ed9c23030/d93d9/graph1.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 49.32432432432432%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABrUlEQVQoz22R6Y7bMAyE/f6P1v4rsN2NY+fylcSJ4zOORYlfIWU3aBclMBClGY0oMlJVPHxYe2VZSsxyxiwnjKmwtsTaCid7xj5mmvY4VwZu8ZqAY9CJlET/GErO7daS5T2Hfcc4nIEVkIRVzBsifh8Hrqzu7HcN9blHNcW5mIi/wtoL83yiaUqapsCYM6o5zhUBqiWq/nzPMGSMY83QH7lPJ5zLsDYjcu6Bc+MLMGHMjcejQd3AsnSIDFj7BMzMs+dvqA6oTr4UVF1AJLIL37A2ZRrfKYsf9P0vRNY4eWPofjLPK9RtuN8/qKovPsZa34Y0GH1FZF3K0NfkhWG1qtltWx7zFvA98eJ3urYny2bW8ZX80GKWFNgEjUiCqg1mfhaRyBYxO8axoG03mCXH2SQMwrkEkd8YkzP0GV27Dby6NehTI5J+N9x9TjFhvp+oLy1VdWUaa1TXiHx8VuMvH7he+8C3t1uY7H8MN6jGwJr7VFHXF07HY8hVV6FC1QRljVn2NNeG47Gia8+hUt/rb4ZFeMWYTeiHtUnonc+fZ0888/TF+/V57/Aaijf8Ayc9AJuuP5aFAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/2972056e98817e392f730b0ed9c23030/fcda8/graph1.png" srcset="/static/2972056e98817e392f730b0ed9c23030/12f09/graph1.png 148w,
/static/2972056e98817e392f730b0ed9c23030/e4a3f/graph1.png 295w,
/static/2972056e98817e392f730b0ed9c23030/fcda8/graph1.png 590w,
/static/2972056e98817e392f730b0ed9c23030/efc66/graph1.png 885w,
/static/2972056e98817e392f730b0ed9c23030/c83ae/graph1.png 1180w,
/static/2972056e98817e392f730b0ed9c23030/d93d9/graph1.png 1243w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>If operating on packages level, the build metadata stored on disk in files
would actually lead to construction of this dependency graph:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 318px; ">
      <a class="gatsby-resp-image-link" href="/static/5379a2c0a29478fe4879d93e1bba163f/cd2d9/graph2.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 54.05405405405405%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlklEQVQoz32T207jMBCG8/6vhbgoZQXqxS4tC02bpElzaAhxnJPtb2WHlG4RWJpM5Pnn9xw9ro4xxmmtDW2jJ5Ef/1IjG03f6TN2xs/H+47QOh1CxSHShMFAFI5EgRVFno4WiYX+SPj5omHoNGUJcdKzWPxmuXxitfLJc3h/c2BmrktS7zqyWXdyZO83ZKkhiUcnx0SRxIpDWH9gv/p5c1SX9VBKkaUZUZgy9JZc07W2dgZRD+x3EUIImwjX/l9qqLWiqt4waH46bSsRop7z+0zZsvatppPK6apsKE/CRdY209058tHQSkXbjAydocgqRN27yK3/0Gs8+znGijyDU8F/Oj1ONqMnQvGuSA6KIseJbZDFpqkmiTWnXOHZblpjEAiWyzWLuz/c3q64/7XhmCrXFH1BWBTw/JyyvF+77t/cPPLw8Nfdl4XCc/MWKIJ9z+tLjb+VbJ5KdlvpZvCwH8+EdaWIAs3Ol/ivDdsXwWZdOl97nx9HvPNGSEPX2o7aobbauK2wtrmG42iQQjubw7YXWDFt0D/p2lSs912dGQAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/5379a2c0a29478fe4879d93e1bba163f/cd2d9/graph2.png" srcset="/static/5379a2c0a29478fe4879d93e1bba163f/12f09/graph2.png 148w,
/static/5379a2c0a29478fe4879d93e1bba163f/e4a3f/graph2.png 295w,
/static/5379a2c0a29478fe4879d93e1bba163f/cd2d9/graph2.png 318w" sizes="(max-width: 318px) 100vw, 318px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>This means that whenever <em>any</em> file in the <code class="language-text">shared</code> directory would change,
tests within both packages (<code class="language-text">A</code> and <code class="language-text">B</code>) would be considered impacted.</p>
<p>A build system that relies on dependency inference (such as Pants) is able to track dependencies
across each file individually with the powerful concept of <a href="https://www.pantsbuild.org/stable/docs/using-pants/key-concepts/targets-and-build-files#target-generation">target generators</a>.
This means that every file in your project may be an individual node in the dependency graph with all the dependencies
mapped out by statically analyzing the source code of the files
and augmenting the build metadata manually where the inference falls short.
In practice, this means that even though you can organize your code and specify dependencies at the broader package level
— mirroring how you are likely to think about the project architecture and deliverable artifacts —
Pants still provides the benefit of fine-grained recompilation avoidance.
This reduces unnecessary rebuilds and test runs, shortens feedback cycles,
and encourages better dependency hygiene — all without forcing you to manage dependencies at a per-file level manually.
It might also provide better incentive for engineers to care more about dependencies in files individually
which can be harder to achieve if a file is part of a build target with lots of other files with many dependencies.</p>
<p>Intuitively, one may want to go with as fine-grained dependency graph as possible hoping to avoid unnecessary build actions.
For big repositories, however, the granularity of build targets often doesn’t matter as much as it does for smaller projects.
This is because doing distributed builds across multiple machines would immediately require presence of a shared cache
to be able to track results of any previously executed build actions that could be reused (such as compilation or linking).
It is not immediately obvious, though, what operation would complete faster —
rebuilding an entire directory (using packages as nodes) or querying the cache on the network for each
individual file (using files as nodes) with the ambition to invoke only truly required build actions.</p>
<h2 id="dependency-graph-scope" style="position:relative;"><a href="#dependency-graph-scope" aria-label="dependency graph scope permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dependency graph scope</h2>
<p>With source code modules or packages being nodes,
what are other types of dependencies that also contribute to the dependency graph?
Almost any project relies on third-party libraries
which might be available in version control
or are to be downloaded from some kind of binary repository at build time
when the dependency graph needs to be constructed.
The same applies to any static resources and data your applications might need
to be built or to run.</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 509px; ">
      <a class="gatsby-resp-image-link" href="/static/a3f1dc2796896be6f588fa6af977b8c6/71554/graph3.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 40.54054054054054%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABRElEQVQoz2WQy04cMRBF++9RNhGLLLLMD2STfST4BgRIDErCMDQ9I+iH6bbbj6qD7J4hI6Yky+X7qFtyNTvBWbCTEoOiqrjpgEl5Zzzzs6Oc3KeoiGjRHPDsq9oX5f6+4++fETuBJKV+CqxWrzzXcW9SHv5Zrq9rrq42rNeuBMYgRXNz03B398K2EarxTXnaGHbNTPCUjbrXxOZxoG8FTcpsMyY0z+MH7mdBEpgBHtcdu61l6JUKIKWItRbvPSKSIZyzSHYAyh6zFtWlL18RA/PsijarclWZWAQQQsA5V06METO80f3uMD8N7aolSCCGiHPzxwIpLaGHkOrQ7OeWSjHh8dSXNcOXAfvV0nxv6LueEGPhj2vxn2yo/4n98N3FjvasZTwfab412MmeDPnsrz4nHd9+8vS/eswPg7k1CyenQ47rHb+fbGexVIRXAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/a3f1dc2796896be6f588fa6af977b8c6/71554/graph3.png" srcset="/static/a3f1dc2796896be6f588fa6af977b8c6/12f09/graph3.png 148w,
/static/a3f1dc2796896be6f588fa6af977b8c6/e4a3f/graph3.png 295w,
/static/a3f1dc2796896be6f588fa6af977b8c6/71554/graph3.png 509w" sizes="(max-width: 509px) 100vw, 509px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>These are called <em>explicit</em> dependencies because users declare them in the build metadata.
In some build systems, such as Bazel, only a subset of dependencies, typically the ones you explicitly declare,
are fully tracked, while others may be left to the surrounding system.
These become <em>implicit</em> dependencies, because the build system relies on their presence without explicitly encoding them.
For example, your application might link to <code class="language-text">libcurl</code>, which in turn depends on <code class="language-text">OpenSSL</code>.
In some environments, <code class="language-text">OpenSSL</code> is provided by the system rather than the build metadata, making it an <em>implicit</em> dependency.
Note that in other systems, like Nix, the complete transitive dependency graph is fully encoded,
so this distinction does not always apply.</p>
<p>Apart from those, there are implicit dependencies which bind your code to build-time dependencies
such as a compiler.
In addition to saying that your source code (say, a C++ application) depends on a compiler,
it also depends on the compiler’s runtime libraries such as <code class="language-text">libstdc++</code> or <code class="language-text">libc++</code>, an assembler,
and a linker.
Depending on the build system used and how your build workflow is configured,
the connection between build targets and the compilers might be recorded.
For example, in Bazel, it is documented that when processing build metadata for C++ sources, a dependency
between each source unit and a C++ compiler is created implicitly.</p>
<p>Any configuration such as options passed to the compiler (<code class="language-text">copts</code> among other inputs) matters, too,
just as any environment variables you might have set in the build environment.
You wouldn’t probably think of compiler flags as something your application’s logic might depend on,
but some optimizations like inlining can make the performance of your application worse in certain situations.
Take a look at the <a href="https://abseil.io/resources/swe-book/html/ch01.html#example_compiler_upgrade">Compiler Upgrade use case</a>
from the <a href="https://abseil.io/resources/swe-book">Software Engineering at Google</a> book to appreciate
how critical compilers are in a large codebase context.</p>
<p>Transitively, the compilation would also typically depend on system headers and libraries
such as <code class="language-text">libc</code> (e.g., <code class="language-text">glibc</code>), a C library used for system-level functionality
(unless you are able to link against <a href="https://musl.libc.org/">musl libc</a>, which is designed for static linking).
For instance, a binary compiled with <code class="language-text">glibc 2.17</code> might not run on a system with <code class="language-text">glibc 2.12</code>
because the system may lack the required symbols.</p>
<p>How far can one go? In addition to the build dependencies —
everything that might be needed to <em>build</em> your application — one could argue
that libraries your compiled application needs to run might also be part of the dependency graph
as ultimately this is what’s necessary for this application to be useful to the end user.
For example, you can run <code class="language-text">ldd &lt;yourbinary></code> in Linux (or <code class="language-text">otool -L</code> on a macOS device)
to take a peek at shared object dependencies your binary might have.</p>
<p>Taking it to the extreme, if your application accesses Linux kernel features
such as <a href="https://docs.kernel.org/driver-api/ioctl.html">ioctl</a> in unsupported or undocumented ways,
the kernel version might matter, too.
Driver interfaces can change between kernel versions, breaking user-space tools,
so the operating system version along with the underlying kernel version
are technically part of the dependency graph, too:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/88ca269ff5c496bf674b3693976e58ff/64a58/graph4.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABBUlEQVQoz42RXU7DMBCEc/9X7oPEPXihQBKVxk4Tx+u/9YecQtUKCRhrtbZmdr0ed9yhUit8HANvrwvWxHu2kX+gy7nSQrWSE3if2baC+Mq6ZEIopFQppX5d+Ht0x1GYToGqMPSOw+HMsiRSgv595fBiGfqNFPkXOi3K5hJmEpazsDnh47hiJo81gvjMbCPm5BFRYqi71hghBiWIYm3AGs9yTnStq3jdp7tMpkynSN83DxMlwzRHhskhUncrmnYc3L53a2YYHOPoMCbR3RqtqpSSUS17TjESNRKeBf+0ESUQU6TkdNW03Ly9Pvn7924bX87Q7Mhk5scZ+2Dxs0fb0gv/s6byCctSIxpIEsniAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/88ca269ff5c496bf674b3693976e58ff/fcda8/graph4.png" srcset="/static/88ca269ff5c496bf674b3693976e58ff/12f09/graph4.png 148w,
/static/88ca269ff5c496bf674b3693976e58ff/e4a3f/graph4.png 295w,
/static/88ca269ff5c496bf674b3693976e58ff/fcda8/graph4.png 590w,
/static/88ca269ff5c496bf674b3693976e58ff/efc66/graph4.png 885w,
/static/88ca269ff5c496bf674b3693976e58ff/c83ae/graph4.png 1180w,
/static/88ca269ff5c496bf674b3693976e58ff/64a58/graph4.png 1577w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>So far, we have looked at the dependency graph in one dimension only.
However, it’s not uncommon to have conditional dependencies
particularly when doing cross-compilation or producing artifacts for multiple environments.
For instance, the backend system of the <a href="https://matplotlib.org/">matplotlib</a> visualization library
is chosen based on the platform and available GUI libraries,
which affects what transitive dependencies are going to be pulled when being installed.
Imagine building your application for various CPU architectures (x86_64 or ARM)
or a package for different operating systems (Linux or Windows)
and the graph complexity explodes.</p>
<p>A typical approach to “freeze” the build environment and treat it as an immutable set of instructions
is to provide a “golden” Docker image where you can find the build dependencies
that are known to produce the correct artifacts.
They are baked in and then all the build actions are taking place within containers spun from that image.
This is a lot better than relying on the host, but this solution has a number of drawbacks
as it forces you to treat all your dependencies as a single blob — an image.
Making changes and experimenting is not encouraged as, from the dependency graph perspective,
every time you do so would rebuild the image, which doesn’t end up being the same bit-for-bit,
so you need to “re-build” your whole project as it’s not known what exactly has changed
and what part of your project depends on it.</p>
<p>To focus on build-time dependencies only, even documenting (not mentioning properly declaring!)
all the inputs necessary to build your application is not a trivial task.
Using tools such as <code class="language-text">nix</code> and <code class="language-text">NixOS</code> to drive the build workflow is appealing,
as it makes it possible to describe practically all inputs to your build,
though, admittedly, this can require a significant investment from your engineering organization (but we can <a href="https://nix-bazel.build/">help you with it</a>).</p>
<p>The least one can do is to be aware of the implicit dependencies
even if properly describing them in build instructions is not immediately possible.
No matter what approach is taken, any implicit relationship between your code and some other dependency
that can be expressed in build metadata gets you closer to a fully declared state even if achieving it
might be impossible in practice.</p>
<hr>
<p>In the next post, we’ll explore some graph querying techniques
that can help with related test selection, code review strategy, and more.</p>]]></description><link>https://tweag.io/blog/2025-12-04-the-anatomy-of-a-dependency-graph/</link><guid isPermaLink="false">https://tweag.io/blog/2025-12-04-the-anatomy-of-a-dependency-graph/</guid><pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Shrinking while linking]]></title><description><![CDATA[<p>If you’re anxious about the size of your <em>binary</em>, there’s a lot of useful
advice on the internet to help you reduce it. In my experience, though,
people are reticent to discuss their static libraries.
If they’re mentioned at all, you’ll be told not to worry about
their size: dead code will be optimized away when <a href="https://en.wikipedia.org/wiki/Linker_(computing)">linking</a> the final binary, and the
final binary size is what matters.</p>
<p>But that advice didn’t help me, because I wanted to distribute a static library
and the size was causing me problems. Specifically, I had a Rust library<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>
that I wanted to make available to Go developers. Both Rust and Go can interoperate
with C, so I compiled the Rust code into a C-compatible library and made a little
Go wrapper package for it.
Like most pre-compiled C libraries, I can distribute it either as a static
or a dynamic library. Now Go developers are accustomed to static linking, which
produces self-contained binaries that are refreshingly easy to deploy. Bundling
a pre-compiled static library with our Go package allows Go developers to just
<code class="language-text">go get https://github.com/nickel-lang/go-nickel</code> and get to work. Dynamic
libraries, on the other hand, require runtime dependencies, linker paths, and installation instructions.</p>
<p>So I really wanted to go the static route, even if it came with a slight size
penalty. How large of a penalty are we talking about, anyway?</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">❯ ls -sh target/release/
132M libnickel_lang.a
15M  libnickel_lang.so</code></pre></div>
<p>😳 Ok, that’s too much. Even if I were morally satisfied with 132MB of library,
it’s way beyond GitHub’s 50MB file size limit.<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup> (Honestly, even the 15M shared
library seems large to me; we haven’t put much effort into optimizing code size yet.)</p>
<h2 id="the-compilation-process-in-a-nutshell" style="position:relative;"><a href="#the-compilation-process-in-a-nutshell" aria-label="the compilation process in a nutshell permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The compilation process in a nutshell</h2>
<p>Back in the day, your compiler or assembler would turn each source file into
an “object” file containing the compiled code. In order to allow for source files
to call functions defined in other source files, each object file could announce
the list of functions<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup> that it defines, and the list of functions that
it very much hopes someone else will define. Then you’d run the <em>linker</em>, a program
that takes all those object files and mashes them together into a binary,
matching up the hoped-for functions with actual function
definitions or yelling “undefined symbol” if it can’t.
Modern compiled languages tweak this pipeline a little: Rust produces an
object file per crate<sup id="fnref-4"><a href="#fn-4" class="footnote-ref">4</a></sup> instead of one per source file. But the basics
haven’t changed much.</p>
<p>A static library is nothing but a bundle of object files, wrapped
in an ancient and never-quite-standardized <a href="https://en.wikipedia.org/wiki/Ar_(Unix)">archive format</a>. No linker
is involved in the creation of a static library: it will be used
eventually to link the static library into a binary. The unfortunate
consequence is that a static library contains a lot of information
that we don’t want. For a start, it contains all the code of all our
dependencies even if much of that code is unused. If you compiled
your code with support for link-time optimization (LTO), it contains
<em>another</em> copy (in the form of <a href="https://llvm.org">LLVM</a> bitcode — more on that later)
of all our code and the code of all our dependencies. And then because it has
so much redundant code, it contains a bunch of metadata (section
headers) to make it easier for the linker to remove that redundant
code later. The underlying reason for all this is that extra
fluff in object files isn’t usually considered a problem:
it’s removed when linking the final binary (or shared library),
and that’s all that most people care about.</p>
<h2 id="re-linking-with-ld" style="position:relative;"><a href="#re-linking-with-ld" aria-label="re linking with ld permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Re-linking with <code class="language-text">ld</code></h2>
<p>I wrote above that a linker takes a bunch of object files and mashes
them together into a binary. Like everything in the previous section,
this was an oversimplification: if you pass the <code class="language-text">--relocatable</code> flag
to your linker, it will mash your object files together but write out
the result as an object file instead of a binary.
If you also pass the <code class="language-text">--gc-sections</code> flag, it will remove
unused code while doing so.</p>
<p>This gives us a first strategy for shrinking a static archive:</p>
<ul>
<li>unpack the archive, retrieving all the object files</li>
<li>link them all together into a single large object, removing unused code.
In this step we need to tell the linker which code is <em>used</em>, and then
it will remove anything that can’t be reached from the used code.</li>
<li>pack that single object back into a static library</li>
</ul>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token comment"># Unpack the archive</span>
ar x libnickel_lang.a

<span class="token comment"># Link all the objects together, keeping on the parts reachable from our</span>
<span class="token comment"># public API (about 50 functions worth)</span>
ld <span class="token parameter variable">--relocatable</span> --gc-sections <span class="token parameter variable">-o</span> merged.o *.o <span class="token parameter variable">-u</span> nickel_context_alloc <span class="token parameter variable">-u</span> nickel_context_free <span class="token punctuation">..</span>.

<span class="token comment"># Pack it back up</span>
ar rcs libsmaller_nickel_lang.a merged.o</code></pre></div>
<p>This helps a bit: the archive size went from 132MB to 107MB. But there’s
clearly still room for improvement.</p>
<p>Examining our merged object file with the <code class="language-text">size</code> command, the largest
section by far — weighing in at 84MB — is <code class="language-text">.llvmbc</code>. Remember I wrote
that we’d come back the LLVM bitcode? Well, when you compile something with
LLVM (and the Rust compiler uses LLVM), it converts the original source
code into an intermediate representation, then it converts the
intermediate representation into machine code, and then<sup id="fnref-5"><a href="#fn-5" class="footnote-ref">5</a></sup> it writes <em>both
the intermediate representation and the machine code</em> into an object file.
It keeps the intermediate representation around in case it has useful
information for further optimization during linking time. Even if that
information is useful, it isn’t 84MB useful.<sup id="fnref-6"><a href="#fn-6" class="footnote-ref">6</a></sup> Out it goes:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">objcopy --remove-section .llvmbc merged.o without_llvmbc.o</code></pre></div>
<p>The next biggest sections contain debug information. Those <em>might</em> be
useful, but we’ll remove them for now just to see how small we can get.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">strip --strip-unneeded without_llvmbc.o <span class="token parameter variable">-o</span> stripped.o</code></pre></div>
<p>At this point there aren’t any giant sections left. But there are more
than 48,000 small sections. It turns out that the Rust compiler puts
every single tiny function into its own little section within the object
file. It does this to help the linker remove unused code: remember the
<code class="language-text">--gc-sections</code> argument to <code class="language-text">ld</code>? It removes unused <em>sections</em>, and so if the
sections are small then unused code can be removed precisely. But
we’ve already removed unused code, and each of those 48,000 section
headers is taking up space.</p>
<p>To do this, we write a linker script that tells <code class="language-text">ld</code> to merge sections together.
The meaning of the various sections isn’t important here: the point is that
we’re merging sections with names like <code class="language-text">.text._ZN11nickel_lang4Expr7to_json17h</code>
and <code class="language-text">.text._ZN11nickel_lang4Expr7to_yaml17h</code> into a single big <code class="language-text">.text</code> section.</p>
<div class="gatsby-highlight" data-language="ld"><pre class="language-ld"><code class="language-ld"><span class="token comment">/* merge.ld */</span>
SECTIONS
<span class="token punctuation">{</span>
  <span class="token section keyword">.text</span> <span class="token operator">:</span>
  <span class="token punctuation">{</span>
    <span class="token operator">*</span><span class="token punctuation">(</span><span class="token section keyword">.text</span> <span class="token section keyword">.text</span>.<span class="token operator">*</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token section keyword">.rodata</span> <span class="token operator">:</span>
  <span class="token punctuation">{</span>
    <span class="token operator">*</span><span class="token punctuation">(</span><span class="token section keyword">.rodata</span> <span class="token section keyword">.rodata</span>.<span class="token operator">*</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token comment">/* and a couple more */</span>
<span class="token punctuation">}</span></code></pre></div>
<p>And we use it like this:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">ld <span class="token parameter variable">--relocatable</span> <span class="token parameter variable">--script</span> merge.ld stripped.o <span class="token parameter variable">-o</span> without_tiny_sections.o</code></pre></div>
<p>Let’s take a look back at what we did to our archive, and how much it helped:</p>
<table>
<thead>
<tr>
<th></th>
<th>Size</th>
</tr>
</thead>
<tbody>
<tr>
<td>original</td>
<td>132MB</td>
</tr>
<tr>
<td>linked with <code class="language-text">--gc-sections</code></td>
<td>107MB</td>
</tr>
<tr>
<td>removed <code class="language-text">.llvmbc</code></td>
<td>33MB</td>
</tr>
<tr>
<td>stripped</td>
<td>25MB</td>
</tr>
<tr>
<td>merged sections</td>
<td>19MB</td>
</tr>
</tbody>
</table>
<p>It’s probably possible to continue, but this is already a big improvement. We got rid of more than 85%
of our original size!</p>
<p>We did lose something in the last two steps, though. Stripping the debug
information might make backtraces less useful, and merging the sections removes
the ability for future linking steps to remove unused code from the final
binaries.
In our case, our library has a relatively small and coarse API;
I checked that as soon as you use any non-trivial function, less than 150KB of
dead code remains.
But you’ll need to decide for yourself whether these costs are worth
the size reduction.</p>
<h2 id="more-portability-with-llvm-bitcode" style="position:relative;"><a href="#more-portability-with-llvm-bitcode" aria-label="more portability with llvm bitcode permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>More portability with LLVM bitcode</h2>
<p>I was reasonably pleased with the outcome of the previous section until I tried to port
it to MacOS, because it turns out that the MacOS linker doesn’t support
<code class="language-text">--gc-sections</code> (it has a <code class="language-text">-dead_strip</code> option, but it’s incompatible with <code class="language-text">--relocatable</code>
because apparently no one cares about code size unless they’re building a binary).
After drafting this post but before publishing it, I found
<a href="https://www.amyspark.me/blog/posts/2024/01/10/stripping-rust-libraries.html">this</a>
nice post on shrinking MacOS static libraries using the toolchain from XCode.
I’m no MacOS expert so I’m probably using it wrong, but I only got down to
about 25MB (after stripping) using those tools. (If you know how to do better, let me know!)</p>
<p>But there is another way! Remember that we had two copies of all our code: the
LLVM intermediate representation and the machine code.<sup id="fnref-7"><a href="#fn-7" class="footnote-ref">7</a></sup> Last time, we chucked out
the intermediate representation and used the machine code. But since I don’t
know how to massage the machine code on MacOS, we can work with the intermediate
representation instead.</p>
<p>The first step is to extract the LLVM bitcode and throw out the rest.
(The section name on MacOS is <code class="language-text">__LLVM,__bitcode</code> instead of <code class="language-text">.llvmbc</code> like it was on Linux.)</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token keyword">for</span> <span class="token for-or-select variable">obj_file</span> <span class="token keyword">in</span> ./*.o<span class="token punctuation">;</span> <span class="token keyword">do</span>
  llvm-objcopy --dump-section<span class="token operator">=</span>__LLVM,__bitcode<span class="token operator">=</span><span class="token string">"<span class="token variable">$obj_file</span>.bc"</span> <span class="token string">"<span class="token variable">$obj_file</span>"</span>
<span class="token keyword">done</span></code></pre></div>
<p>Then we combine all the little bitcode files into one gigantic one:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">llvm-link <span class="token parameter variable">-o</span> merged.bc ./*.bc</code></pre></div>
<p>And we remove the unused code by telling LLVM which functions make up
the public API. We ask it to “internalize” every function that isn’t in
the list, and to remove code that isn’t reachable from a public function
(the “dce” in “globaldce” stands for “dead-code elimination”).</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">opt <span class="token punctuation">\</span>
  --internalize-public-api-list<span class="token operator">=</span>nickel_context_alloc,<span class="token punctuation">..</span>. <span class="token punctuation">\</span>
  <span class="token parameter variable">--passes</span><span class="token operator">=</span><span class="token string">'internalize,globaldce'</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-o</span> small.bc <span class="token punctuation">\</span>
  merged.bc</code></pre></div>
<p>Finally, we recompile the result back into an object file and pop
it into a static library. <code class="language-text">llc</code> turns the LLVM bitcode back into
machine code, so the resulting object file can be consumed by
non-LLVM toolchains.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">llc <span class="token parameter variable">--filetype</span><span class="token operator">=</span>obj --relocation-model<span class="token operator">=</span>pic small.bc <span class="token parameter variable">-o</span> small.o
ar rcs libsmaller_nickel_lang.a small.o</code></pre></div>
<p>The result is a 19MB static library, pretty much the same as the other workflow.
Note that we don’t need the section-merging step here, because we
didn’t ask <code class="language-text">llc</code> to generate a section per function.</p>
<h2 id="dragonfire" style="position:relative;"><a href="#dragonfire" aria-label="dragonfire permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dragonfire</h2>
<p>Soon after drafting this post, I learned about <a href="https://centricular.com/devlog/2025-11/dragonfire/">dragonfire</a>, a recently-released
and awesomely-named tool for shrinking <em>collections</em> of static libs by pulling
out and deduplicating object files. I don’t think this post’s techniques can be
combined with theirs for extra savings, because you can’t both deduplicate and
merge object files (I guess in principle you could deduplicate some and merge
others, if you have very specific needs.) But it’s a great read, and I was gratified
to discover that someone else shared my giant-Rust-static-library concerns.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>We saw two ways to significantly reduce the size of a static library, one using
classic tools like <code class="language-text">ld</code> and <code class="language-text">objcopy</code> and another using LLVM-specific tools.
They both produced similar-sized outputs, but as with everything in life
there are some tradeoffs. The “classic” bintools approach works with both GNU bintools
and LLVM bintools, and it’s significantly faster — a few seconds, compared
to a minute or so — than the LLVM tools,
which need to recompile everything from the intermediate representation to
machine code. Moreover, the bintools approach should work with any static library,
not just one compiled with a LLVM-based toolchain.</p>
<p>On the other hand, the LLVM approach works on MacOS (and Linux, Windows, and probably
others). For this reason alone, this is the way we’ll be building our static
libraries for <a href="https://nickel-lang.org">Nickel</a>.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">Namely, the library API for <a href="https://nickel-lang.org/">Nickel</a>, which
is going to have a stable embedding API real soon now, including bindings for
C and Go!<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">Go expects packages with pre-compiled dependencies to check the compiled
code directly into a git repository.<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-3">technically “symbols”, not “functions”. But for this abbreviated discussion,
the distinction doesn’t matter.<a href="#fnref-3" class="footnote-backref">↩</a></li>
<li id="fn-4">Or not. To improve parallelization, Rust sometimes generates multiple
object files per crate.<a href="#fnref-4" class="footnote-backref">↩</a></li>
<li id="fn-5">if you’ve turned on link-time optimization<a href="#fnref-5" class="footnote-backref">↩</a></li>
<li id="fn-6">Linux distributions that use LTO seem <a href="https://wiki.debian.org/ToolChain/LTO">to</a>
<a href="https://fedoraproject.org/wiki/LTOByDefault#Detailed_Description">agree</a>
that this intermediate representation should be stripped before distributing the
library.<a href="#fnref-6" class="footnote-backref">↩</a></li>
<li id="fn-7">We have the LLVM intermediate representation because we build with LTO.
If you aren’t using LTO then there are probably other ways to get it, like with
Rust’s <a href="https://rustc-dev-guide.rust-lang.org/backend/debugging.html#get-your-hands-on-raw-llvm-input"><code class="language-text">--emit-llvm-ir</code></a>
flag.<a href="#fnref-7" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-11-27-shrinking-static-libs/</link><guid isPermaLink="false">https://tweag.io/blog/2025-11-27-shrinking-static-libs/</guid><pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Migrating to Bazel symbolic macros]]></title><description><![CDATA[<p>In Bazel, there are two types of macros: legacy macros and symbolic macros,
that were introduced in Bazel 8.
Symbolic macros are recommended for code clarity, where possible.
They include enhancements like typed arguments
and the ability to define and limit the visibility of the targets they create.</p>
<p>This post is intended for experienced Bazel engineers
or those tasked with modernizing the build metadata of their codebases.
The following discussion assumes a solid working knowledge of Bazel’s macro system
and build file conventions.
If you are looking to migrate legacy macros
or deepen your understanding of symbolic macros,
you’ll find practical guidance and nuanced pitfalls addressed here.</p>
<hr>
<h2 id="what-are-symbolic-macros" style="position:relative;"><a href="#what-are-symbolic-macros" aria-label="what are symbolic macros permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What are symbolic macros?</h2>
<p>Macros instantiate rules by acting as templates that generate targets.
As such, they are expanded in the <a href="https://bazel.build/extending/concepts#evaluation-model">loading phase</a>,
when Bazel definitions and <code class="language-text">BUILD</code> files are loaded and evaluated.
This is in contrast with build rules that are run later in the analysis phase.
In older Bazel versions, macros were defined exclusively as Starlark functions
(the form that is now called “legacy macros”).
<a href="https://bazel.build/extending/macros">Symbolic macros</a> are an improvement on that idea;
they allow defining a set of attributes similar to those of build rules.</p>
<p>In a <code class="language-text">BUILD</code> file, you invoke a symbolic macro by supplying attribute values as arguments.
Because Bazel is explicitly aware of symbolic macros and their function in the build process,
they can be considered “first-class macros”.
See the <a href="https://docs.google.com/document/d/1aOp2TsS6iM0byBxrGMKgnUnBR2ihYr2RGeYuScvduUg/edit?tab=t.0">Symbolic macros design document</a>
to learn more about the rationale.
Symbolic macros also intend to support <em>lazy evaluation</em>,
a feature that is currently being considered for a future Bazel release.
When that functionality is implemented,
Bazel would defer evaluating a macro until
the targets defined by that macro are actually requested.</p>
<h2 id="conventions-and-restrictions" style="position:relative;"><a href="#conventions-and-restrictions" aria-label="conventions and restrictions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conventions and restrictions</h2>
<p>There is already <a href="https://bazel.build/versions/8.1.0/rules/macro-tutorial">good documentation</a> that explains how to write symbolic macros.
In this section, we are going to take a look at some practical examples of the restrictions that apply to their implementation,
which you can learn more about in the <a href="https://bazel.build/extending/macros#restrictions">Restrictions</a> docs page.</p>
<h3 id="naming" style="position:relative;"><a href="#naming" aria-label="naming permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Naming</h3>
<p>Any targets created by a symbolic macro <em>must</em> either match the macro’s name parameter exactly
or begin with that name followed by a <code class="language-text">_</code> (preferred), <code class="language-text">.</code>, or <code class="language-text">-</code>.
This is different from legacy macros which don’t have naming constraints.</p>
<p>This symbolic macro</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_simple_macro_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">:</span>
    native<span class="token punctuation">.</span>genrule<span class="token punctuation">(</span>
        name <span class="token operator">=</span> <span class="token string">"genrule"</span> <span class="token operator">+</span> name<span class="token punctuation">,</span>
        outs <span class="token operator">=</span> <span class="token punctuation">[</span>name <span class="token operator">+</span> <span class="token string">"_out.data"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"//:file.json"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
simple_macro<span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"tool"</span><span class="token punctuation">)</span></code></pre></div>
<p>would fail when evaluated:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //...
ERROR: in genrule rule //src:genruletool: Target //src:genruletool declared in symbolic macro 'tool'
violates macro naming rules and cannot be built.</code></pre></div>
<p>This means <code class="language-text">simple_macro(name = "tool")</code> may only produce files or targets named <code class="language-text">tool</code> or starting with <code class="language-text">tool_</code>,
<code class="language-text">tool.</code>, or <code class="language-text">tool-</code>.
In this particular macro, <code class="language-text">tool_genrule</code> would work.</p>
<h3 id="access-to-undeclared-resources" style="position:relative;"><a href="#access-to-undeclared-resources" aria-label="access to undeclared resources permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Access to undeclared resources</h3>
<p>Symbolic macros must follow Bazel’s standard visibility rules:
they cannot directly access source files unless those files are passed in as arguments
or are made public by their parent package.
This is different from legacy macros,
whose implementations were effectively inlined into the <code class="language-text">BUILD</code> file where they were called.</p>
<h2 id="attributes" style="position:relative;"><a href="#attributes" aria-label="attributes permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Attributes</h2>
<h3 id="positional-arguments" style="position:relative;"><a href="#positional-arguments" aria-label="positional arguments permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Positional arguments</h3>
<p>In legacy macro invocations, you could have passed the attribute values as positional arguments.
For instance, these are perfectly valid legacy macro calls:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">special_test_legacy</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> tag <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span> <span class="token operator">=</span> name
    kwargs<span class="token punctuation">[</span><span class="token string">"tags"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>tag<span class="token punctuation">]</span> <span class="token keyword">if</span> tag <span class="token keyword">else</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    cc_test<span class="token punctuation">(</span><span class="token operator">**</span>kwargs<span class="token punctuation">)</span>


<span class="token comment"># BUILD.bazel</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"no-tag"</span><span class="token punctuation">)</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"with-tag"</span><span class="token punctuation">,</span> <span class="token string">"manual"</span><span class="token punctuation">)</span></code></pre></div>
<p>With the macro’s name and tags collected as expected:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //test/package:no-tag --output=build
cc_test(
  name = "no-tag",
  tags = [],
  ...
)

$ bazel cquery //test/package:with-tag --output=build
cc_test(
  name = "with-tag",
  tags = ["manual"],
  ...
)</code></pre></div>
<p>You can control how arguments are passed to functions by using an asterisk (<code class="language-text">*</code>)
in the parameter list of a legacy macro, as per the <a href="https://github.com/bazelbuild/starlark/blob/master/spec.md#function-definitions">Starlark language specs</a>.
If you are a seasoned Python developer (Starlark’s syntax is heavily inspired by Python), you might have already guessed
that this asterisk separates positional arguments from keyword-only arguments:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">special_test_legacy</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token operator">*</span><span class="token punctuation">,</span> tag <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span> <span class="token operator">=</span> name
    kwargs<span class="token punctuation">[</span><span class="token string">"tags"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>tag<span class="token punctuation">]</span> <span class="token keyword">if</span> tag <span class="token keyword">else</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    cc_test<span class="token punctuation">(</span><span class="token operator">**</span>kwargs<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"no-tag"</span><span class="token punctuation">)</span>  <span class="token comment"># okay</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"with-tag"</span><span class="token punctuation">,</span> tag<span class="token operator">=</span><span class="token string">"manual"</span><span class="token punctuation">)</span> <span class="token comment"># okay</span>
<span class="token comment"># Error: special_test_legacy() accepts no more than 1 positional argument but got 2</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"with-tag"</span><span class="token punctuation">,</span> <span class="token string">"manual"</span><span class="token punctuation">)</span></code></pre></div>
<p>Positional arguments are not supported in symbolic macros
as attributes must either be declared in the <a href="https://bazel.build/extending/macros#attributes"><code class="language-text">attrs</code></a> dictionary
(which would make it automatically a keyword argument)
or be inherited in which case it should also be provided by name.</p>
<p>Arguably, avoiding positional arguments in macros altogether is helpful
because it eliminates subtle bugs caused by incorrect order of parameters passed
and makes them easier to read and easier to process by tooling such as <a href="https://github.com/bazelbuild/buildtools/blob/main/buildozer/README.md"><code class="language-text">buildozer</code></a>.</p>
<h3 id="default-values" style="position:relative;"><a href="#default-values" aria-label="default values permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Default values</h3>
<p>Legacy macros accepted default values for their parameters
which made it possible to skip passing certain arguments:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">special_test_legacy</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token operator">*</span><span class="token punctuation">,</span> purpose <span class="token operator">=</span> <span class="token string">"dev"</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span> <span class="token operator">=</span> name
    kwargs<span class="token punctuation">[</span><span class="token string">"tags"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>purpose<span class="token punctuation">]</span>
    cc_test<span class="token punctuation">(</span><span class="token operator">**</span>kwargs<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"dev-test"</span><span class="token punctuation">)</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"prod-test"</span><span class="token punctuation">,</span> purpose<span class="token operator">=</span><span class="token string">"prod"</span><span class="token punctuation">)</span></code></pre></div>
<p>With symbolic macros, the default values are declared in the <code class="language-text">attrs</code> dictionary instead:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_special_test_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> purpose <span class="token operator">=</span> <span class="token string">"dev"</span><span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"tags"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>purpose<span class="token punctuation">]</span>
    cc_test<span class="token punctuation">(</span>
        name <span class="token operator">=</span> name<span class="token punctuation">,</span>
        <span class="token operator">**</span>kwargs
    <span class="token punctuation">)</span>

special_test <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    inherit_attrs <span class="token operator">=</span> native<span class="token punctuation">.</span>cc_test<span class="token punctuation">,</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"purpose"</span><span class="token punctuation">:</span> attr<span class="token punctuation">.</span>string<span class="token punctuation">(</span>configurable <span class="token operator">=</span> <span class="token boolean">False</span><span class="token punctuation">,</span> default <span class="token operator">=</span> <span class="token string">"staging"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string">"copts"</span><span class="token punctuation">:</span> <span class="token boolean">None</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _special_test_impl<span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
special_test<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"my-special-test-prod"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"test.cc"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    purpose <span class="token operator">=</span> <span class="token string">"prod"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

special_test<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"my-special-test-dev"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"test.cc"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>Let’s see what kind of tags are going to be set for these <code class="language-text">cc_test</code> targets:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //test/package:my-special-test-prod --output=build
cc_test(
  name = "my-special-test-prod",
  tags = ["prod"],
  ...
)

$ bazel cquery //test/package:my-special-test-dev --output=build
cc_test(
  name = "my-special-test-dev",
  tags = ["staging"],
  ...
)</code></pre></div>
<p>Notice how the default <code class="language-text">dev</code> value declared in the macro implementation was never used.
This is because the default values defined for parameters in the macro’s function are going to be ignored,
so it’s best to remove them to avoid any confusion.</p>
<p>Also, all the inherited attributes have a default value of <code class="language-text">None</code>,
so make sure to refactor your macro logic accordingly.
Be careful when processing the keyword arguments to avoid
subtle bugs such as checking whether a user has passed <code class="language-text">[]</code> in a keyword argument
merely by doing <code class="language-text">if not kwargs["attr-name"]</code>
as <code class="language-text">None</code> would also be evaluated to <code class="language-text">False</code> in this context.</p>
<p>This might be potentially confusing as the default value for many common attributes is not <code class="language-text">None</code>.
Take a look at the <a href="https://bazel.build/reference/be/common-definitions#common.target_compatible_with"><code class="language-text">target_compatible_with</code></a> attribute
which normally has the default value <code class="language-text">[]</code> when used in a rule,
but when used in a macro, would still by default be set to <code class="language-text">None</code>.
Using <code class="language-text">bazel cquery //:target --output=build</code>
with some <code class="language-text">print</code> calls in your <code class="language-text">.bzl</code> files can help when refactoring.</p>
<h3 id="inheritance" style="position:relative;"><a href="#inheritance" aria-label="inheritance permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Inheritance</h3>
<p>Macros are frequently designed to wrap a rule (or another macro), and the macro’s author typically aims to pass
most of the wrapped symbol’s attributes using <code class="language-text">**kwargs</code> directly to the macro’s primary target
or the main inner macro without modification.</p>
<p>To enable this behavior, a macro can inherit attributes from a rule or another macro by providing the rule
or macro symbol to the <code class="language-text">inherit_attrs</code> parameter of <code class="language-text">macro()</code>.
Note that when <code class="language-text">inherit_attrs</code> is set, the implementation function <em>must</em> have a <code class="language-text">**kwargs</code> parameter.
This makes it possible to avoid listing every attribute that the macro may accept,
and it is also possible to disable certain attributes that you don’t want macro callers to provide.
For instance, let’s say you don’t want <code class="language-text">copts</code> to be defined in macros that wrap <code class="language-text">cc_test</code>
because you want to manage them internally within the macro body instead:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># BUILD.bazel</span>
special_test<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"my-special-test"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"test.cc"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    copts <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"-std=c++22"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>This can be done by setting the attributes you don’t want to inherit to <code class="language-text">None</code>.</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
special_test <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    inherit_attrs <span class="token operator">=</span> native<span class="token punctuation">.</span>cc_test<span class="token punctuation">,</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">"copts"</span><span class="token punctuation">:</span> <span class="token boolean">None</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _special_test_impl<span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>Now the macro caller will see that <code class="language-text">copts</code> is not possible to declare when calling the macro:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel query //test/package:my-special-test
        File "defs.bzl", line 19, column 1, in special_test
                special_test = macro(
Error: no such attribute 'copts' in 'special_test' macro</code></pre></div>
<p>Keep in mind that all inherited attributes are going to be included in the <code class="language-text">kwargs</code> parameter
with the default value of <code class="language-text">None</code> unless specified otherwise.
This means you have to be extra careful in the macro implementation function if you refactor a legacy macro:
you can no longer merely check for the presence of a key in the <code class="language-text">kwargs</code> dictionary.</p>
<h2 id="mutation" style="position:relative;"><a href="#mutation" aria-label="mutation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Mutation</h2>
<p>In symbolic macros, you will not be able to mutate the arguments passed to the macro implementation function.</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_simple_macro_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> visibility<span class="token punctuation">,</span> env<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">type</span><span class="token punctuation">(</span>env<span class="token punctuation">)</span><span class="token punctuation">,</span> env<span class="token punctuation">)</span>
    env<span class="token punctuation">[</span><span class="token string">"some"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"more"</span>

simple_macro <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"env"</span><span class="token punctuation">:</span> attr<span class="token punctuation">.</span>string_dict<span class="token punctuation">(</span>configurable <span class="token operator">=</span> <span class="token boolean">False</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _simple_macro_impl
<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
simple_macro<span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"tool"</span><span class="token punctuation">,</span> env <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"state"</span><span class="token punctuation">:</span> <span class="token string">"active"</span><span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div>
<p>Let’s check how this would get evaluated:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //...
DEBUG: defs.bzl:36:10: dict {"state": "active"}
        File "defs.bzl", line 37, column 17, in _simple_macro_impl
                env["some"] = "more"
Error: trying to mutate a frozen dict value</code></pre></div>
<p>This, however, is no different to legacy macros where you could not modify mutable objects in place either.
In situations like this, creating a new dict with <code class="language-text">env = dict(env)</code> would be of help.</p>
<p>In legacy macros you can still modify objects in place when they are inside the <code class="language-text">kwargs</code>,
but this arguably leads to code that is harder to reason about
and invites subtle bugs that are a nightmare to troubleshoot in a large codebase.
See the <a href="https://bazel.build/rules/language#mutability">Mutability in Starlark</a> section to learn more.</p>
<p>This is still possible in legacy macros:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">special_test_legacy</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span> <span class="token operator">=</span> name
    kwargs<span class="token punctuation">[</span><span class="token string">"env"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"some"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"more"</span>
    cc_test<span class="token punctuation">(</span><span class="token operator">**</span>kwargs<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
special_test_legacy<span class="token punctuation">(</span><span class="token string">"small-test"</span><span class="token punctuation">,</span> env <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"state"</span><span class="token punctuation">:</span> <span class="token string">"active"</span><span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div>
<p>Let’s see how the updated environment variables were set for the <code class="language-text">cc_test</code> target created in the legacy macro:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //test/package:small-test --output=build
...
cc_test(
  name = "small-test",
  ...
  env = {"state": "active", "some": "more"},
)</code></pre></div>
<p>This is no longer allowed in symbolic macros:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_simple_macro_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> visibility<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">type</span><span class="token punctuation">(</span>kwargs<span class="token punctuation">[</span><span class="token string">"env"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> kwargs<span class="token punctuation">[</span><span class="token string">"env"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"env"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"some"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"more"</span></code></pre></div>
<p>It would fail to evaluate:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //...

DEBUG: defs.bzl:35:10: dict {"state": "active"}
        File "defs.bzl", line 36, column 27, in _simple_macro_impl
                kwargs["env"]["some"] = "more"
Error: trying to mutate a frozen dict value</code></pre></div>
<h2 id="configuration" style="position:relative;"><a href="#configuration" aria-label="configuration permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Configuration</h2>
<p>Symbolic macros, just like legacy macros, support <a href="https://bazel.build/docs/configurable-attributes">configurable attributes</a>,
commonly known as <code class="language-text">select()</code>, a Bazel feature that lets users determine the values of build rule (or macro)
attributes at the command line.</p>
<p>Here’s an example symbolic macro with the <code class="language-text">select</code> toggle:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_special_test_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    cc_test<span class="token punctuation">(</span>
        name <span class="token operator">=</span> name<span class="token punctuation">,</span>
        <span class="token operator">**</span>kwargs
    <span class="token punctuation">)</span>
special_test <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    inherit_attrs <span class="token operator">=</span> native<span class="token punctuation">.</span>cc_test<span class="token punctuation">,</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _special_test_impl<span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
config_setting<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"linking-static"</span><span class="token punctuation">,</span>
    define_values <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"static-testing"</span><span class="token punctuation">:</span> <span class="token string">"true"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

config_setting<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"linking-dynamic"</span><span class="token punctuation">,</span>
    define_values <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"static-testing"</span><span class="token punctuation">:</span> <span class="token string">"false"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

special_test<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"my-special-test"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"test.cc"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    linkstatic <span class="token operator">=</span> select<span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token string">":linking-static"</span><span class="token punctuation">:</span> <span class="token boolean">True</span><span class="token punctuation">,</span>
        <span class="token string">":linking-dynamic"</span><span class="token punctuation">:</span> <span class="token boolean">False</span><span class="token punctuation">,</span>
        <span class="token string">"//conditions:default"</span><span class="token punctuation">:</span> <span class="token boolean">False</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>Let’s see how this expands in the <code class="language-text">BUILD</code> file:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel query //test/package:my-special-test --output=build
cc_test(
  name = "my-special-test",
  ...(omitted for brevity)...
  linkstatic = select({
    "//test/package:linking-static": True,
    "//test/package:linking-dynamic": False,
    "//conditions:default": False
  }),
)</code></pre></div>
<p>The <code class="language-text">query</code> command does show that the macro was expanded into a <code class="language-text">cc_test</code> target,
but it does not show what the <code class="language-text">select()</code> is resolved to.
For this, we would need to use the <code class="language-text">cquery</code> (configurable query)
which is a variant of <code class="language-text">query</code> that runs after <code class="language-text">select()</code>s have been evaluated.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //test/package:my-special-test --output=build
cc_test(
  name = "my-special-test",
  ...(omitted for brevity)...
  linkstatic = False,
)</code></pre></div>
<p>Let’s configure the test to be statically linked:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //test/package:my-special-test --output=build --define="static-testing=true"
cc_test(
  name = "my-special-test",
  ...(omitted for brevity)...
  linkstatic = True,
)</code></pre></div>
<p>Each attribute in the <code class="language-text">macro</code> function explicitly declares whether it tolerates <code class="language-text">select()</code> values,
in other words, whether it is <em>configurable</em>.
For common attributes, consult the <a href="https://bazel.build/reference/be/common-definitions#typical-attributes">Typical attributes defined by most build rules</a>
to see which attributes can be configured.
Most attributes are configurable, meaning that their values may change
when the target is built in different ways;
however, there are a handful which are not.
For example, you cannot assign a <code class="language-text">*_test</code> target to be flaky using a <code class="language-text">select()</code>
(e.g., to mark a test as flaky only on <code class="language-text">aarch64</code> devices).</p>
<p>Unless specifically declared, all attributes in symbolic macros are configurable (if they support this)
which means they will be wrapped in a <code class="language-text">select()</code> (that simply maps <code class="language-text">//conditions:default</code> to the single value),
and you might need to adjust the code of the legacy macro you migrate.
For instance, this legacy code used to append some dependencies with the <code class="language-text">.append()</code> list method,
but this might break:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_simple_macro_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> visibility<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span>kwargs<span class="token punctuation">[</span><span class="token string">"deps"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    kwargs<span class="token punctuation">[</span><span class="token string">"deps"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token string">"//:commons"</span><span class="token punctuation">)</span>
    cc_test<span class="token punctuation">(</span><span class="token operator">**</span>kwargs<span class="token punctuation">)</span>

simple_macro <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"deps"</span><span class="token punctuation">:</span> attr<span class="token punctuation">.</span>label_list<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _simple_macro_impl<span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
simple_macro<span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"simple-test"</span><span class="token punctuation">,</span> deps <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"//:helpers"</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre></div>
<p>Let’s evaluate the macro:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //...
DEBUG: defs.bzl:35:10: select({"//conditions:default": [Label("//:helpers")]})
        File "defs.bzl", line 36, column 19, in _simple_macro_impl
                kwargs["deps"].append("//:commons")
Error: 'select' value has no field or method 'append'</code></pre></div>
<p>Keep in mind that <code class="language-text">select</code> is an opaque object with limited interactivity.
It does, however, support modification in place, so that you can extend it,
e.g., with <code class="language-text">kwargs["deps"] += ["//:commons"]</code>:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //test/package:simple-test --output=build
...
cc_test(
  name = "simple-test",
  generator_name = "simple-test",
  ...
  deps = ["//:commons", "//:helpers", "@rules_cc//:link_extra_lib"],
)</code></pre></div>
<p>Be extra vigilant when dealing with attributes of <code class="language-text">bool</code> type that are configurable
because the return type of <code class="language-text">select</code> converts silently in truthy contexts to <code class="language-text">True</code>.
This can lead to some code being legitimate, but not doing what you intended.
See <a href="https://bazel.build/docs/configurable-attributes#faq-boolean-select">Why does select() always return true?</a> to learn more.</p>
<p>When refactoring, you might need to make an attribute configurable, however, it may stop working
using the existing macro implementation.
For example, imagine you need to pass different files as input to your macro depending on the
configuration specified at runtime:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_deployment_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> visibility<span class="token punctuation">,</span> filepath<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span>filepath<span class="token punctuation">)</span>
    <span class="token comment"># implementation</span>

simple_macro <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"filepath"</span><span class="token punctuation">:</span> attr<span class="token punctuation">.</span>string<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _deployment_impl<span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
deployment<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"deploy"</span><span class="token punctuation">,</span>
    filepath <span class="token operator">=</span> select<span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token string">"//conditions:default"</span><span class="token punctuation">:</span> <span class="token string">"deploy/config/dev.ini"</span><span class="token punctuation">,</span>
        <span class="token string">"//:production"</span><span class="token punctuation">:</span> <span class="token string">"deploy/config/production.ini"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>In rules, <code class="language-text">select()</code> objects are resolved to their actual values,
but in macros, <code class="language-text">select()</code> creates a special object of type <code class="language-text">select</code>
that isn’t evaluated until the analysis phase,
which is why you won’t be able to get actual values out of it.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //:deploy
...
select({
    Label("//conditions:default"): "deploy/config/dev.ini",
    Label("//:production"): "deploy/config/production.ini"
    })
...</code></pre></div>
<p>In some cases, such as when you need to have the <code class="language-text">select</code>ed value available in the macro function,
you can have the <code class="language-text">select</code> object resolved before it’s passed to the macro.
This can be done with the help of an <code class="language-text">alias</code> target, and the label of a target can be turned into a filepath
using the special <a href="https://bazel.build/reference/be/make-variables#predefined_label_variables"><code class="language-text">location</code> variable</a>:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># defs.bzl</span>
<span class="token keyword">def</span> <span class="token function">_deployment_impl</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> visibility<span class="token punctuation">,</span> filepath<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">type</span><span class="token punctuation">(</span>filepath<span class="token punctuation">)</span><span class="token punctuation">,</span> filepath<span class="token punctuation">)</span>
    native<span class="token punctuation">.</span>genrule<span class="token punctuation">(</span>
        name <span class="token operator">=</span> name <span class="token operator">+</span> <span class="token string">"_gen"</span><span class="token punctuation">,</span>
        srcs <span class="token operator">=</span> <span class="token punctuation">[</span>filepath<span class="token punctuation">]</span><span class="token punctuation">,</span>
        outs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"config.out"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        cmd <span class="token operator">=</span> <span class="token string">"echo '$(location {})' > $@"</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>filepath<span class="token punctuation">)</span>
    <span class="token punctuation">)</span>

deployment <span class="token operator">=</span> macro<span class="token punctuation">(</span>
    attrs <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"filepath"</span><span class="token punctuation">:</span> attr<span class="token punctuation">.</span>label<span class="token punctuation">(</span>configurable <span class="token operator">=</span> <span class="token boolean">False</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    implementation <span class="token operator">=</span> _deployment_impl<span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># BUILD.bazel</span>
alias<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"configpath"</span><span class="token punctuation">,</span>
    actual <span class="token operator">=</span> select<span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token string">"//conditions:default"</span><span class="token punctuation">:</span> <span class="token string">"deploy/config/dev.ini"</span><span class="token punctuation">,</span>
        <span class="token string">"//:production"</span><span class="token punctuation">:</span> <span class="token string">"deploy/config/production.ini"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    visibility <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"//visibility:public"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

deployment<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"deploy"</span><span class="token punctuation">,</span>
    filepath <span class="token operator">=</span> <span class="token string">":configpath"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>You can confirm the right file is chosen when passing different configuration flags before building the target:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ bazel cquery //tests:configpath --output=build
INFO: Analyzed target //tests:configpath (0 packages loaded, 1 target configured).
...
alias(
  name = "configpath",
  visibility = ["//visibility:public"],
  actual = "//tests:deploy/config/dev.ini",
)
...

$ bazel build //tests:deploy_gen &amp;&amp; cat bazel-bin/tests/config.out
...
DEBUG: defs.bzl:29:10: Label //tests:configpath
...
tests/deploy/config/dev.ini</code></pre></div>
<h2 id="querying-macros" style="position:relative;"><a href="#querying-macros" aria-label="querying macros permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Querying macros</h2>
<p>Since macros are evaluated when <code class="language-text">BUILD</code> files are queried,
you cannot use Bazel itself to query “raw” <code class="language-text">BUILD</code> files.
Identifying definitions of legacy macros is quite difficult,
as they resemble Starlark functions, but instantiate targets.
Using <code class="language-text">bazel cquery</code> with the <a href="https://bazel.build/query/cquery#output-format-definition"><code class="language-text">--output=starlark</code></a>
might help printing the properties of targets to see
if they have been instantiated from macros.</p>
<p>When using <code class="language-text">--output=build</code>, you can also inspect some of the properties:</p>
<ul>
<li><code class="language-text">generator_name</code> (the name attribute of the macro)</li>
<li><code class="language-text">generator_function</code> (which function generated the rules)</li>
<li><code class="language-text">generator_location</code> (where the macro was invoked)</li>
</ul>
<p>This information with some heuristics might help you to identify the macros.
Once you have identified the macro name,
you can run <code class="language-text">bazel query --output=build 'attr(generator_function, simple_macro, //...)'</code>
to find all targets that are generated by a particular macro.
Finding symbolic macros, in contrast, is trivial
as you would simply need to grep for <code class="language-text">macro()</code> function calls in <code class="language-text">.bzl</code> files.</p>
<p>To query unprocessed <code class="language-text">BUILD</code> files, you might want to use <a href="https://github.com/bazelbuild/buildtools/blob/main/buildozer/README.md">buildozer</a>
which is a tool that lets you query the contents of <code class="language-text">BUILD</code> files using a static parser.
The tool will come in handy for various use cases when refactoring, such as migrating the macros.
Because both legacy and symbolic macros follow the same <code class="language-text">BUILD</code> file syntax,
<code class="language-text">buildozer</code> can be used to query build metadata for either type.</p>
<p>Let’s write some queries for these macro invocations:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># BUILD.bazel</span>
perftest<span class="token punctuation">(</span>
  name <span class="token operator">=</span> <span class="token string">"apis"</span><span class="token punctuation">,</span>
  srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"//:srcA"</span><span class="token punctuation">,</span> <span class="token string">"//:srcB"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  env <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"performance"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

perftest<span class="token punctuation">(</span>
  name <span class="token operator">=</span> <span class="token string">"backend"</span><span class="token punctuation">,</span>
  srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"//:srcC"</span><span class="token punctuation">,</span> <span class="token string">"//:srcD"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  env <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"performance"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>Print all macro invocations (raw) across the whole workspace:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ buildozer 'print rule' "//...:%perftest"

perftest(
    name = "apis",
    srcs = [
        "//:srcA",
        "//:srcB",
    ],
    env = {"type": "performance"},
)

perftest(
    name = "backend",
    srcs = [
        "//:srcC",
        "//:srcD",
    ],
    env = {"type": "performance"},
)</code></pre></div>
<p>Print attribute’s values for all macro invocations:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ buildozer 'print label srcs' "//...:%perftest"
//test/package:apis [//:srcA //:srcB]
//test/package:backend [//:srcC //:srcD]</code></pre></div>
<p>Print path to files where macros are invoked:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ buildozer 'print path' "//...:%perftest" | xargs realpath --relative-to "$PWD" | sort | uniq
test/package/BUILD.bazel</code></pre></div>
<p>The <code class="language-text">path</code> can be combined with an attribute, e.g., print <code class="language-text">path</code> and the <code class="language-text">srcs</code> to make reviewing easier:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ buildozer 'print path srcs' "//...:%perftest"
/home/user/code/project/test/package/BUILD.bazel [//:srcA //:srcB]
/home/user/code/project/test/package/BUILD.bazel [//:srcC //:srcD]</code></pre></div>
<p>Remove an attribute from a macro invocation (e.g., <code class="language-text">env</code> will be set up in the macro implementation function):</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ buildozer 'remove env' "//...:%perftest"
fixed /home/user/code/project/test/package/BUILD.bazel</code></pre></div>
<p>You might also want to check that no macro invocation passes an attribute that is not supposed to be passed.
In the command output, the <code class="language-text">missing</code> means the attribute doesn’t exist;
these lines can of course be ignored with <code class="language-text">grep -v missing</code>:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ buildozer -quiet 'print path env' "//...:%perftest" 2>/dev/null
/home/user/code/project/test/package/BUILD.bazel {"type": "performance"}
/home/user/code/project/test/package/BUILD.bazel (missing)</code></pre></div>
<hr>
<p>We hope that these practical suggestions and examples will assist you in your efforts
to modernize the use of macros throughout your codebase.
Remember that you can compose legacy and symbolic macros, which may be useful during the transition.
Also, legacy macros can still be used and are to remain supported in Bazel for the foreseeable future.
Some organizations may even choose not to migrate at all,
particularly if they rely on the current behavior of the legacy macros heavily.</p>]]></description><link>https://tweag.io/blog/2025-11-20-migrating-bazel-symbolic-macros/</link><guid isPermaLink="false">https://tweag.io/blog/2025-11-20-migrating-bazel-symbolic-macros/</guid><pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Continuous Performance Testing: staying fast]]></title><description><![CDATA[<p>The performance of a system is critical to the user experience. Whether it’s a website, mobile app, or service, users demand fast response and seamless functionality.
Every change to a system brings the risk of performance degradation, so you should check every commit during development to ensure that loyal users do not face any performance issues.</p>
<p>From my experience, one of the most effective methods to achieve this is with Continuous Performance Testing (CPT). In this post, I want to explain how CPT is effective in catching performance-related issues during development.
CPT is a performance testing strategy, so you might benefit from a basic understanding of the latter. A look at my <a href="https://www.tweag.io/blog/2025-08-14-performance-testing">previous blog post</a> will be helpful!</p>
<h2 id="what-is-continuous-performance-testing" style="position:relative;"><a href="#what-is-continuous-performance-testing" aria-label="what is continuous performance testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What is Continuous Performance Testing?</h2>
<p>Continuous Performance Testing (CPT) is an automated and systematic approach to performance testing, leveraging various tools to spontaneously conduct tests throughout the development lifecycle.
Its primary goal is to gather insightful data, providing real-time feedback on how code changes impact system performance and ensuring the system is performing adequately before proceeding further.</p>
<p>As shown in the example below, CPT is integrated directly into the Continuous Integration and Continuous Deployment (CI/CD) pipeline.
This integration allows performance testing to act as a crucial gatekeeper, enabling quick and accurate assessments to ensure that software meets required performance benchmarks before moving to subsequent stages.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/231091081669143af96c3d9cfea9392b/6871f/automated-load-testing.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 29.054054054054056%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAy0lEQVQY042P224DIQxE+f8frZJluRkw2GSngg0PUVW1SEcez4BljPMezgcQEUpXNFHEmCAi8D5gjLH0PNd1/YlJlBEToVZGbYLWFZUbWpdVu+iviP70zONw+HpapMz3sCbgPsBdF7UrCgu43bruuvz+vr8zgbEpwIUIe86vR1BlPL1b/ek8DutAhXGEW58urMyeDqU2OCI8Trv6+d6kUpHyTS6MzA3To83069vLdQ3f2dxwLjCzSAU+EoyOFz4ZGP/yXlAdq+5MdOAbF+PSoXwnPmkAAAAASUVORK5CYII='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Automated Load Testing"
        title="Automated Load Testing"
        src="/static/231091081669143af96c3d9cfea9392b/fcda8/automated-load-testing.png"
        srcset="/static/231091081669143af96c3d9cfea9392b/12f09/automated-load-testing.png 148w,
/static/231091081669143af96c3d9cfea9392b/e4a3f/automated-load-testing.png 295w,
/static/231091081669143af96c3d9cfea9392b/fcda8/automated-load-testing.png 590w,
/static/231091081669143af96c3d9cfea9392b/efc66/automated-load-testing.png 885w,
/static/231091081669143af96c3d9cfea9392b/c83ae/automated-load-testing.png 1180w,
/static/231091081669143af96c3d9cfea9392b/6871f/automated-load-testing.png 2688w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>A key benefit of this approach is its alignment with shift-left testing, which emphasizes bringing performance testing earlier into the development lifecycle.
By identifying and addressing performance issues much sooner, teams can avoid costly late-stage fixes, improve software quality, and accelerate the overall development process, ultimately ensuring that performance standards and Service Level Agreements (SLAs) are consistently met.</p>
<h2 id="to-which-types-of-performance-testing-can-cpt-be-applied" style="position:relative;"><a href="#to-which-types-of-performance-testing-can-cpt-be-applied" aria-label="to which types of performance testing can cpt be applied permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>To which types of performance testing can CPT be applied?</h2>
<p>Continuous performance testing can be applied to the all types of performance testing. However each types has different challenges.</p>
<p>Automated Performance Testing is</p>
<ul>
<li>Easily applied to load testing</li>
<li>Hard to apply to stress and spike tests, but still has benefits</li>
<li>Very hard to apply to soak-endurance tests</li>
</ul>
<p>For more details about why the latter two performance testing types are difficult to implement in CI/CD, see the <a href="https://www.tweag.io/blog/2025-08-14-performance-testing">previous blog post</a>.</p>
<h2 id="why-prefer-automated-load-testing" style="position:relative;"><a href="#why-prefer-automated-load-testing" aria-label="why prefer automated load testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why prefer automated load testing?</h2>
<p>The load test is designed with the primary objective of assessing how well the system performs under a specific and defined load.
This type of testing is crucial for evaluating the system’s behavior and ensuring it can handle expected levels of user activity or data processing.
The success of a load test is determined by its adherence to predefined metrics, which serve as benchmarks against which the system’s performance is measured.
These metrics might include factors such as response times, throughput, and resource utilization.
Given this focus on quantifiable outcomes, load testing is considered the most appropriate, easiest and well-suited type of performance testing type for Continuous Performance Testing (CPT).</p>
<h2 id="how-to-apply-continuous-load-testing" style="position:relative;"><a href="#how-to-apply-continuous-load-testing" aria-label="how to apply continuous load testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to apply continuous load testing</h2>
<h3 id="strategy" style="position:relative;"><a href="#strategy" aria-label="strategy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Strategy</h3>
<p>Performance testing can be conducted at every level, starting with unit testing.
It should be tailored to evaluate the specific performance requirements of each development stage, ensuring the system meets its capabilities and user expectation.</p>
<p>Load testing can be performed at any level—unit, integration, system, or acceptance.
In Continuous Performance Testing (CPT), performance testing should start as early as possible in the development process to provide timely feedback, especially at the integration level.
Early testing helps identify bottlenecks and optimize the application before progression.
When CPT is applied at the system level, it offers insights into the overall performance of the entire system and how components interact, helping ensure the system meets its performance goals.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/6749fccc359945fc800164516b7a87ef/34cfb/performance-testing-in-pipeline.jpg"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 32.43243243243243%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAGABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAQD/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAbqCXYH/xAAaEAAABwAAAAAAAAAAAAAAAAAAAQIREiIx/9oACAEBAAEFAj1LxsP/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAgEBPwFn/8QAGBAAAwEBAAAAAAAAAAAAAAAAAAExQUL/2gAIAQEABj8Cd3oZT//EABoQAAICAwAAAAAAAAAAAAAAAAERACExkcH/2gAIAQEAAT8hbsIBAFGslxMaan//2gAMAwEAAgADAAAAEIPv/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERMf/aAAgBAwEBPxBYyn//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPxAj/8QAHBAAAgICAwAAAAAAAAAAAAAAAREAITHBQaGx/9oACAEBAAE/EDGnLAWq1DKlCEvoQ1C3Q49z/9k='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Performance Testing in Pipeline"
        title="Performance Testing in Pipeline"
        src="/static/6749fccc359945fc800164516b7a87ef/1c72d/performance-testing-in-pipeline.jpg"
        srcset="/static/6749fccc359945fc800164516b7a87ef/a80bd/performance-testing-in-pipeline.jpg 148w,
/static/6749fccc359945fc800164516b7a87ef/1c91a/performance-testing-in-pipeline.jpg 295w,
/static/6749fccc359945fc800164516b7a87ef/1c72d/performance-testing-in-pipeline.jpg 590w,
/static/6749fccc359945fc800164516b7a87ef/a8a14/performance-testing-in-pipeline.jpg 885w,
/static/6749fccc359945fc800164516b7a87ef/fbd2c/performance-testing-in-pipeline.jpg 1180w,
/static/6749fccc359945fc800164516b7a87ef/34cfb/performance-testing-in-pipeline.jpg 3507w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>In my opinion, to maximize CPT benefits, it’s best to apply automated load testing at both integration and system level.
This ensures realistic load conditions, highlights performance issues early, and helps optimize performance throughout development for a robust, efficient application.</p>
<h3 id="evaluation-with-static-thresholds" style="position:relative;"><a href="#evaluation-with-static-thresholds" aria-label="evaluation with static thresholds permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Evaluation with static thresholds</h3>
<p>Continuous Performance Testing (CPT) is fundamentally centered around fully automated testing processes, meaning that the results obtained from performance testing must also be evaluated automatically to ensure efficiency and accuracy.
This automatic evaluation can be achieved in different ways.
Establishing static metrics that serve as benchmarks against which the current results can be measured is one of them.
By setting and comparing against these predefined metrics, we can effectively assess whether the application meets the required performance standards.</p>
<p>The below code snippet shows how we can set threshold values for various metrics with <a href="https://k6.io">K6</a>.
K6 is an open source performance testing tool built in Go and it allows us to write performance testing scripts in Javascript, and it
has an embedded threshold feature that we can use to evaluate the performance test results.
For more information about setting thresholds, please see the documentation of <a href="https://grafana.com/docs/k6/latest/using-k6/thresholds/">K6 thresholds</a>.</p>
<div style="width: 960px;">
<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> check<span class="token punctuation">,</span> sleep <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"k6"</span>
<span class="token keyword">import</span> http <span class="token keyword">from</span> <span class="token string">"k6/http"</span>

<span class="token keyword">export</span> <span class="token keyword">let</span> options <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">vus</span><span class="token operator">:</span> <span class="token number">250</span><span class="token punctuation">,</span> <span class="token comment">// number of virtual users</span>
  <span class="token literal-property property">duration</span><span class="token operator">:</span> <span class="token string">"30s"</span><span class="token punctuation">,</span> <span class="token comment">// duration of the test</span>
  <span class="token literal-property property">thresholds</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">http_req_duration</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token string">"avg&lt;20"</span><span class="token punctuation">,</span> <span class="token comment">// average response time must be below 2ms</span>
      <span class="token string">"p(90)&lt;30"</span><span class="token punctuation">,</span> <span class="token comment">// 90% of requests must complete below 3ms</span>
      <span class="token string">"p(95)&lt;40"</span><span class="token punctuation">,</span> <span class="token comment">// 95% of requests must complete below 4ms</span>
      <span class="token string">"max&lt;50"</span><span class="token punctuation">,</span> <span class="token comment">// max response time must be below 5ms</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">http_req_failed</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token string">"rate&lt;0.01"</span><span class="token punctuation">,</span> <span class="token comment">// http request failures should be less than 1%</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">checks</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token string">"rate>0.99"</span><span class="token punctuation">,</span> <span class="token comment">// 99% of checks should pass</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span></code></pre></div>
</div>
<p>With the example above, K6 tests the service for 30 seconds with 250 virtual users and compares the results to the metrics defined in the threshold section.
Let’s look at the results of this test:</p>
<div style="width: 960px;">
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">running (0m30.0s), 250/250 VUs, 7250 complete and 0 interrupted iterations
default   [ 100% ] 250 VUs  30.0s/30s

     ✓ is status 201
     ✓ is registered

   ✓ checks.........................: 100.00% 15000 out of 15000
   ✗ http_req_duration..............: avg=2.45ms   min=166.47µs med=1.04ms   max=44.52ms p(90)=3.68ms   p(95)=7.71ms
       { expected_response:true }...: avg=2.45ms   min=166.47µs med=1.04ms   max=44.52ms p(90)=3.68ms   p(95)=7.71ms
   ✓ http_req_failed................: 0.00%   0 out of 7500
     iterations.....................: 7500    248.679794/s
     vus_max........................: 250     min=250            max=250


running (0m30.2s), 000/250 VUs, 7500 complete and 0 interrupted iterations
default ✓ [ 100% ] 250 VUs  30s
time=&quot;2025-03-12T12:09:54Z&quot; level=error msg=&quot;thresholds on metrics &#39;http_req_duration&#39; have been crossed&quot;
Error: Process completed with exit code 99.</code></pre></div>
</div>
<p>Although the <code class="language-text">checks</code> and the <code class="language-text">http_req_failed</code> rate thresholds are satisfied, this test failed because all the calculated <code class="language-text">http_req_duration</code> metrics are greater than the thresholds defined above.</p>
<h3 id="evaluation-by-comparing-to-historical-data" style="position:relative;"><a href="#evaluation-by-comparing-to-historical-data" aria-label="evaluation by comparing to historical data permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Evaluation by comparing to historical data</h3>
<p>Another method of evaluation involves comparing the current results with historical data within a defined confidence level.
This statistical approach allows us to understand trends over time and determine if the application’s performance is improving, declining, or remaining stable.</p>
<p>In many cases, performance metrics such as response times or throughput can be assumed to follow a normal distribution, especially when you have a large enough sample size.
The normal distribution, often referred to as the bell curve, is a probability distribution that is symmetric about the mean. You can read more about it on <a href="https://en.wikipedia.org/wiki/Normal_distribution">Wikipedia</a>.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/57e49feadb0e7a2168a0c776f865a1f1/60a48/normal-distribution.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 59.45945945945946%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACI0lEQVQoz2WSXU8TQRSG+aNGSawKCXqhiYn/ALzxngtjuDcmEqJ8VK2ItoBtSYzEL9rSWth2uzu723Z3Z2YfM7MtFDzJ2byZc84z75nsXJZlZDrPXl9w0jijezbgb9eldepw7ngXZybbnb5NU2u2zhFiiAmtNIY1Zz4yVWiV2UOjVaonqUBDr+9z3vOtvqzldS21nTVzxpQFaq3tLUYrqW3BNsX5Rb4X4vaF1WksLcT0KKnQWZY71DMO1QSo9SXcNEqlrI7SBC8eWy2VvuzR2s6YUOoaMHc6AWb5oInjYMCT+iceVkt8drsX0CkkB2ZXgbMrT7WJdhTw4KjEfHmL25UtCtUida93UZ+u+f/KSuOLiCSR1q0IhvaRV44PufFxg8XKDosHRW7tvuFxbZcgiQmDEalUSKnsrNFXgAaSpBKpNUkUUxn0WKqVKOxtcre8zb39He7sbVLY3+Gt0yaOYgsxaQ1cB05XNu/XScesdU94VN9lofrOulvYL7JYe8/9rx9YPf3JjziyvUxWVrMrT8Oob0PBmtPkefcPS/USNw+2mK9M8nCbwmGR1c4vXjgNKoGLZGbeAM1jinCIE4R877ustxqst5u8bjV4dnTAcr3MSrXMcvULK0cVntbKvGz8ZqPT4lXzhLrjcCZCvCCyv9nceBzj9AaMxjG+HzLoeahEEokIMRAgNekoIR3FVg9FhO/6yDgl9EL6fY/hKMYdCIQI+QdX2YJA5Ll3VwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Normal Distribution"
        title="Normal Distribution"
        src="/static/57e49feadb0e7a2168a0c776f865a1f1/fcda8/normal-distribution.png"
        srcset="/static/57e49feadb0e7a2168a0c776f865a1f1/12f09/normal-distribution.png 148w,
/static/57e49feadb0e7a2168a0c776f865a1f1/e4a3f/normal-distribution.png 295w,
/static/57e49feadb0e7a2168a0c776f865a1f1/fcda8/normal-distribution.png 590w,
/static/57e49feadb0e7a2168a0c776f865a1f1/efc66/normal-distribution.png 885w,
/static/57e49feadb0e7a2168a0c776f865a1f1/c83ae/normal-distribution.png 1180w,
/static/57e49feadb0e7a2168a0c776f865a1f1/60a48/normal-distribution.png 1325w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Here’s how the statistical analysis works:
from your historical data, calculate the mean (or average, μ) and standard deviation (SD, σ) of the performance metrics. These values will serve as the basis for hypothesis testing.
Then, determine the performance metric from the current test run that you want to compare against the historical data. This could be the mean response time, p(90), error rate, etc.</p>
<h4 id="define-test-hyptheses" style="position:relative;"><a href="#define-test-hyptheses" aria-label="define test hyptheses permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Define test hyptheses</h4>
<p>Concretely, let’s first create an hypothesis to test the current result with the historical data.</p>
<ul>
<li>
<p><strong>Null Hypothesis (H0)</strong>: The current performance metric is equal to the historical mean (no significant difference).</p>
<div class="math math-display"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>H</mi><mn>0</mn></msub><mo>:</mo><msub><mi>μ</mi><mtext>current</mtext></msub><mo>=</mo><msub><mi>μ</mi><mtext>historical</mtext></msub></mrow><annotation encoding="application/x-tex">H_0: \mu_{\text{current}} = \mu_{\text{historical}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0813em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">current</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">historical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></div>
</li>
<li>
<p><strong>Alternative Hypothesis (H1)</strong>: The current performance metric is not equal to the historical mean (there is a significant difference).</p>
<div class="math math-display"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>H</mi><mn>1</mn></msub><mo>:</mo><msub><mi>μ</mi><mtext>current</mtext></msub><mo mathvariant="normal">≠</mo><msub><mi>μ</mi><mtext>historical</mtext></msub></mrow><annotation encoding="application/x-tex">H_1: \mu_{\text{current}} \neq \mu_{\text{historical}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0813em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">current</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel"><span class="mrel"><span class="mord vbox"><span class="thinbox"><span class="rlap"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="inner"><span class="mord"><span class="mrel"></span></span></span><span class="fix"></span></span></span></span></span><span class="mrel">=</span></span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">historical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></div>
</li>
</ul>
<h4 id="define-a-comparison-metric-and-acceptance-criterion" style="position:relative;"><a href="#define-a-comparison-metric-and-acceptance-criterion" aria-label="define a comparison metric and acceptance criterion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Define a comparison metric and acceptance criterion</h4>
<p>To compare the current result to the historical mean, we calculate the Z-score, which tells you how many standard deviations the current mean is from the historical mean. The formula for the Z-score is:</p>
<div class="math math-display"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>Z</mi><mo>=</mo><mfrac><mrow><msub><mi>μ</mi><mtext>current</mtext></msub><mo>−</mo><msub><mi>μ</mi><mtext>historical</mtext></msub></mrow><msub><mi>σ</mi><mtext>historical</mtext></msub></mfrac></mrow><annotation encoding="application/x-tex">Z = \frac{\mu_{\text{current}} - \mu_{\text{historical}}}{\sigma_{\text{historical}}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.0963em;vertical-align:-0.836em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.2603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">historical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">current</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">historical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></div>
<p>Where:</p>
<ul>
<li><span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>μ</mi><mtext>current</mtext></msub></mrow><annotation encoding="application/x-tex">\mu_{\text{current}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">current</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span> is the current mean.</li>
<li><span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>μ</mi><mtext>historical</mtext></msub></mrow><annotation encoding="application/x-tex">\mu_{\text{historical}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">μ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">historical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span> is the historical mean.</li>
<li><span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>σ</mi><mtext>historical</mtext></msub></mrow><annotation encoding="application/x-tex">\sigma_{\text{historical}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">σ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">historical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span> is the standard deviation of the historical data.</li>
</ul>
<p>Finally, we need to determine the critical value of the Z-score: for a 95% confidence level, you can extract it from the standard normal distribution table. For a two-tailed test, the critical values are approximately ±1.96. For the full standard normal distribution table, see, for example, <a href="https://www.sjsu.edu/faculty/gerstman/EpiInfo/z-table.htm">this website</a>.</p>
<p>The confidence level means that the calculated difference between current and historical performance would fall within the chosen range around the historical mean in 95% of the cases.
I believe the 95% confidence level provides good enough coverage for most purposes, but depending on the criticality of the product or service, you can increase or decrease it.</p>
<h4 id="make-a-decision" style="position:relative;"><a href="#make-a-decision" aria-label="make a decision permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Make a decision</h4>
<p>If the calculated Z-score falls outside the range of -1.96 to +1.96, you reject the null hypothesis (H0) and conclude that there is a statistically significant difference between the current performance metric and the historical mean.
If the Z-score falls within this range, you fail to reject the null hypothesis, indicating no significant difference.</p>
<p>Based on these findings, you can interpret whether the application’s performance has improved, declined, or remained stable compared to historical data.
This statistical analysis provides a robust framework for understanding performance trends over time and making data-driven decisions for further optimizations.</p>
<h4 id="implementation" style="position:relative;"><a href="#implementation" aria-label="implementation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Implementation</h4>
<p>In the above section, I tried to provide a clear explanation of how we can effectively evaluate the results of performance testing using historical data.
It is important to note that we do not need to engage in complex manual statistical analyses to check the validity of these results.
Instead, we should focus on scripting a comprehensive process that allows us to test the hypothesis for the <code class="language-text">Z-score</code> within the 95% confidence level.
This approach will streamline our evaluation and ensure that we rely on a straightforward method to assess the performance outcomes in the CI/CD pipeline.</p>
<div style="width: 960px;">
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np
<span class="token keyword">from</span> scipy <span class="token keyword">import</span> stats

<span class="token keyword">def</span> <span class="token function">hypothesis_test</span><span class="token punctuation">(</span>historical_data<span class="token punctuation">,</span> current_data<span class="token punctuation">,</span> confidence_level<span class="token operator">=</span><span class="token number">0.95</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token comment"># Calculate historical mean and standard deviation</span>
    historical_mean <span class="token operator">=</span> np<span class="token punctuation">.</span>mean<span class="token punctuation">(</span>historical_data<span class="token punctuation">)</span>
    historical_std <span class="token operator">=</span> np<span class="token punctuation">.</span>std<span class="token punctuation">(</span>historical_data<span class="token punctuation">,</span> ddof<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span>

    <span class="token comment"># Calculate the current mean</span>
    current_mean <span class="token operator">=</span> np<span class="token punctuation">.</span>mean<span class="token punctuation">(</span>current_data<span class="token punctuation">)</span>

    <span class="token comment"># Number of observations in the current dataset</span>
    n_current <span class="token operator">=</span> <span class="token builtin">len</span><span class="token punctuation">(</span>current_data<span class="token punctuation">)</span>

    <span class="token comment"># Calculate Z-score</span>
    z_score <span class="token operator">=</span> <span class="token punctuation">(</span>current_mean <span class="token operator">-</span> historical_mean<span class="token punctuation">)</span> <span class="token operator">/</span> historical_std

    <span class="token comment"># Determine the critical Z-values for the two-tailed test</span>
    critical_value <span class="token operator">=</span> stats<span class="token punctuation">.</span>norm<span class="token punctuation">.</span>ppf<span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> confidence_level<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span>

    <span class="token comment"># Print results</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Historical Mean: </span><span class="token interpolation"><span class="token punctuation">{</span>historical_mean<span class="token punctuation">:</span><span class="token format-spec">.2f</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Current Mean: </span><span class="token interpolation"><span class="token punctuation">{</span>current_mean<span class="token punctuation">:</span><span class="token format-spec">.2f</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Z-Score: </span><span class="token interpolation"><span class="token punctuation">{</span>z_score<span class="token punctuation">:</span><span class="token format-spec">.2f</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Critical Value for </span><span class="token interpolation"><span class="token punctuation">{</span>confidence_level<span class="token operator">*</span><span class="token number">100</span><span class="token punctuation">}</span></span><span class="token string">% confidence: ±</span><span class="token interpolation"><span class="token punctuation">{</span>critical_value<span class="token punctuation">:</span><span class="token format-spec">.2f</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

    <span class="token comment"># Hypothesis testing decision</span>
    <span class="token keyword">if</span> <span class="token builtin">abs</span><span class="token punctuation">(</span>z_score<span class="token punctuation">)</span> <span class="token operator">></span> critical_value<span class="token punctuation">:</span>
        <span class="token keyword">assert</span> <span class="token builtin">abs</span><span class="token punctuation">(</span>z_score<span class="token punctuation">)</span> <span class="token operator">&lt;=</span> critical_value<span class="token punctuation">,</span> <span class="token string-interpolation"><span class="token string">f"z_score </span><span class="token interpolation"><span class="token punctuation">{</span>z_score<span class="token punctuation">}</span></span><span class="token string"> exceeds the critical value </span><span class="token interpolation"><span class="token punctuation">{</span>critical_value<span class="token punctuation">}</span></span><span class="token string">"</span></span>

<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>
    <span class="token comment"># Read the historical data (performance metrics)</span>
    historical_data <span class="token operator">=</span> get_historical_data<span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token comment"># Current data to compare</span>
    current_data <span class="token operator">=</span> get_current_result<span class="token punctuation">(</span><span class="token punctuation">)</span>
    hypothesis_test<span class="token punctuation">(</span>historical_data<span class="token punctuation">,</span> current_data<span class="token punctuation">,</span> confidence_level<span class="token operator">=</span><span class="token number">0.95</span><span class="token punctuation">)</span></code></pre></div>
</div>
<h2 id="the-challenges-with-cpt" style="position:relative;"><a href="#the-challenges-with-cpt" aria-label="the challenges with cpt permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The challenges with CPT</h2>
<p>CPT can add additional cost to your project.
It is an additional step in the CI pipeline, and requires performance engineering expertise that organizations might need to hire for.
Furthermore, an additional test environment is needed to run the performance testing.</p>
<p>In addition to the costs, maintenance can be challenging.
Likewise, data generation is very critical for the success of the performance testing:
it requires obtaining data, masking sensitive information, and deleting them securely.
CPT also requires testing new services, reflecting changes in the current services or removing unused services.
Following up on detected issues and on new features of performance testing tools are also mandatory.
All these must be done regularly to keep the system afloat, adding to existing maintenance efforts.</p>
<h2 id="the-benefits-of-cpt" style="position:relative;"><a href="#the-benefits-of-cpt" aria-label="the benefits of cpt permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The benefits of CPT</h2>
<p>Continuous Performance Testing offers significant benefits by enabling automatic early detection of performance issues within the development process.
This proactive approach allows teams to identify and address bottlenecks before they reach production, reducing both costs and efforts associated with fixing problems later.
By continuously monitoring and optimizing application performance, CPT helps ensure a fast, responsive user experience and minimizes the risk of outages or slowdowns that could disrupt users and business operations.</p>
<p>In addition to early detection, CPT enhances resource utilization by pinpointing inefficient code and infrastructure setups, ultimately reducing overall costs despite initial investments.
It also fosters better collaboration among development, testing, and operations teams by providing a shared understanding of performance metrics:
each test generates valuable data that supports advanced analysis and better decision-making regarding code improvements, infrastructure upgrades, and capacity planning.
Finally, CPT offers the convenience of on-demand testing with just one click, providing an easy-to-use baseline for more rigorous performance evaluations when needed.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>Continuous Performance Testing (CPT) transforms traditional performance testing by integrating it directly into the CI/CD pipeline.
CPT can, in principle, be applied to each performance testing type, but load testing is most advantageous with lower cost and higher benefits.</p>
<p>The core idea is to automate and conduct performance tests continuously and earlier in the development cycle, aligning with the “shift-left” philosophy.
This approach provides real-time feedback on performance impacts, helps identify and resolve issues sooner, and ultimately leads to improved software quality, faster development, and consistent adherence to performance standards and SLAs.</p>]]></description><link>https://tweag.io/blog/2025-10-30-continuous-performance-testing/</link><guid isPermaLink="false">https://tweag.io/blog/2025-10-30-continuous-performance-testing/</guid><pubDate>Thu, 30 Oct 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Introduction to Agentic Coding]]></title><description><![CDATA[<p>AI-assisted coding is having its moment. For autocomplete tools and AI agents like GitHub Copilot and Cursor, the hype is real. But so is the confusion. Are we replacing developers? Can anyone build software just by prompting? Is “vibe coding” the future?</p>
<p>At Modus Create, we wanted to cut through the noise. So we ran a <a href="https://moduscreate.com/insights/ai-tooling-experiment-report-download/">real experiment</a>: two teams, same scope, same product, same timeline. One team used traditional workflows. The other used AI agents to scaffold, implement, and iterate — working in a new paradigm we call <strong>Agentic Coding</strong>.</p>
<p>Every technique we learned along the way and every insight this approach taught us is collected in our <a href="https://tweag.github.io/agentic-coding-handbook/">Agentic Coding Handbook</a>. This article distills the lessons from the handbook into the core principles and practices any engineer can start applying today.</p>
<h2 id="from-typing-code-to-designing-systems" style="position:relative;"><a href="#from-typing-code-to-designing-systems" aria-label="from typing code to designing systems permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>From Typing Code to Designing Systems</h2>
<p>Agentic coding isn’t about writing code faster. It’s about working differently. Instead of manually authoring every line, engineers become high-level problem solvers. They define the goal, plan the implementation, and collaborate with an AI agent that writes code on their behalf.</p>
<blockquote>
<p>Agentic Coding is a structured, AI-assisted workflow where skilled engineers prompt intentionally, validate rigorously, and guide the output within clear architectural boundaries.</p>
</blockquote>
<p>This approach is fundamentally different from what many refer to as “vibe coding”, the idea that you can throw a vague prompt at an LLM and see what comes back. That mindset leads to bloated code, fragile architecture, and hallucinations.</p>
<h3 id="agentic-coding-vs-vibe-coding" style="position:relative;"><a href="#agentic-coding-vs-vibe-coding" aria-label="agentic coding vs vibe coding permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Agentic Coding vs. <a href="https://tweag.github.io/agentic-coding-handbook/VIBE_CODING/">Vibe Coding</a></h3>
<p>To illustrate the difference, here’s how agentic coding compares to the more casual “vibe coding” approach across key dimensions:</p>
<table>
<thead>
<tr>
<th></th>
<th>Agentic Coding</th>
<th>Vibe Coding</th>
</tr>
</thead>
<tbody>
<tr>
<td>Planning</td>
<td>Structured implementation plan</td>
<td>None or minimal upfront thinking</td>
</tr>
<tr>
<td>Prompting</td>
<td>Scoped, intentional, reusable</td>
<td>Loose, improvisational, trial-and-error</td>
</tr>
<tr>
<td>Context</td>
<td>Deliberately curated via files/MCPs</td>
<td>Often missing or overloaded</td>
</tr>
<tr>
<td>Validation</td>
<td>Treated as a critical engineering step</td>
<td>Frequently skipped or shallow</td>
</tr>
<tr>
<td>Output Quality</td>
<td>High, repeatable, aligned to standards</td>
<td>Inconsistent, often needs full rewrite</td>
</tr>
<tr>
<td>Team Scalability</td>
<td>Enables leaner squads with high output</td>
<td>Prone to technical debt and drift</td>
</tr>
</tbody>
</table>
<p>Agentic coding provides the structure, discipline, and scalability that large organizations need to standardize success across multiple squads. It aligns AI workflows with existing engineering quality gates, enabling automation without losing control. In contrast, vibe coding may produce short-term wins but fails to scale under the weight of enterprise demands for predictability, maintainability, and shared accountability.</p>
<h2 id="a-note-on-our-experiment" style="position:relative;"><a href="#a-note-on-our-experiment" aria-label="a note on our experiment permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A Note on Our Experiment</h2>
<p>We ran a structured experiment with two engineering squads working on the same product. One team (DIY) built the product using traditional methods. The other team (AI) used Cursor and GitHub Copilot Agent to complete the same scope, using agentic workflows. The AI team had 30% fewer engineers and delivered in half the time. More importantly, the code quality — verified by SonarQube and human reviewers — was consistent across both teams.</p>
<h2 id="core-practices-that-make-the-difference" style="position:relative;"><a href="#core-practices-that-make-the-difference" aria-label="core practices that make the difference permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Core Practices That Make the Difference</h2>
<h3 id="implementation-planning-is-non-negotiable" style="position:relative;"><a href="#implementation-planning-is-non-negotiable" aria-label="implementation planning is non negotiable permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Implementation Planning is Non-Negotiable</h3>
<p>Before any prompting happens, engineers must do the thinking. Creating an <a href="https://tweag.github.io/agentic-coding-handbook/examples-prompts/create-implementation-plan/PROMPT/">implementation plan</a> isn’t just a formality but the most critical piece in making agentic coding work. It’s where intent becomes design.</p>
<p>A solid implementation plan defines what to build, but also why, how, and within what constraints. It includes:</p>
<ul>
<li><strong>Functional goals</strong>: What should this piece of code do?</li>
<li><strong>Constraints</strong>: Performance expectations, architecture rules, naming conventions, etc.</li>
<li><strong>Edge cases</strong>: Known pitfalls, alternate flows, integration risks.</li>
<li><strong>Required context</strong>: Links to schemas, designs, existing modules, etc.</li>
<li><strong>Step-by-step plan</strong>: Breakdown of the task into scoped units that will become individual prompts.</li>
</ul>
<p>This plan is usually written in markdown and lives inside the codebase. It acts like a contract between the engineer and the AI agent.</p>
<p>The more precise and explicit this document is, the easier it is to turn each unit into a high-quality prompt. This is where agentic coding shifts from “throw a prompt and see what happens” to deliberate system design, supported by AI.</p>
<p>In short, prompting is the act. Planning is the discipline. Without it, you’re not doing agentic coding — you’re just taking shots in the dark and hoping something works.</p>
<h3 id="prompt-engineering-is-a-real-skill" style="position:relative;"><a href="#prompt-engineering-is-a-real-skill" aria-label="prompt engineering is a real skill permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Prompt Engineering is a Real Skill</h3>
<p><a href="https://tweag.github.io/agentic-coding-handbook/PROMPT_ENGINEERING/">Prompt engineering</a> is not about being clever. It’s about being precise, scoped, and iterative. We teach engineers to break down tasks into discrete steps, write action-oriented instructions, avoid vague intentions, chain prompts, and use prompting strategies like:</p>
<ul>
<li><a href="https://tweag.github.io/agentic-coding-handbook/PROMPT_THREE_EXPERTS_METHOD/"><strong>Three Experts</strong></a>: Use this when you want multiple perspectives on a tough design problem. For example, ask the AI to respond as a senior engineer, a security expert, and a performance-focused architect.</li>
<li><a href="https://tweag.github.io/agentic-coding-handbook/PROMPT_ZERO_ONE_N_SHOT_PROMPTS/"><strong>N-Shot Prompting</strong></a>: Provide the AI with N examples of the desired output format or pattern. Zero-shot uses no examples, one-shot provides a single example, and few-shot (N-shot) includes multiple examples to guide the AI toward the expected structure and style.</li>
<li><a href="https://tweag.github.io/agentic-coding-handbook/PROMPT_MULTIPLE_ITERATIONS_REASONING/"><strong>10 Iteration Self-Refinement</strong></a>: Best used when you want the AI to improve its own output iteratively. Give it a problem, then prompt it to improve its previous response 10 times, evaluating each step with reasoning.</li>
</ul>
<p>Choosing the right style depends on the type of challenge you’re tackling — architectural design, implementation, refactoring, or debugging.</p>
<h3 id="context-is-a-first-class-citizen" style="position:relative;"><a href="#context-is-a-first-class-citizen" aria-label="context is a first class citizen permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Context is a First-Class Citizen</h3>
<p>Model Context Providers (<a href="https://tweag.github.io/agentic-coding-handbook/MCPS/">MCPs</a>) give GitHub Copilot a second brain. Instead of treating the LLM as an isolated suggester, MCPs stream relevant context — from Figma designs, documentation in Confluence, code changes from GitHub, and decision logs — directly into the Copilot chat session.</p>
<p>This allows engineers to ask Copilot to write code that matches an actual UI layout, or implements some logic described in a design doc, without manually pasting content into the prompt. The results are significantly more relevant and aligned. Some of the MCPs we use are:</p>
<ul>
<li><strong>GitHub MCP</strong>: Pulls in pull request content and comments to give the model full context for writing review responses, proposing changes, or continuing implementation from feedback.</li>
<li><strong>Figma MCP</strong>: Streams UI layouts into the session, enabling the AI to generate frontend code that accurately reflects the design.</li>
<li><strong>Database Schema MCP</strong>: Injects table structures, column types, and relationships to help the AI write or update queries, migrations, or API models with accurate field-level context.</li>
<li><strong>Memory Bank MCP</strong>: Shares scoped memory across sessions and team members, maintaining continuity of architectural decisions, prompt history, and recent iterations.</li>
<li><strong>CloudWatch MCP</strong>: Supplies log output to the AI for debugging and incident triage — essential during the <strong>Debugging</strong> workflow.</li>
<li><strong>SonarQube MCP</strong>: Feeds static analysis results so the AI can refactor code to eliminate bugs, smells, or duplication.</li>
<li><strong>Confluence MCP</strong>: Integrates architecture and business documentation to inform decisions around domain logic, constraints, and requirements.</li>
</ul>
<p>MCPs are just one part of the context curation puzzle. Engineers also need to deliberately craft the model’s working memory for each session. That includes:</p>
<ul>
<li><strong>Implementation Plans</strong>: Markdown files that define goals, steps, constraints, and trade-offs, acting as an onboarding doc for the AI agent.</li>
<li><strong>Codebase Files</strong>: Selectively attaching relevant parts of the codebase (like entry points, shared utilities, schemas, or config files) so the AI operates with architectural awareness.</li>
<li><strong>Console Logs or Test Output</strong>: Including runtime details helps the AI understand execution behavior and suggest context-aware fixes.</li>
<li><strong>Instructions or TODO Blocks</strong>: GitHub Copilot supports markdown-based instruction files and inline TODO comments to guide its code generation. These instructions act like lightweight tickets embedded directly in the repo. For example, an <code class="language-text">INSTRUCTIONS.md</code> might define architectural rules, file responsibilities, or interface contracts. Within code files, TODOs like <code class="language-text">// TODO: replace mock implementation with production-ready logic</code> act as scoped prompts that Copilot can act on directly. Used consistently, these become in-repo signals that align the agent’s output with team expectations and design intent, markers inside the code to direct the model towards a specific change or design pattern.</li>
</ul>
<p>Effective context curation is an engineering discipline. Give too little, and the agent hallucinates. Give too much, and it loses focus or runs out of space in the LLM context window. The best results come from curating the smallest possible set of high-signal resources. When you treat context as a design artifact the AI becomes a more reliable collaborator.</p>
<h2 id="the-role-of-workflows" style="position:relative;"><a href="#the-role-of-workflows" aria-label="the role of workflows permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Role of Workflows</h2>
<p>We embedded AI in our delivery pipeline using a set of core workflows. You can explore each one in more detail in our handbook, but here is the high-level overview:</p>
<table>
<thead>
<tr>
<th>Workflow</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_SPEC_FIRST_APPROACH/">Spec-First</a></td>
<td>Write a scoped prompt plan before coding</td>
</tr>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_EXPLORATORY/">Exploratory</a></td>
<td>Understand unfamiliar codebases with AI help</td>
</tr>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_MEMORY_BANK/">Memory Bank</a></td>
<td>Maintain continuity across sessions and team members</td>
</tr>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_TDD/">TDD</a></td>
<td>Test-first with AI-generated test coverage</td>
</tr>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_DEBUG/">Debugging</a></td>
<td>Use AI to triage, investigate, and fix bugs</td>
</tr>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_VISUAL_FEEDBACK/">Visual Feedback</a></td>
<td>Align AI output with Figma and screenshots</td>
</tr>
<tr>
<td><a href="https://tweag.github.io/agentic-coding-handbook/WORKFLOW_AUTO_VALIDATIONS/">Auto Validations</a></td>
<td>Run tools like SonarQube, ESLint post-output</td>
</tr>
</tbody>
</table>
<p>In our experience, these workflows are not just productivity boosters; they’re the foundation for scaling AI-assisted development across teams. They provide consistency, repeatability, and shared mental models. We believe this approach is especially critical in enterprise environments, where large engineering organizations require predictable output, quality assurance, and alignment with established standards. Agentic workflows bring just enough structure to harness AI’s strengths without sacrificing accountability or control.</p>
<h2 id="building-a-validation-loop" style="position:relative;"><a href="#building-a-validation-loop" aria-label="building a validation loop permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Building a Validation Loop</h2>
<p>We use <a href="https://tweag.github.io/agentic-coding-handbook/examples-scripts/pre-commitator/">validation tools</a> like SonarQube, ESLint, Vitest, and Prettier to provide automatic feedback to the AI. For example, if SonarQube flags duplication, we prompt the AI to refactor accordingly. This creates a tight loop where validation tools become coaching signals.</p>
<p>Some tools, like GitHub Copilot, can even collect log output from the terminal running tests or executing scripts. This allows the AI to observe the outcome of code execution, analyze stack traces or test failures, and automatically attempt fixes. One common approach is asking the AI to run a test suite, interpret the failed test results, make corrections, and repeat this process until all tests pass.</p>
<p><a href="https://github.com/terryyin/lizard">Lizard</a>, a tool that calculates code complexity metrics, is another useful validation tool. Engineers can instruct the AI to execute Lizard against the codebase. When the output indicates that a function exceeds the defined complexity threshold (typically 10), the AI is prompted to refactor that function into smaller, more maintainable blocks. This method forces the AI to act on specific, measurable quality signals and improves overall code readability.</p>
<p>In this setup, engineers can let the AI operate in a closed loop for several iterations. Once the AI produces clean validation results — whether through passing tests, static analysis, or complexity reduction — the human engineer steps back in to review the result. This combination of automation and oversight speeds up bug fixing while maintaining accountability.</p>
<p>But here’s the thing: the team needs to actually understand what the AI built. If you’re just rubber-stamping AI changes without really getting what they do, you’re setting yourself up for trouble. The review step isn’t just a checkbox — it’s where you make sure the code actually makes sense for your system.</p>
<h2 id="why-human-oversight-still-matters" style="position:relative;"><a href="#why-human-oversight-still-matters" aria-label="why human oversight still matters permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why Human Oversight Still Matters</h2>
<p>No AI is accountable for what goes to production. Engineers are. AI doesn’t own architectural tradeoffs, domain-specific reasoning, or security assumptions. Human-in-the-loop is the safety mechanism.</p>
<p>Humans are the only ones who can recognize when business context changes, when a feature should be cut for scope, or when a security concern outweighs performance gains. AI can assist in code generation, validation, and even debugging — but it lacks the experience, judgment, and ownership required to make trade-offs that affect users, stakeholders, or the long-term health of the system.</p>
<p>Human engineers are also responsible for reviewing the AI’s decisions, ensuring they meet legal, ethical, and architectural constraints. This is especially critical in regulated industries, or when dealing with sensitive data. Without a human to enforce these standards, the risk of silent failure increases dramatically.</p>
<p>Agentic coding isn’t about handing off responsibility, it’s about amplifying good engineering judgment.</p>
<h2 id="where-people-fail-and-blame-the-ai" style="position:relative;"><a href="#where-people-fail-and-blame-the-ai" aria-label="where people fail and blame the ai permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Where People Fail (And Blame the AI)</h2>
<p>Common mistakes include vague prompts, lack of planning, poor context, and not validating output. While LLMs have inherent limitations — they hallucinate, make incorrect assumptions, and produce plausible-sounding but wrong outputs even with good inputs — engineering discipline significantly increases the reliability of results.</p>
<p>A prompt like “make this better” tells the AI nothing about what “better” means — faster? more readable? safer? Without clear constraints and context, LLMs default to producing generic solutions that may not align with your actual needs. The goal isn’t to eliminate all AI errors, but to create workflows that catch and correct them systematically.</p>
<p>Lack of validation is another key failure mode. Trusting the first output, skipping tests, or ignoring code quality tools defeats the point of the feedback loop. AI agents need boundaries and coaching signals or, without them, they can drift into plausible nonsense.</p>
<p>Using these tools effectively also means understanding their current limitations. AI models work best with well-represented programming languages like JavaScript, TypeScript, and Python (to name a few examples). However, teams working in <a href="https://newsletter.pragmaticengineer.com/i/167269400/biotech-ai-startup">specialized domains</a> may see limited results even with popular languages.</p>
<h2 id="a-closer-look-at-our-tooling" style="position:relative;"><a href="#a-closer-look-at-our-tooling" aria-label="a closer look at our tooling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A Closer Look at Our Tooling</h2>
<p>GitHub Copilot played a key role in our experiment, especially when paired with <strong>instruction files</strong>, <strong>validation scripts</strong>, and <strong>Model Context Providers (MCPs)</strong>.</p>
<p>What made GitHub Copilot viable for agentic workflows wasn’t just its autocomplete or inline chat. It was how we surrounded it with structure and feedback mechanisms:</p>
<h3 id="instruction-files" style="position:relative;"><a href="#instruction-files" aria-label="instruction files permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Instruction Files</h3>
<p><a href="https://tweag.github.io/agentic-coding-handbook/PRJ_INSTRUCTIONS/">Instruction files</a> served as the AI’s map. These markdown-based guides detailed the implementation plan, scoped tasks, architectural constraints, naming conventions, and even file-level goals. When placed inside the repo, they gave GitHub Copilot context it otherwise wouldn’t have. Unlike ad-hoc prompts, these files were written with intent and discipline, and became a critical part of the repo’s knowledge layer.</p>
<h3 id="validation-scripts" style="position:relative;"><a href="#validation-scripts" aria-label="validation scripts permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Validation Scripts</h3>
<p>We paired Copilot with post-generation validation tools like ESLint, Vitest, Horusec, and SonarQube. These weren’t just guardrails but closers of the loop. When Copilot generated code that violated rules or failed tests, engineers would reframe the prompt with validation results as input. This prompted Copilot to self-correct. It’s how we turned passive AI output into an iterative feedback process.</p>
<h3 id="copilot--workflows--impact" style="position:relative;"><a href="#copilot--workflows--impact" aria-label="copilot  workflows  impact permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Copilot + Workflows = Impact</h3>
<p>Used this way, GitHub Copilot became more than a helper. It became a participant in our structured workflows:</p>
<ul>
<li>In <strong>Spec-First</strong>, Copilot consumed instruction files to scaffold code.</li>
<li>In <strong>Debugging</strong>, it analyzed logs fed via MCP and proposed targeted fixes.</li>
<li>In <strong>TDD</strong>, it generated unit tests from requirements, then refactored code until tests passed.</li>
<li>In <strong>Visual Feedback</strong>, it aligned components with Figma via the design MCP.</li>
</ul>
<p>By aligning Copilot with prompts, plans, validation, and context, we moved from “code completion” to <strong>code collaboration</strong>.</p>
<p>So no — GitHub Copilot isn’t enough on its own. But when embedded inside a disciplined workflow, with context and feedback flowing in both directions, it’s a capable agent. One that gets better the more structured your engineering practice becomes.</p>
<h2 id="final-advice-how-to-actually-start" style="position:relative;"><a href="#final-advice-how-to-actually-start" aria-label="final advice how to actually start permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Final Advice: How to Actually Start</h2>
<p>The path to agentic coding begins with a single, well-chosen task. Pick something atomic that you understand deeply — a function you need to refactor, a component you need to build, or a bug you need to fix. Before touching any AI tool, write an implementation plan that defines your goals, constraints, and step-by-step approach.</p>
<p>Once you have your plan, start experimenting with the workflows we’ve outlined. Try <strong>Spec-First</strong> to scaffold your implementation, then use <strong>Auto Validations</strong> to create feedback loops. If you’re working with UI, explore <strong>Visual Feedback</strong> with design tools. As you gain confidence, introduce <strong>Model Context Providers</strong> to give your AI agent richer context about your codebase and requirements. Always keep in mind that the quality of AI output depends on the quality of the task setup and the availability of feedback.</p>
<p>Treat each interaction as both an experiment and a learning opportunity. Validate every output as if it came from a junior developer. Most importantly, remember that this isn’t about replacing your engineering judgment; it’s about amplifying it. The most successful engineers in our experiments were the ones who treated the AI as a collaborator — not a magician.</p>
<p>What we’ve described isn’t just a productivity technique — it’s a fundamental shift in how we think about human creativity and machine capability. When engineers become high-level problem solvers, supported by AI agents within well-defined boundaries, we unlock new possibilities for what software teams can accomplish. Welcome to the next era of software development.</p>]]></description><link>https://tweag.io/blog/2025-10-23-agentic-coding-intro/</link><guid isPermaLink="false">https://tweag.io/blog/2025-10-23-agentic-coding-intro/</guid><pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Single-line and multi-line formatting with Topiary]]></title><description><![CDATA[<ol>
<li><a href="https://www.tweag.io/blog/2025-01-30-topiary-tutorial-part-1/">Writing a formatter has never been so easy: a Topiary tutorial</a></li>
<li>Single-line and multi-line formatting with Topiary</li>
</ol>
<p>In a <a href="https://www.tweag.io/blog/2025-01-30-topiary-tutorial-part-1/">previous post</a>, I introduced <a href="https://github.com/tweag/topiary">Topiary</a>, a
universal formatter (or one could say <em>a formatter generator</em>), and showed how
to start a formatter for a programming language from scratch. This post is the
second part of the tutorial, where we’ll explore more advanced features of
Topiary that come in handy when handling real-life languages, and in particular
the single-line and multi-line layouts. I’ll assume that you have a working
setup to format our toy Yolo language. If you don’t, please follow the relevant
sections of the <a href="https://www.tweag.io/blog/2025-01-30-topiary-tutorial-part-1/">previous post</a> first.</p>
<h2 id="single-line-and-multi-line" style="position:relative;"><a href="#single-line-and-multi-line" aria-label="single line and multi line permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Single-line and multi-line</h2>
<p>A fundamental tenet of formatting is that you want to lay code out in different
ways depending on if it fits on one line or not. For example, in
<a href="https://nickel-lang.org">Nickel</a>, or any functional programming language for that matter, it’s
idiomatic to write small anonymous functions on one line, as in <code class="language-text">std.array.map (fun x => x * 2 + 1) [1,2,3]</code>. But longer functions would rather look like:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token keyword">fun</span><span class="token operator"> </span><span class="token operator">x</span><span class="token operator"> </span><span class="token operator">y</span><span class="token operator"> </span><span class="token operator">z</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token keyword">if</span><span class="token operator"> </span><span class="token operator">x</span><span class="token operator"> </span><span class="token keyword">then</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator">y</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token keyword">else</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator">z</span></code></pre></div>
<p>This is true for almost any language construct that you can think of: you’d
write a small boolean condition <code class="language-text">is_a &amp;&amp; is_b</code>, but write a long validation
expressions as:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span>
<span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">></span><span class="token operator"> </span><span class="token number">5</span>
<span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">&lt;</span><span class="token operator"> </span><span class="token number">10</span>
<span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">!</span><span class="token operator">(</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">m</span><span class="token operator">a</span><span class="token operator">t</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator"> </span><span class="token string">"\\d"</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span><span class="token operator">)</span></code></pre></div>
<p>In Rust, with <code class="language-text">rustfmt</code>, short method calls are formatted on one line as in
<code class="language-text">x.clone().unwrap().into()</code>, but they are spread over several lines when the
line length is over a fixed threshold:</p>
<div class="gatsby-highlight" data-language="rust"><pre class="language-rust"><code class="language-rust">value
    <span class="token punctuation">.</span><span class="token function">maybe_do_something</span><span class="token punctuation">(</span><span class="token closure-params"><span class="token closure-punctuation punctuation">|</span>x<span class="token closure-punctuation punctuation">|</span></span> x<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">or_something_else</span><span class="token punctuation">(</span><span class="token closure-params"><span class="token closure-punctuation punctuation">|</span>_<span class="token closure-punctuation punctuation">|</span></span> <span class="token class-name">Err</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">into_iter</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre></div>
<p>You usually either want the single-line layout or the multi-line one. A hybrid
solution wouldn’t be very consistent:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span>
<span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">></span><span class="token operator"> </span><span class="token number">5</span><span class="token operator"> </span><span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">&lt;</span><span class="token operator"> </span><span class="token number">10</span>
<span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">!</span><span class="token operator">(</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">m</span><span class="token operator">a</span><span class="token operator">t</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator"> </span><span class="token string">"\\d"</span><span class="token operator"> </span><span class="token operator">v</span><span class="token operator">a</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">e</span><span class="token operator">)</span></code></pre></div>
<p>Some formatters, such as Rust’s, choose the layout automatically depending
on the length of the line. Long lines are wrapped and laid out in the multi-line
style automatically, freeing the programmer from any micro decision. On the flip
side, the programmer can’t force one style in cases where it’d make more sense.</p>
<p>Some other formatters, like our own <a href="https://github.com/tweag/ormolu">Ormolu</a> for Haskell, decide on the
layout based on the original source code. For any syntactic construct, the
programmer has two options:</p>
<ol>
<li>Write it on one line, or</li>
<li>Write it on two lines or more.</li>
</ol>
<p>1. will trigger the single-line layout, and 2. the multi-line one. No
effort is made to try to fit within reasonable line lengths. That’s up to the
programmer.</p>
<p>As we will see, Topiary follows the same approach as Ormolu, although future
support for optional line wrapping isn’t off the table<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>.</p>
<h2 id="softlines" style="position:relative;"><a href="#softlines" aria-label="softlines permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Softlines</h2>
<h3 id="less-line-breaks-please" style="position:relative;"><a href="#less-line-breaks-please" aria-label="less line breaks please permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Less line breaks, please</h3>
<p>Let’s see how our Yolo formatter handles the following source:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">input income, status
output income_tax

income_tax := case { status = &quot;exempted&quot; =&gt; 0, _ =&gt; income * 0.2 }</code></pre></div>
<p>Since the <code class="language-text">case</code> is short, we want to keep it single-line. Alas, this gets
formatted as:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">input income, status
output income_tax

income_tax := case {
  status = &quot;exempted&quot; =&gt; 0,
  _ =&gt; income * 0.2
}</code></pre></div>
<p>The simplest mechanism for multi-line-aware layout is to use <a href="https://topiary.tweag.io/book/reference/capture-names/vertical-spacing.html#hardlines-and-softlines">soft
lines</a> instead of spaces or hardlines. Let’s change the
<code class="language-text">@append_hardline</code> capture in the <a href="https://github.com/tweag/blog-resources/blob/936743d750f161467abc18accf5caff42f790eaf/topiary-tutorial-part-1/tree-sitter-yolo/yolo.scm#L37-L42">case branches separating
rule</a> to <code class="language-text">@append_spaced_softline</code>:</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; Put case branches on their own lines</span>
<span class="token punctuation">(</span><span class="token keyword">case</span>
  <span class="token string">","</span> @append_spaced_softline
<span class="token punctuation">)</span></code></pre></div>
<p>As the name indicates, a spaced softline will result in a space for the
single-line case, and a line break for the multi-line case, which is precisely
what we want. However, if we try to format our example, we get the dreaded
idempotency check failure, meaning that formatting one time or two times in a
row doesn’t give the same result, which is a usually a red flag (and is why
Topiary performs this check). What happens is that our braces <code class="language-text">{</code> and <code class="language-text">}</code> also
introduce hardlines, so the double formatting goes like:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">income_tax := case { status = "exempted" => 0, _ => income * 0.2 }

--> (case is single-line: @append_spaced_softline is a space)
income_tax := case {
  status = "exempted" => 0, _ => income * 0.2
}
--> (case is multi-line! @append_spaced_softline is a line break)
income_tax := case {
  status = "exempted" => 0,
  _ => income * 0.2
}</code></pre></div>
<p>We need to amend the <a href="https://github.com/tweag/blog-resources/blob/936743d750f161467abc18accf5caff42f790eaf/topiary-tutorial-part-1/tree-sitter-yolo/yolo.scm#L44-L48">rule for braces</a> as well:</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; Lay out the case skeleton</span>
<span class="token punctuation">(</span><span class="token keyword">case</span>
  <span class="token string">"{"</span> @prepend_space @append_spaced_softline
  <span class="token string">"}"</span> @prepend_spaced_sofline
<span class="token punctuation">)</span></code></pre></div>
<p>Our original example is now left untouched, as desired. Note that softline
annotations are expanded depending on the multi-lineness <strong>of the direct parent of
the node they attach to</strong> (and neither the subtree matched by the whole query
nor the node itself). Topiary applies this logic because this is most often what
you want. The parse tree of the multi-line version of <code class="language-text">income_tax</code>:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">income_tax := case {
  status = &quot;exempted&quot; =&gt; 0,
  _ =&gt; income * 0.2
}</code></pre></div>
<p>is as follows (hiding irrelevant parts in <code class="language-text">[...]</code>):</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">0:0  - 4:0    tax_rule
0:0  - 3:1      statement
0:0  - 3:1        definition_statement
0:0  - 0:10         identifier `income_tax`
0:11 - 0:13         ":="
0:14 - 3:1          expression
0:14 - 3:1            case
0:14 - 0:18             "case"
0:19 - 0:20             "{"
1:2  - 1:26             case_branch
                        [...]
1:26 - 1:27             ","
2:2  - 2:19             case_branch
                        [...]
3:0  - 3:1              "}"</code></pre></div>
<p>The left part is the span of the node, in the format <code class="language-text">start_line:start_column - end_line:end_column</code>. A node is multiline simply if <code class="language-text">end_line > start_line</code>. You
can see that since <code class="language-text">"{"</code> is not multiline (it can’t be, as it’s only one
character!), if Topiary considered the multi-lineness of the node itself, our
previous <code class="language-text">"{" @append_spaced_softline</code> would always act as a space.</p>
<p>What happens is that Topiary considers the direct parent instead, which is <code class="language-text">0:14 - 3:1 case</code>
here, and is indeed multi-line.</p>
<p>Both single-line and multi-line <code class="language-text">case</code> are now formatted as expected.</p>
<h3 id="more-line-breaks-please" style="position:relative;"><a href="#more-line-breaks-please" aria-label="more line breaks please permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>More line breaks, please</h3>
<p>Let’s consider the dual issue, where line breaks are unduly removed. We’d like
to allow inputs and outputs to span multiple lines, but the following snippet:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">input
  income,
  status,
  tax_coefficient
output income_tax</code></pre></div>
<p>is formatted as:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">input income, status, tax_coefficient
output income_tax</code></pre></div>
<p>The <a href="https://github.com/tweag/blog-resources/blob/936743d750f161467abc18accf5caff42f790eaf/topiary-tutorial-part-1/tree-sitter-yolo/yolo.scm#L22-L26">rule for spacing around <code class="language-text">input</code> and
<code class="language-text">output</code></a> and the <a href="https://github.com/tweag/blog-resources/blob/936743d750f161467abc18accf5caff42f790eaf/topiary-tutorial-part-1/tree-sitter-yolo/yolo.scm#L28-L35">rule for spacing around
<code class="language-text">,</code> and identifiers</a> both use <code class="language-text">@append_space</code>. We
can simply replace this with a spaced softline. Recall that a spaced softline
turns into a space and thus behaves like <code class="language-text">@append_space</code> in a single-line
context, making it a proper substitution.</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; Add spaced softline after `input` and `output` decl</span>
<span class="token punctuation">[</span>
  <span class="token string">"input"</span>
  <span class="token string">"output"</span>
<span class="token punctuation">]</span> @append_spaced_softline


<span class="token comment">; Add a spaced softline after and remove space before the comma in an identifier</span>
<span class="token comment">; list</span>
<span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token function">identifier</span><span class="token punctuation">)</span>
  .
  <span class="token string">","</span> @prepend_antispace @append_spaced_softline
  .
  <span class="token punctuation">(</span><span class="token function">identifier</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span></code></pre></div>
<p>We also need to add new rules to indent multi-line lists of inputs or outputs.</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; Indent multi-line lists of inputs.</span>
<span class="token punctuation">(</span><span class="token function">input_statement</span>
  <span class="token string">"input"</span> @append_indent_start
<span class="token punctuation">)</span> @append_indent_end

<span class="token comment">; Indent multi-line lists of outputs.</span>
<span class="token punctuation">(</span><span class="token function">output_statement</span>
  <span class="token string">"output"</span> @append_indent_start
<span class="token punctuation">)</span> @append_indent_end</code></pre></div>
<p>A matching pair of indentation captures <code class="language-text">*_indent_start</code> and <code class="language-text">*_indent_end</code> will
amount to a no-op if they are on the same line, so those rules don’t disturb the
single-line layout.</p>
<p>Recall that as long as you don’t use anchors (<code class="language-text">.</code>), additional nodes can be
omitted from a Tree-sitter query: here, the first query will match an input
statement with an <code class="language-text">"input"</code> child somewhere, and any children before or after
that (although in our case, there won’t be any children before).</p>
<h2 id="scopes" style="position:relative;"><a href="#scopes" aria-label="scopes permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Scopes</h2>
<h3 id="more-scoped-line-breaks-please" style="position:relative;"><a href="#more-scoped-line-breaks-please" aria-label="more scoped line breaks please permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>More (scoped) line breaks, please</h3>
<p>Let us now consider a similar example, at least on the surface. We want to allow
long arithmetic expressions to be laid out on multiple lines as well, as in:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">input
  some_long_name,
  other_long_name,
  and_another_one
output result

result :=
  some_long_name
  + other_long_name
  + and_another_one</code></pre></div>
<p>As before, <code class="language-text">result</code> is currently smashed back into one line by our current
formatter. Unsurprisingly, since our <a href="https://github.com/tweag/blog-resources/blob/936743d750f161467abc18accf5caff42f790eaf/topiary-tutorial-part-1/tree-sitter-yolo/yolo.scm#L7-L20">keywords rule</a> uses
<code class="language-text">@prepend_space</code> and <code class="language-text">@append_space</code>. At this point, you start to get the trick:
let’s use softlines! I’ll only handle <code class="language-text">+</code> for simplicity. We remove <code class="language-text">"+"</code> from
the original keywords rule and add the following rule:</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; (Multi-line) spacing around +</span>
<span class="token punctuation">(</span><span class="token string">"+"</span> @prepend_spaced_softline @append_space<span class="token punctuation">)</span></code></pre></div>
<p>Ignoring indentation for now, the line wrapping seems to work. For the following
example at least:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">result :=
  some_long_name
  + other_long_name + and_another_one</code></pre></div>
<p>which is reformatted as:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">result := some_long_name
+ other_long_name
+ and_another_one</code></pre></div>
<p>However, perhaps surprisingly, the following example:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">result :=
some_long_name + other_long_name
+ and_another_one</code></pre></div>
<p>is reformatted as:</p>
<div class="gatsby-highlight" data-language="yolo"><pre class="language-yolo"><code class="language-yolo">result := some_long_name + other_long_name
+ and_another_one</code></pre></div>
<p>The first addition hasn’t been split! To understand why, we have to look at how
our grammar parses arithmetic expressions:</p>
<div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token function-variable function">expression</span><span class="token operator">:</span> <span class="token parameter">$</span> <span class="token operator">=></span> <span class="token function">choice</span><span class="token punctuation">(</span>
  $<span class="token punctuation">.</span>identifier<span class="token punctuation">,</span>
  $<span class="token punctuation">.</span>number<span class="token punctuation">,</span>
  $<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
  $<span class="token punctuation">.</span>arithmetic_expr<span class="token punctuation">,</span>
  $<span class="token punctuation">.</span>case<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">,</span>

<span class="token function-variable function">arithmetic_expr</span><span class="token operator">:</span> <span class="token parameter">$</span> <span class="token operator">=></span> <span class="token function">choice</span><span class="token punctuation">(</span>
  prec<span class="token punctuation">.</span><span class="token function">left</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token function">seq</span><span class="token punctuation">(</span>
    $<span class="token punctuation">.</span>expression<span class="token punctuation">,</span>
    <span class="token function">choice</span><span class="token punctuation">(</span><span class="token string">'+'</span><span class="token punctuation">,</span> <span class="token string">'-'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    $<span class="token punctuation">.</span>expression<span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  prec<span class="token punctuation">.</span><span class="token function">left</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">seq</span><span class="token punctuation">(</span>
    $<span class="token punctuation">.</span>expression<span class="token punctuation">,</span>
    <span class="token function">choice</span><span class="token punctuation">(</span><span class="token string">'*'</span><span class="token punctuation">,</span> <span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    $<span class="token punctuation">.</span>expression<span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">prec</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token function">seq</span><span class="token punctuation">(</span>
    <span class="token string">'('</span><span class="token punctuation">,</span>
    $<span class="token punctuation">.</span>expression<span class="token punctuation">,</span>
    <span class="token string">')'</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">,</span></code></pre></div>
<p>Even if you don’t understand everything, there are two important points:</p>
<ol>
<li>Arithmetic expressions are <strong>recursively nested</strong>. Indeed, we can compose
arbitrarily complex expressions, as in <code class="language-text">(foo*2 + 1) + (bar / 4 * 6)</code>.</li>
<li>They are parsed in a left-associative way.</li>
</ol>
<p>This means that our big addition is parsed as: <code class="language-text">((some_long_name "+" other_long_name) "+" and_another_one)</code>. In the first example, since the line
break happens just after <code class="language-text">some_long_name</code> in the original source, both the inner
node and the outer one are multi-line. However, in the second example, the line
break happens <em>after</em> <code class="language-text">other_long_name</code>, meaning that the innermost arithmetic
expression is contained in a single line, and the corresponding <code class="language-text">+</code> isn’t
considered multi-line. Indeed, you can see here that the parent of the first <code class="language-text">+</code>
is <code class="language-text">7:0 - 7:32 arithmetic_expr</code>, which fits entirely on line <code class="language-text">7</code>.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">7:0  - 8:17           arithmetic_expr
7:0  - 7:32             expression
7:0  - 7:32               arithmetic_expr
7:0  - 7:14                 expression
7:0  - 7:14                   identifier `some_long_name`
7:15 - 7:16                 "+"
7:17 - 7:32                 expression
7:17 - 7:32                   identifier `other_long_name`
8:0  - 8:1              "+"
8:2  - 8:17             expression
8:2  - 8:17               identifier `and_another_one`</code></pre></div>
<p>The solution here is to use <em>scopes</em>. A scope is a user-defined group of nodes
associated with an identifier. Crucially, when using scoped softline captures
such as <code class="language-text">@append_scoped_space_softline</code> within a scope, Topiary will consider
the multi-lineness of the <em>whole scope</em> instead of the multi-lineness of the
(parent) node.</p>
<p>Let’s create a scope for all the nested sub-expressions of an arithmetic
expression. Scopes work the same as other node groups in Topiary: we create them
by using a matching pair of <code class="language-text">begin</code> and <code class="language-text">end</code> captures. We need to find a parent
node that can’t occur recursively in an arithmetic expression. A good candidate
would be <a href="https://github.com/tweag/blog-resources/blob/936743d750f161467abc18accf5caff42f790eaf/topiary-tutorial-part-1/tree-sitter-yolo/grammar.js#L38-L42"><code class="language-text">definition_statement</code></a>, which
encompasses the whole right-hand side of the definition of an output:</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; Creates a scope for the whole right-hand side of a definition statement</span>
<span class="token punctuation">(</span><span class="token function">definition_statement</span>
  <span class="token punctuation">(</span><span class="token function">#scope_id!</span> <span class="token string">"definition_rhs"</span><span class="token punctuation">)</span>
  <span class="token string">":="</span>
  <span class="token punctuation">(</span><span class="token function">expression</span><span class="token punctuation">)</span> @prepend_begin_scope @append_end_scope
<span class="token punctuation">)</span></code></pre></div>
<p>We must specify an identifier for the scope using the
<a href="https://tree-sitter.github.io/tree-sitter/using-parsers/queries/3-predicates-and-directives.html#predicates">predicate</a> <code class="language-text">scope_id</code>. Identifiers are useful when
several scopes might be nested or even overlap, and help readability in general.</p>
<p>We then amend our initial attempt at formatting multi-line arithmetic
expressions:</p>
<div class="gatsby-highlight" data-language="scheme"><pre class="language-scheme"><code class="language-scheme"><span class="token comment">; (Multi-line) spacing around +</span>
<span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token function">#scope_id!</span> <span class="token string">"definition_rhs"</span><span class="token punctuation">)</span>
  <span class="token string">"+"</span> @prepend_scoped_spaced_softline @append_space
<span class="token punctuation">)</span></code></pre></div>
<p>We use a <code class="language-text">scoped</code> version of softlines, in which case we need to specify the
identifier of the corresponding scope. The captured node must also be part of
said scope. You can check that both examples (and multiple variations of them)
are finally formatted as expected.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>This second part of the Topiary tutorial has taught how to finely specify an
alternative formatting layout depending on whether an expression spans multiple
lines or not. The main concepts at play here are multi-line versus single-line
nodes, and scopes. There is an extension to this concept not covered here,
<a href="https://topiary.tweag.io/book/reference/capture-names/scopes.html#measuring-scopes">measuring scopes</a>, but standard scopes already go a
long way for formatting a real life language. If you’re looking for a
comprehensive resource to help you write your formatter, the official <a href="https://topiary.tweag.io/book/index.html">Topiary
book</a> is for you. You can however find the complete code for this
post in the <a href="https://github.com/tweag/blog-resources/blob/master/topiary-tutorial-part-2/">companion repository</a>. Happy hacking!</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">See <a href="https://github.com/tweag/topiary/issues/700">#700</a><a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-10-02-topiary-tutorial-part-2/</link><guid isPermaLink="false">https://tweag.io/blog/2025-10-02-topiary-tutorial-part-2/</guid><pubDate>Thu, 02 Oct 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Managing dependency graph in a large codebase]]></title><description><![CDATA[<p>This is the second in a series of three companion blog posts about dependency graphs.
These blog posts explore the key terminology, graph theory concepts,
and the challenges of managing large graphs and their underlying complexity.</p>
<ol>
<li><a href="../2025-09-04-introduction-to-dependency-graph"><em>Introduction to the dependency graph</em></a></li>
<li><em>Managing dependency graph in a large codebase</em></li>
<li><a href="../2025-12-04-the-anatomy-of-a-dependency-graph"><em>The anatomy of a dependency graph</em></a></li>
</ol>
<hr>
<p>In the <a href="https://www.tweag.io/blog/2025-09-04-introduction-to-dependency-graph/">previous post</a>,
we explored the concepts of the dependency graph
and got familiar with some of its applications in the context of build systems.
We also observed that managing dependencies can be complicated.</p>
<p>In this post, we are going to take a closer look at some of the issues
you might need to deal with when working in a large codebase,
such as having incomplete build metadata
or conflicting requirements between components.</p>
<h2 id="common-issues" style="position:relative;"><a href="#common-issues" aria-label="common issues permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Common issues</h2>
<h3 id="diamond-dependency" style="position:relative;"><a href="#diamond-dependency" aria-label="diamond dependency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Diamond dependency</h3>
<p>The <em>diamond dependency</em> problem is common in large projects,
and resolving it often requires careful dependency version management
or deduplication strategies.</p>
<p>Imagine you have these dependencies in your project:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 581px; ">
      <a class="gatsby-resp-image-link" href="/static/bc65c3d141af8fd3ede49475df3280f3/92d15/graph1.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 87.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACG0lEQVQ4y4WT6W4iMRCE9/0fbCMl+ZWNkIhIIMDcJ3Mz4/a3agfYARKtJctXu1xV3f4FYK3lq9mbftq1/+b/i/l1ntQHQ5kLbQ1NBVVpKTLDsRd3Po2WMjfUh69zHQ+FUJUGEXsBPQFaskTw/YnXV4+3t5TttiGO9bJxwX1rSBN4fy9ZLAJWqxzPG8gSfUzuAdNYAQ2LRchqVbBeV3eASQwfHweWy8QB7vcDaWK/A8RRL3PrZGw+MidJJQ8zybpWuf6+Igq6k+wbybemixjyPGXerpMGTVPRds3V3l1SdNSXxAh13WAFtz5boqN1axiGI303uPn8/pXk4yBEvvokxJEhDoU8NafALy4qz8VEQhIZQs/Qd/I9Q/VKTV8uI56fV0SRociUtVwAy0KIIsvT0xt/XnfEEXTtD4Bab2EgBP7I56ZxF7WUzjIdYG4IA8tu27qSCTz5GVCMpW+FuhrZrAOGjlOGLXNbhh4CLyOJKxczTTeA919LaNrqLsvzmHEcOB77S5bnZxeGWmfKcOgsZdEx9NYZPs+iMtb9ph5d19jx+INkzaD6s98NfG5aAt8QeMY9dLYkDo3zefvZsVnXBL6QJ2amguufkmXw8rLl98OC3b51WT9/K2MsWWoJo4mHhwWPj0uSVH/T2WeuAZtKXA3mqSXPcJe11sx0YijWsUkTcedFjotXZd8y1PIwk1x3c224yr6Nmf9j9fAvDtcs+/NxyWMAAAAASUVORK5CYII='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/bc65c3d141af8fd3ede49475df3280f3/92d15/graph1.png" srcset="/static/bc65c3d141af8fd3ede49475df3280f3/12f09/graph1.png 148w,
/static/bc65c3d141af8fd3ede49475df3280f3/e4a3f/graph1.png 295w,
/static/bc65c3d141af8fd3ede49475df3280f3/92d15/graph1.png 581w" sizes="(max-width: 581px) 100vw, 581px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Packaging <code class="language-text">appA</code> and <code class="language-text">appB</code> individually is not a problem
because they will end up having <code class="language-text">libX</code> of a particular version.
But what if <code class="language-text">appA</code> starts using something from <code class="language-text">libB</code> as well?
Now when building <code class="language-text">appA</code>, it is unclear
what version of <code class="language-text">libX</code> should be used — <code class="language-text">v1</code> or <code class="language-text">v2</code>.
This results in having a part of the dependency graph looking
like a diamond hence the dependency name.</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/cc835c6bcb5f857c92d26bccb398e632/d9217/graph2.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 98.64864864864865%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACiklEQVQ4y42U607bQBCF8/6P0zeo1P6DqqhBtAk40Ca2187FF7C9l9mv2jUkhkBVS6Mdz5k5PjM78sx7j7WPWNt8aMY0/8RHqxHRzJwzGLNEZIn3wRZHE1kACUnymYuLT2h9AyyRSU7M80uc+4UxRSDUOHcXC3X/gO5/o4fRhv4BMzzEDw7DDdasYuwFD7lD94Cz98AtxpSjwkAYkot8QOWWLB3IM02hhFJ1OLMC7qgPCqX8EVe5QeVCfSiAxTnhthRu7youLxPm85T1xrDbOoxOYqv1oWS7hctvCd+vfnPzU1GWxDj8ek3o7IoiaynUQJH3FHmHygdK1WJNEltqDhkqKI/4c06maap8qvA0Q3ETkxX4e0SSiAXzPsHLCu9Xx5xwhvg4w4KZiEHrJdYuzqxtr46+MQucW9J1c7ruOvrTXGN+onXJLOyhiCUQhzOMwHtH1z2Spuvoj5gBhLo+oFQW/ZA71o713gszPngen9qPIAbdY61+F4sKg0FQ6mlrR1N5qr2lqYSmcojzMbl/Cisi1FU4XfR1L7H2hScqHAnBWWFbeNZrzfxHGs9CeYyWiFc7h8rhep6xXO5RiihgyvGa0Ai7LaSZ4cd8zSYd2JYcCYOqsHfX1ym3t7u4k23zD8LQcmyl8qzutmN7B4d7bvmpDeOAdNOSbR6jP3RyTjgNhHmEZ7/b4Zx9M/IRq6uKvu+OsbH0HYWnCwpqw4rUGBMUCtZKvJy2faLrukn+lPRdwtN7fTBkqeGwh2oPZSHk6XAcwVTAWcvT1l++GtZmtyde0Jevc9abjv3udElTwuMevrecJ4Uurs36T88qaeKvqlSCNfJm7v9JaI2Py6x7MAP0nTD0ctbmlPAvnDYPqvJjt3sAAAAASUVORK5CYII='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/cc835c6bcb5f857c92d26bccb398e632/fcda8/graph2.png" srcset="/static/cc835c6bcb5f857c92d26bccb398e632/12f09/graph2.png 148w,
/static/cc835c6bcb5f857c92d26bccb398e632/e4a3f/graph2.png 295w,
/static/cc835c6bcb5f857c92d26bccb398e632/fcda8/graph2.png 590w,
/static/cc835c6bcb5f857c92d26bccb398e632/efc66/graph2.png 885w,
/static/cc835c6bcb5f857c92d26bccb398e632/d9217/graph2.png 904w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Depending on the programming language and the packaging mechanisms, it might be possible to specify
that when calls are made from <code class="language-text">libA</code>, then <code class="language-text">libX.v1</code> should be used,
and when calls are made from <code class="language-text">libB</code>, then <code class="language-text">libX.v2</code> should be used,
but in practice it can get quite complicated.
The worst situation is perhaps when <code class="language-text">appA</code> is compatible with both <code class="language-text">v1</code> and <code class="language-text">v2</code>,
but may suffer from intermittent failures when being used in certain conditions such as under high load.
Then you would actually be able to build your application,
and since it includes a “build compatible” yet different version of the third-party library,
you won’t be able to spot the issue straight away.</p>
<p>Some tools, such as the functional package manager <a href="https://nixos.org/">nix</a>, treat packages as immutable values
and allow you to specify exact versions of dependencies for each package,
and these can coexist without conflict.</p>
<p>Having a single set of requirements can also be desirable,
because if all the code uses the same versions of required libraries,
you avoid version conflicts entirely
and everyone in the company works with the same dependencies, reducing “works on my machine”-type issues.
In practice, however, this is often unrealistic for large or complex projects,
especially in large monorepos or polyglot codebases.
For instance, upgrading a single dependency may require updating many parts of the codebase at once,
which might be risky and time-consuming.
Likewise, if you want to split your codebase into independently developed modules or services,
a single requirements set can become a bottleneck.</p>
<h3 id="re-exports" style="position:relative;"><a href="#re-exports" aria-label="re exports permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Re-exports</h3>
<p>Re-exports — when a module imports a member from another module and re-exports it —
are possible in some languages such as Python or JavaScript.</p>
<p>Take a look at this graph</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/787cf8488408f1767cfe9c604332ce97/ab3e6/graph3.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABrElEQVQoz5WTiW7bQAxE9f9fFwRB4iuVA7eKZFv3uRdfwfWRpC3QlgC1FzkagsOk7wJ1BeXZU+QLTQ1tHRARzCJMQ4junBCCMI2BaRTmMaBmjDDGGMFaIWkb4XBYeHrKeHw8kKY95VmQIDS1sNs17Pcj8wTOClnmWG9KjoUCCn0H201Dmg6MAyR9J/z4PpF+q3jbtxwOA3V1Yagg71lPVVqsUYZQVy7ead6lCo0ZOB+XuE+CVzbgPXTdiLUB7y7lqDm34L29n0UcxszXveC9Z54nhEtOcg27B1hrWJaFZZkZex+Z1JVh6CWW1NSOrg30rQLNGGMIIdzzE/3c/Ip7eQzaLE+WWYrCs92WrNcn8lzvDKdjiE36YC4fDG+Hr+BCUwmvu5bNpmT1cuLluWC9Osd9kfsroHzJT/jFbo8KqF21FryDefY09UDw2m1ikz4D3ew3wM8B2jBNVH3pOk02/kS1592N3V8A7yUDQxd4zwKnE+zTnu224lgIRS5RWoom/85Qp4Io6tXqGEX/8PDG83PO7rVBB0L+D5A4el2jEgkMPdFVMm3j4wj+qeSfDqunQ2LlAQYAAAAASUVORK5CYII='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/787cf8488408f1767cfe9c604332ce97/fcda8/graph3.png" srcset="/static/787cf8488408f1767cfe9c604332ce97/12f09/graph3.png 148w,
/static/787cf8488408f1767cfe9c604332ce97/e4a3f/graph3.png 295w,
/static/787cf8488408f1767cfe9c604332ce97/fcda8/graph3.png 590w,
/static/787cf8488408f1767cfe9c604332ce97/efc66/graph3.png 885w,
/static/787cf8488408f1767cfe9c604332ce97/c83ae/graph3.png 1180w,
/static/787cf8488408f1767cfe9c604332ce97/ab3e6/graph3.png 1441w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>where <code class="language-text">appA</code> needs value of <code class="language-text">dpi</code> from the <code class="language-text">config</code>, but instead of importing from the <code class="language-text">config</code>,
it imports it from <code class="language-text">libA</code>.
While re-exports may simplify imports and improve encapsulation,
they also introduce implicit dependencies:
downstream code like <code class="language-text">appA</code> becomes coupled not only to <code class="language-text">libA</code>,
but also to the transitive closure of <code class="language-text">libA</code>.
In this graph this means that changes in any modules
that <code class="language-text">libA</code> depends on would require rebuilding <code class="language-text">appA</code>.
This is not truly needed since <code class="language-text">appA</code> doesn’t really depend on any code members from that closure.</p>
<p>To improve the chain of dependencies, the refactored graph would look like this:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/7f403d149b60cc4b579142e7628cb6df/fe60e/graph4.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 35.13513513513513%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABM0lEQVQoz0WSWXPCMAyE8/9/W48nhlLCMeUITSAnJI5t+evIIeVBo2hWXu+ukwA4F+gfWvKsgLNBIUIIeB8QT+zz7D3I/0zEdU70kJJkmbBelyyXOceDY+gjX1wqcsdu11GVQhD4vVj2+zttExCB7DzG+d6hhAEzBM6nge9Vzn5Xc/h50LWj6iMEaBvhdGx53CdFTe05HTuGfsKr0kbcDE9CEYnA0JvJhoAxJpa1Vn1jhp6gclDrjlFPo3Y942hwzkYsUbt1BV0LRW6oK6FtVdWUmRJWpYs7arm86XeIc9s4rB2joDnvRIk2m440rXh737JYXGKWWWZj2Ko8zz2bbcPXquDjc886LUnThqqaH24i057oi14Ly+1q4+3ao9LSI/LKbN7RuhYjRT5y7+SZ8+uP+APsVh37hxEAwgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/7f403d149b60cc4b579142e7628cb6df/fcda8/graph4.png" srcset="/static/7f403d149b60cc4b579142e7628cb6df/12f09/graph4.png 148w,
/static/7f403d149b60cc4b579142e7628cb6df/e4a3f/graph4.png 295w,
/static/7f403d149b60cc4b579142e7628cb6df/fcda8/graph4.png 590w,
/static/7f403d149b60cc4b579142e7628cb6df/efc66/graph4.png 885w,
/static/7f403d149b60cc4b579142e7628cb6df/c83ae/graph4.png 1180w,
/static/7f403d149b60cc4b579142e7628cb6df/fe60e/graph4.png 1677w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Identifying re-exports can be tricky particularly with highly dynamic languages such as Python.
The available tooling is limited (e.g. see <a href="https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-no-implicit-reexport">mypy</a>),
and custom static analysis programs might need to be written.</p>
<h3 id="stale-dependencies" style="position:relative;"><a href="#stale-dependencies" aria-label="stale dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stale dependencies</h3>
<p>Maintaining up-to-date and correct build metadata is necessary
to represent the dependency graph accurately, but issues might appear silently.
For example, you might have modules that were once declared to depend on a particular library
but do not depend on them any longer (however, the metadata in build files suggests they still are).
This can cause your modules to be unnecessarily rebuilt every time the library changes.</p>
<p>Some build systems such as Pants rely on <a href="https://www.pantsbuild.org/blog/2022/10/27/why-dependency-inference">dependency inference</a>
where users do not have to maintain the build metadata in build files,
but any manual dependencies declared (where inference cannot be done programmatically in all situations)
still need to be kept up-to-date and might easily get stale.</p>
<p>There are tools that can help ensuring the dependency metadata is fresh for
C++ (<a href="https://github.com/martis42/depend_on_what_you_use">1</a>, <a href="https://github.com/storypku/bazel_iwyu">2</a>)
<a href="https://github.com/tweag/FawltyDeps">Python</a>,
and <a href="https://github.com/bazeltools/bazel-deps">JVM</a> codebases,
but often keeping the build metadata up-to-date is still a semi-automated process
that cannot be safely automated completely due to edge cases and occasional false positives.</p>
<h3 id="incompatible-dependencies" style="position:relative;"><a href="#incompatible-dependencies" aria-label="incompatible dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Incompatible dependencies</h3>
<p>It is possible for an application to end up depending on third-party libraries that cannot be used together.
This could be enforced for multiple reasons:</p>
<ul>
<li>to ensure the design is sane (e.g., only a single cryptography library may be used by an application)</li>
<li>to avoid malfunctioning of the service (e.g., two resource intensive backend services can’t be run concurrently)</li>
<li>to keep the CI costs under control (e.g., tests may not depend on a live database instance
and should always use rich mock objects instead).</li>
</ul>
<p>Appropriate rules vary between organizations,
and should be updated continuously as the dependency graph evolves.
If you use Starlark for declaring build metadata,
take a look at <a href="https://github.com/bazelbuild/buildtools/blob/main/buildozer/README.md">buildozer</a>
which can help querying the build files when validating dependencies statically.</p>
<h3 id="large-transitive-closures" style="position:relative;"><a href="#large-transitive-closures" aria-label="large transitive closures permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Large transitive closures</h3>
<p>If a module depends on a lot of other modules, it’s more likely that it will also need to be changed
whenever any of those dependencies change.
Usually, bigger files (with more lines of code) have more dependencies, but that’s not always true.
For example, a file full of boilerplate or generated code might be huge, but barely depend on anything else.
Sticking to good design practices — like grouping related code together and making sure classes only do one thing —
can help keep your dependencies under control.</p>
<p>For example, with this graph</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/948ffdeb2dd58e6cf596f39a3c477a77/12bff/graph5.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.5945945945946%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABPUlEQVQoz21S7W7CMBDL+z8dUsUPxihD2kjLBnRpvpucp0vJKGwnubnKZ0dxIlIiOJsRPBAD4EzGFAlc0zRzsXI2I003Lt64gKItXCIIazJOPeFwGLFvB3SSMKpcRFpl9D3hba/Q7obSm5E5ghpmrm2HomUPqzMEf/oeWK8lmuYdUhLU92zIxl1HaJqPAu61uhtKmbBaHbDdXoqHYcMYMrQiXC8B3VHBaMC7WcSrHoHPky3Qio9356xG0VzOoXiwl8CtiBKU+kaMHkQZy/LBFCyLZ3iWNaytJYgIjF+x99DawFoL5yLMyJmmAj6udxHWGhhj4H1YbDD7iGr2bJzzhOHqSvC712sJny9MDQ45pz9GtRe1WZL13xlC3xE2my9sX86QR87u/9m6isdcHkl+h3xzwaGAn0x9h8+zNcMf9XC9bUUXQNwAAAAASUVORK5CYII='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/948ffdeb2dd58e6cf596f39a3c477a77/fcda8/graph5.png" srcset="/static/948ffdeb2dd58e6cf596f39a3c477a77/12f09/graph5.png 148w,
/static/948ffdeb2dd58e6cf596f39a3c477a77/e4a3f/graph5.png 295w,
/static/948ffdeb2dd58e6cf596f39a3c477a77/fcda8/graph5.png 590w,
/static/948ffdeb2dd58e6cf596f39a3c477a77/efc66/graph5.png 885w,
/static/948ffdeb2dd58e6cf596f39a3c477a77/12bff/graph5.png 1032w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>a build system is likely to require running all test cases in <code class="language-text">tests</code> should any of the apps change
which would be wasteful most of the time since most likely you are going to change only one of them at a time.</p>
<p>This could be refactored in having individual test modules targeting every application individually:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/db0462f0b7bda8e94118de1ba69219c0/34e70/graph6.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 46.621621621621614%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABc0lEQVQoz22R6Y7aQBCE/f4PR35kCXFAELDxfQH22Izd80U9Znez0pY0Uqtr+qjqQMRhn8Iy45/GmlMss3LuC+fcys3W+bcsn7FygRmEIhfiyBBHA1kiaE5xa4UsXbice8+XuWDtylWFkCaWy/lBls401bpIYHqhKODn25XtNiHLQHPguLWOJLFsfpwIw5KqXLdUri7hdLqz2RyJ44mmBlmE4DkJ985R5CNp8uDWguYUQy9r0/hOU1nunXgbFBq3zUJ0aX3947asknWawtoRM/ascB9e+cbDHRH7DSc8Hh3/I9CJ4+AYjfPemcExv7bQTZVTCyaD598PNhrxNUO/MBqYjLyO0qvxwp+wJgwr0qvzUhVdI1zjmV/bjMO+o8jch4dl7jj/NWzfMi6Xibp0q4c6Nc8dh0PDfl+Tpe7LldNk5vcu53i8URafR6kKRxSN7HY5cfykqV8NVbI2sJN+Xg/xbrwWewueKv87yXhOpU/jKvkflDq409jYnz8AAAAASUVORK5CYII='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/db0462f0b7bda8e94118de1ba69219c0/fcda8/graph6.png" srcset="/static/db0462f0b7bda8e94118de1ba69219c0/12f09/graph6.png 148w,
/static/db0462f0b7bda8e94118de1ba69219c0/e4a3f/graph6.png 295w,
/static/db0462f0b7bda8e94118de1ba69219c0/fcda8/graph6.png 590w,
/static/db0462f0b7bda8e94118de1ba69219c0/efc66/graph6.png 885w,
/static/db0462f0b7bda8e94118de1ba69219c0/34e70/graph6.png 1053w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<h3 id="third-party-dependencies" style="position:relative;"><a href="#third-party-dependencies" aria-label="third party dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Third-party dependencies</h3>
<p>It is generally advisable to be cautious about adding any dependency, particularly a third-party one,
and its usage should be justified — it may pay off to be reluctant to adding any external dependencies
unless the benefits of bringing them outweigh the associated cost.</p>
<p>For instance, a team working on a Python command-line application processing some text data may consider
using <code class="language-text">pandas</code> because it’s a powerful data manipulation tool
and twenty lines of code written using built-in modules could be replaced by a one-liner with <code class="language-text">pandas</code>.
But what happens when this application is going to be distributed?
The team will have to make sure that <code class="language-text">pandas</code> (which contains C code that needs to be compiled)
can be used on all supported operating systems and CPU architectures meeting the reliability and performance constraints.</p>
<p>It may sound harsh, but there’s truth to the idea that every dependency eventually becomes a liability.
By adding a dependency (either to your dependency graph, if it’s a new one, or to your program),
you are committing to stay on top of its security vulnerabilities, compatibility with other dependencies and
your build system, and licensing compliance.</p>
<p>Adding a new dependency means adding a new node or a new edge to the dependency graph, too.
The graph traversal time is negligible, but the time spent on rebuilding code at every node is not.
The absolute build time is less of a problem since most build systems can parallelize build actions very aggressively,
but what about the computational time?
While developer time (mind they still have to wait for the builds to finish!) is far more valuable than machine time,
every repeated computation during a build contributes to the total build cost.
These operations still consume resources —
whether you’re paying a cloud provider or covering the energy and maintenance costs of an on-premises setup.</p>
<h3 id="cross-component-dependencies" style="position:relative;"><a href="#cross-component-dependencies" aria-label="cross component dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cross-component dependencies</h3>
<p>It is common for applications to depend on libraries (shared code),
however, it is also possible (but less ideal) for an application to use code from another application.
If multiple applications have some code they both need, it is often advisable
that this code is extracted into a shared library so that both applications can depend on that instead.</p>
<p>Modern build systems such as <a href="https://www.pantsbuild.org/stable/docs/using-pants/validating-dependencies">Pants</a>
and <a href="https://bazel.build/concepts/visibility#target-visibility">Bazel</a> have a visibility control mechanism
that enforces rules of dependency between your codebase components.
These safeguards exist to prevent developers from accessing and incorporating code from unrelated parts of the codebase.
For instance, when building source code for accounting software,
the billing component should never depend on the expenses component just because it also needs to support exports to PDF.</p>
<p>However, visibility rules may not be expressive enough to cover certain cases.
For instance, if you follow a particular deployment model,
you may need to make sure that a specified module will never end up as a transitive dependency of a certain package.
You may also want to enforce that some code is justified to exist in a particular package
only if it’s being imported by some others.
For example, you may want to prevent placing any modules in the <code class="language-text">src/common-plugins</code> package
unless they are imported by <code class="language-text">src/plugins</code> package modules to keep the architecture robust.</p>
<p>Keep in mind that when introducing a modern build system to a large, legacy codebase
that has evolved without paying attention to the dependency graph’s shape,
builds may be slow not because the code compilation or tests take long,
but because any change in the source code requires re-building most or all nodes of the dependency graph.
That is, if all nodes of the graph transitively depend on a node with many widely used code members
that are modified often, there will be lots of re-build actions
unless this module is split across multiple modules each containing only closely related code.</p>
<h3 id="direct-change-propagation" style="position:relative;"><a href="#direct-change-propagation" aria-label="direct change propagation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Direct change propagation</h3>
<p>When source code in a module is changed, downstream nodes (reverse dependencies of this module) often get rebuilt
even if the specific changes don’t truly require it.
In large codebases, this causes unnecessary rebuilds, longer feedback cycles, and higher CI costs.</p>
<p>In most build systems (including Bazel and GNU Make), individual actions or targets are invalidated
if their inputs change.
In GNU Make, this would be <code class="language-text">mtime</code> of declared input files,
and in Bazel, this would be digests, or the action key.
Most build systems can perform an “early cutoff” if the output of an action doesn’t change.
Granted, with GNU Make, the <code class="language-text">mtime</code> could be updated even if the output was already correct from a previous build
(which will force unnecessary rebuilds), but that’s a very nuanced point.</p>
<p>However, with Application Binary Interface (ABI) awareness, it would only be necessary to rebuild downstream dependencies
if the interface they rely on has actually changed.</p>
<p>A related idea is having a stable API, which can help figure out which nodes in the graph actually changed.
Picture a setup like this — an application depends on the database writer module which in turn depends on the database engine:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 555px; ">
      <a class="gatsby-resp-image-link" href="/static/eb97463591b422e6b7e171154497d529/cd039/graph7.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 150%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD90lEQVRIx52WaXPbNhCG8///UTv92DQTuz51WdZpivehg6JIgjieDiA5kWM5TauZZ7DaXbxcDJYrfTLGoLV0KHXE2lJJQPP+Y4456m2+1gqr9UmpmrYdIeUcpaYoNaGTE2DKn5M+D+PPPA1/p9f7jeHoD+L4Cpgj5cTl2j1SzmjbKVoLK3hAqQGwoCoD9ruEchvTVgmfRx6jyZTx8IFh/4H+4y0vizGHfUq5iyi3CYfKPz3gGaXao6BUQ5SckSYtaYojSaFcQ+A3eF6N5x2IY8VmA0lyykkgT0t3mmPFZ4JazcizhjyHPNcUhRXV9Ps+9/dLrq+fGY0i1msbN44sgyLbvRdUpwqLXOAHgoeHJb2e70RubmZO0HJ7t+DxccXV1bOLfSj4WmGW1CSJIYoEUSgIgpYwFO64q1XtbOv3fWu3pAkU6faCoBy6S2nrJc3hhbb2HE3tIUVAHPZJ4qGzj/GVi9fVC6JZXLiUk6B90iWMGTs+il8UNGaBaF4Q7VukWFEfFtSHOZ3w3sUtmB8EtR7QNivClSSOFHH4HX/VEgYdUaiIQnlaj3YcaeKwRYoFSp0JKj1AND5JrF2fvbLfw2SS8/Xrk7tVe1llCdst7HZQrCFLO2R3QdBWmGewWGwYjxMndHMzZTYrXOt8+TLg9nbGcBi6trIPsF2QZxIp5m8F3ZFrnzgEfyWYzyvm8z3P4w2LRcVksuV5vGY0zJjP9kynO55GOUGgSGNJ9+7IaoCSL+zWMVWZUZW547AvaA5rQn9JHHqIZvvNX1cF+11KuY3Q+l/bZnLGDCEGdN3gQuwnbWMFtZ65V/BH2ubZcSlmzPwjwTnNwSONavLsQJYeybOaIm8o7OBI6+9+ayd7RLs4m4+vgt3QHa2uAuIIN1EstjV8v+X6ekqvF7hhsNnCeoObRmmiaZvlBcFThXXluxfe9yvXLhbbQrZF7u7mTKfWt2Y6zQmjljxVtM1PKjzsj4KL5Z6Hx4Cr6xk3t0seewH39x79fkyvF3J35+GtGlexHSgfVLigbTzypGS7qdhta8ptTVW2REFBHK7Zly27zdG/WVcU2da9du8EhehjzAytrXOMkmOkpXvC6AlF/jfr9S3GTJxPyieXY3PtHq2ndN34KGiMouu2dN3OIWXp6LrSfVdqz+GQO6x9zCvP8nbffMZoPvELHyEEQnS/kmorNJxjf8iNNrSNdnTCsC8bh7XbRiFazY/7XnlT4VEQZKeJfOXGWRLp0zw8zkI7A5NQobV5s+dbhZcEO6Epcli+7Pj8V5+vV09uJv59MyFJJUVm0Oo/CL5WGAWSldcQ+t2RQBKH/6NCu8rOIKX9YwTKries367nueeC/wC8PPKsp0VK3AAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/eb97463591b422e6b7e171154497d529/cd039/graph7.png" srcset="/static/eb97463591b422e6b7e171154497d529/12f09/graph7.png 148w,
/static/eb97463591b422e6b7e171154497d529/e4a3f/graph7.png 295w,
/static/eb97463591b422e6b7e171154497d529/cd039/graph7.png 555w" sizes="(max-width: 555px) 100vw, 555px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>This application calls the <code class="language-text">apply</code> function from the database <code class="language-text">writer</code> module to insert some rows,
which then uses the database <code class="language-text">engine</code> to handle the actual disk writing.
If anything in <code class="language-text">internals</code> changes (e.g., how the data is compressed before writing to disk),
the client won’t notice as long as the <code class="language-text">writer</code>’s interface stays the same.
That interface acts as a “stable layer” between the parts.
In the build context, running tests of the application should not be necessary on changes in the database component.</p>
<p>Practically, reordering methods in a Java class, adding a docstring to a Python function,
or even making minor changes in the implementation (such as <code class="language-text">return a + b</code> instead of <code class="language-text">return b + a</code>)
would still be marking that node in the graph as “changed” particularly if you rely on tooling
that queries modified files in the version control repository without taking into account the semantics of the change.</p>
<p>Therefore, relying on the checksum of a source file or all files in a package
(depending on what a node in your dependency graph represents)
just as relying on checksum of compiled objects (be it machine code or bytecode)
may prove insufficient when determining
what kind of change deserves to be propagated further in the dependency chain of the graph.
Take a look at the <a href="https://www.tweag.io/blog/2022-11-03-blog_recompilation/">Recompilation avoidance in rules_haskell</a>
to learn more about checksum based recompilation avoidance in Haskell.</p>
<p>Many programming languages have language constructs, such as interfaces in Go,
that can avoid this problem by replacing a dependency on some concrete implementation
with a dependency on a shared public interface.
The application from the example above could depend on a database interface (or abstract base class)
instead of the actual implementation.
This is another kind of “ABI” system that avoids unnecessary rebuilds and helps to decouple components.</p>
<p>How ABI compatibility is handled depends on the build system used.
In Buck, there is a concept of <a href="https://buck.build/concept/java_abis.html">Java ABI</a>
that is used to figure out which nodes actually need rebuilding during an incremental build.
For example, a Java library doesn’t always need to be rebuilt
just because one of its dependencies changed unless the public interface of that dependency changed too.
Knowing this helps skip unnecessary rebuilds when the output would be the same anyway.</p>
<p>In the most recent versions of Bazel, there is experimental support for <a href="https://bazel.build/rules/lib/toplevel/attr#label.materializer"><em>dormant</em> dependencies</a>
which are not an actual dependency, but the possibility of one.
The idea is that every edge between nodes can be marked as dormant,
and then it is possible for it to be passed up the dependency graph
and turned into an actual dependency (“materialized”) in the reverse transitive closure.
Take a look at the <a href="https://docs.google.com/document/d/1BLgnPvWqI1hfUh-rXD6Gw6QqqjwAyJuqhjuAL0t6lBw/edit?tab=t.0#heading=h.5mcn15i0e1ch">design document</a> to learn more about the rationale.</p>
<hr>
<p>We hope it is clear now how notoriously complex managing a large dependency graph in a monorepo is.
Changes in one package can ripple across dozens or even hundreds of interconnected modules.
Developers must carefully coordinate versioning, detect and prevent circular dependencies,
and ensure that builds remain deterministic, particularly in industries with harder reproducibility constraints
such as automotive or biotech.</p>
<p>Failing to keep the dependency graph sane often leads to brittle CI pipelines and long development feedback loops
which impedes innovation and worsens developer experience.
In the future, we can expect more intelligent tools to emerge such as machine learning based dependency impact analyzers
that predict downstream effects of code changes and self-healing CI pipelines that auto-adjust scope and change propagation.
Additionally, semantic-aware refactoring tools and “intent-based” build systems could automate much of the manual effort
that is currently required to manage interdependencies at scale.</p>
<p>In the next post, we’ll talk about scalability problems and limitations of the dependency graph scope
that is exposed by build systems.</p>]]></description><link>https://tweag.io/blog/2025-09-18-managing-dependency-graph/</link><guid isPermaLink="false">https://tweag.io/blog/2025-09-18-managing-dependency-graph/</guid><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Qualified Imports and Alias Resolution in Liquid Haskell]]></title><description><![CDATA[<p>Liquid Haskell (LH) is a formal verification tool for Haskell programs, with the
potential to prove correctness with considerably less friction than approaches
that aim to make code <em>correct by construction</em> using dependent types—often at
the cost of heavy refactoring (as argued in a previous <a href="https://www.tweag.io/blog/2022-01-19-why-liquid-haskell/">post</a>). It
has come a long way towards becoming a usable tool by adding quality-of-life
features to foster its adoption. Think optimization of spec verification and
improved user experience.</p>
<p>During my GSoC 2025 Haskell.org project with Tweag, I worked on a seemingly
small but impactful feature: allowing LH’s type and predicate aliases to be written
in qualified form.
That is, being able to write <code class="language-text">Foo.Nat</code> instead of only just <code class="language-text">Nat</code>, like we can for regular Haskell type aliases.</p>
<p>In this post, I introduce these annotations and their uses, walk through some of
the design decisions, and share how I approached the implementation.</p>
<h2 id="aliasing-refinement-types" style="position:relative;"><a href="#aliasing-refinement-types" aria-label="aliasing refinement types permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Aliasing refinement types</h2>
<p>Type and predicate aliases in LH help users abstract over refinement type
annotations, making them easier to reuse and more concise. A type alias refines
an existing type. For instance, LH comes with built-in aliases like <code class="language-text">Nat</code> and
<code class="language-text">Odd</code>, which refine <code class="language-text">Int</code> to represent natural and odd numbers, respectively.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token comment">{-@ type Nat = {v: Int | v >= 0 } @-}</span>

<span class="token comment">{-@ type Odd = {v: Int | (v mod 2) = 1 } @-}</span></code></pre></div>
<p>Predicate aliases, by contrast, capture only the predicate part of a refinement
type. For example, we might define aliases for positive and negative numerical
values.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token comment">-- Value parameters in aliases are specified in uppercase,</span>
<span class="token comment">-- while lowercase is reserved for type parameters.</span>

<span class="token comment">{-@ predicate Neg N = N &lt; 0 @-}</span>

<span class="token comment">{-@ predicate Pos N = N > 0 @-}</span></code></pre></div>
<p>Enter the subtle art of giving descriptive names so that our specifications
read more clearly. Consider declaring aliases for <a href="https://en.wikipedia.org/wiki/Interval_(mathematics)#Definitions_and_terminology">open intervals</a>
with freely oriented boundaries.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token comment">{-@ predicate InOpenInterval A B X =
      (A != B) &amp;&amp;
      ((X > A &amp;&amp; X &lt; B) || (X > B &amp;&amp; X &lt; A)) @-}</span>

<span class="token comment">{-@ type OpenInterval A B = { x:Float | InOpenInterval A B x } @-}</span></code></pre></div>
<p>These aliases can then be used to prove, for instance, that an implementation
of an <a href="https://en.wikipedia.org/wiki/Affine_transformation">affine transformation</a>, <code class="language-text">fromUnitInterval</code> below, from the open unit interval to an
arbitrary interval is a bijection. The proof proceeds by supplying an inverse
function (<code class="language-text">toUnitInterval</code>) and specifying<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup> that their composition is the identity.
The example shows one half on the proof; the other half is straightforward
and left to the reader.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">type</span> <span class="token constant">Bound</span> <span class="token operator">=</span> <span class="token constant">Float</span>

<span class="token comment">{-@ inline fromUnitInterval @-}</span>
<span class="token comment">{-@ fromUnitInterval :: a : Bound
                     -> { b : Bound | a != b }
                     -> x : OpenInterval 0 1
                     -> v : OpenInterval a b @-}</span>
<span class="token hvariable">fromUnitInterval</span> <span class="token operator">::</span> <span class="token constant">Bound</span> <span class="token operator">-></span> <span class="token constant">Bound</span> <span class="token operator">-></span> <span class="token constant">Float</span> <span class="token operator">-></span> <span class="token constant">Float</span>
<span class="token hvariable">fromUnitInterval</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token hvariable">x</span> <span class="token operator">=</span> <span class="token hvariable">a</span> <span class="token operator">+</span> <span class="token hvariable">x</span> <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token hvariable">b</span> <span class="token operator">-</span> <span class="token hvariable">a</span><span class="token punctuation">)</span>

<span class="token comment">{-@ inline toUnitInterval @-}</span>
<span class="token comment">{-@ toUnitInterval :: a : Bound
                   -> { b : Bound | a != b }
                   -> x : OpenInterval a b
                   -> v : OpenInterval 0 1 @-}</span>
<span class="token hvariable">toUnitInterval</span> <span class="token operator">::</span> <span class="token constant">Bound</span> <span class="token operator">-></span> <span class="token constant">Bound</span> <span class="token operator">-></span> <span class="token constant">Float</span> <span class="token operator">-></span> <span class="token constant">Float</span>
<span class="token hvariable">toUnitInterval</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token hvariable">x</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token hvariable">x</span> <span class="token operator">-</span> <span class="token hvariable">a</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token hvariable">b</span> <span class="token operator">-</span> <span class="token hvariable">a</span><span class="token punctuation">)</span>

<span class="token comment">{-@ intervalId :: a : Bound
                   -> { b : Bound | a != b }
                   -> x : OpenInterval a b
                   -> {v : OpenInterval a b | x = v} @-}</span>
<span class="token hvariable">intervalId</span> <span class="token operator">::</span> <span class="token constant">Bound</span> <span class="token operator">-></span> <span class="token constant">Bound</span> <span class="token operator">-></span> <span class="token constant">Float</span> <span class="token operator">-></span> <span class="token constant">Float</span>
<span class="token hvariable">intervalId</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token hvariable">x</span> <span class="token operator">=</span> <span class="token hvariable">fromUnitInterval</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span> <span class="token operator">.</span> <span class="token hvariable">toUnitInterval</span> <span class="token hvariable">a</span> <span class="token hvariable">b</span></code></pre></div>
<p>Another case: refining a <code class="language-text">Map</code> type to a fixed length allows us to enforce that
a function can only grant access privileges to a bounded number of users at any
call site.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">type</span> <span class="token constant">Password</span> <span class="token operator">=</span> <span class="token constant">String</span>
<span class="token keyword">type</span> <span class="token constant">Name</span> <span class="token operator">=</span> <span class="token constant">String</span>

<span class="token comment">{-@ type FixedMap a b N = { m : Map a b | len m = N } @-}</span>

<span class="token comment">{-@ giveAccess :: Name
               -> Password
               -> FixedMap Name Password 3
               -> Bool @-}</span>
<span class="token hvariable">giveAccess</span> <span class="token operator">::</span> <span class="token constant">Name</span> <span class="token operator">-></span> <span class="token constant">Password</span> <span class="token operator">-></span> <span class="token constant">Map</span> <span class="token constant">Name</span> <span class="token constant">Password</span> <span class="token operator">-></span> <span class="token constant">Bool</span>
<span class="token hvariable">giveAccess</span> <span class="token hvariable">name</span> <span class="token hvariable">psswd</span> <span class="token hvariable">users</span> <span class="token operator">=</span>
  <span class="token constant">Map</span><span class="token punctuation">.</span><span class="token builtin">lookup</span> <span class="token hvariable">name</span> <span class="token hvariable">users</span> <span class="token operator">==</span> <span class="token constant">Just</span> <span class="token hvariable">psswd</span></code></pre></div>
<p>None of these specifications strictly <em>require</em> aliases, but they illustrate the
practical convenience they bring.</p>
<h2 id="a-crowded-name-space" style="position:relative;"><a href="#a-crowded-name-space" aria-label="a crowded name space permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A crowded name space</h2>
<p>When we try to be simple and reasonable about such aliases, it becomes quite
likely for other people to converge on the same names to describe similar
types. Even a seemingly standard type such as <code class="language-text">Nat</code> is not safe: someone
with a historically informed opinion might want to define it as strictly positive
numbers<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup>, or may just prefer to refine <code class="language-text">Word8</code> instead of <code class="language-text">Int</code>.</p>
<p>Naturally, this is the familiar problem of name scope, for which established
solutions exist, such as modules and local scopes. Yet for LH and its <code class="language-text">Nat</code>, it
<em>was</em> the case that one would have to either invent a non-conflicting name,
<a href="https://ucsd-progsys.github.io/liquidhaskell/options/#loading-specifications-automatically">exclude assumptions</a> for the <code class="language-text">base</code> package, or avoid
importing the <code class="language-text">Prelude</code> altogether. It might be argued that having to invent
alternative names is a minor nuisance, but also that it can quickly lead to
unwieldy and convoluted naming conventions once multiple dependencies expose
their own specifications.</p>
<p>Simply stated, the problem was that LH imported all aliases from transitive
dependencies into a flat namespace. After my contribution, LH still accumulates
aliases transitively, but users gain two key capabilities: (i) to disambiguate
occurrences by qualifying an identifier, and (ii) to overwrite an imported alias
without conflict. In practice, this prevents spurious verification failures
and gives the user explicit means to resolve clashes when they matter.</p>
<p>Consider the following scenario. Module <code class="language-text">A</code> defines alias <code class="language-text">Foo</code>. Two other
modules, <code class="language-text">B</code> and <code class="language-text">B'</code>, both define an alias <code class="language-text">Bar</code> and import <code class="language-text">A</code>.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">module</span> <span class="token constant">A</span> <span class="token keyword">where</span>
<span class="token comment">{-@ type Foo = { ... } @-}</span>

<span class="token keyword">module</span> <span class="token constant">B</span> <span class="token keyword">where</span>
<span class="token import-statement"><span class="token keyword">import</span> A</span>
<span class="token comment">{-@ type Bar = { ... } @-}</span>

<span class="token keyword">module</span> <span class="token constant">B'</span> <span class="token keyword">where</span>
<span class="token import-statement"><span class="token keyword">import</span> A</span>
<span class="token comment">{-@ type Bar = { ... } @-}</span></code></pre></div>
<p>A module <code class="language-text">C</code> that imports <code class="language-text">B</code> and <code class="language-text">B'</code> will now see <code class="language-text">Foo</code> in scope unambiguously,
while any occurrence of <code class="language-text">Bar</code> must be qualified in the usual Haskell manner.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token keyword">module</span> <span class="token constant">C</span> <span class="token keyword">where</span>

<span class="token import-statement"><span class="token keyword">import</span> B</span>
<span class="token import-statement"><span class="token keyword">import</span> B'</span>

<span class="token comment">{-@ baz :: Foo -> B.Bar @-}</span>
<span class="token hvariable">baz</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token builtin">undefined</span></code></pre></div>
<p>Previously, this would have caused <code class="language-text">C</code> to fail verification with a conflicting
definitions error, even if <code class="language-text">Bar</code> was never used.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">examples</span><span class="token operator">/</span><span class="token hvariable">B<span class="token punctuation">.</span>hs</span><span class="token operator">:</span><span class="token number">3</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">:</span> <span class="token builtin">error</span><span class="token operator">:</span>
    <span class="token constant">Multiple</span> <span class="token hvariable">definitions</span> <span class="token keyword">of</span> <span class="token constant">Type</span> <span class="token constant">Alias</span> `<span class="token constant">Bar</span>`
    <span class="token constant">Conflicting</span> <span class="token hvariable">definitions</span> <span class="token hvariable">at</span>
    <span class="token operator">.</span>
    <span class="token operator">*</span> <span class="token hvariable">examples</span><span class="token operator">/</span><span class="token hvariable">B<span class="token punctuation">.</span>hs</span><span class="token operator">:</span><span class="token number">3</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">-</span><span class="token number">39</span>
    <span class="token operator">.</span>
    <span class="token operator">*</span> <span class="token hvariable">examples</span><span class="token operator">/</span><span class="token hvariable">B'<span class="token punctuation">.</span>hs</span><span class="token operator">:</span><span class="token number">3</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">-</span><span class="token number">39</span>
  <span class="token operator">|</span>
<span class="token number">3</span> <span class="token operator">|</span> <span class="token comment">{-@ type Bar = { ... } @-}</span>
  <span class="token operator">|</span>          <span class="token operator">^^^^^^^^^^^^^^</span></code></pre></div>
<p>This error is now only triggered when the alias is defined multiple times within
the same module. And instead, when an ambiguous type alias is found, the user is
prompted to choose among the matching names in scope and directed to the
offending symbol.</p>
<div class="gatsby-highlight" data-language="haskell"><pre class="language-haskell"><code class="language-haskell"><span class="token hvariable">examples</span><span class="token operator">/</span><span class="token hvariable">C<span class="token punctuation">.</span>hs</span><span class="token operator">:</span><span class="token number">6</span><span class="token operator">:</span><span class="token number">19</span><span class="token operator">:</span> <span class="token builtin">error</span><span class="token operator">:</span>
    <span class="token constant">Ambiguous</span> <span class="token hvariable">specification</span> <span class="token hvariable">symbol</span> `<span class="token constant">Bar</span>` <span class="token hvariable">for</span> <span class="token keyword">type</span> <span class="token hvariable">alias</span>
    <span class="token constant">Could</span> <span class="token hvariable">refer</span> <span class="token hvariable">to</span> <span class="token builtin">any</span> <span class="token keyword">of</span> <span class="token hvariable">the</span> <span class="token hvariable">names</span>
    <span class="token operator">.</span>
    <span class="token operator">*</span> <span class="token constant">Bar</span> <span class="token hvariable">imported</span> <span class="token hvariable">from</span> <span class="token keyword">module</span> <span class="token constant">B</span> <span class="token hvariable">defined</span> <span class="token hvariable">at</span> <span class="token hvariable">examples</span><span class="token operator">/</span><span class="token hvariable">B<span class="token punctuation">.</span>hs</span><span class="token operator">:</span><span class="token number">3</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">-</span><span class="token number">39</span>
    <span class="token operator">.</span>
    <span class="token operator">*</span> <span class="token constant">Bar</span> <span class="token hvariable">imported</span> <span class="token hvariable">from</span> <span class="token keyword">module</span> <span class="token constant">B'</span> <span class="token hvariable">defined</span> <span class="token hvariable">at</span> <span class="token hvariable">examples</span><span class="token operator">/</span><span class="token hvariable">B'<span class="token punctuation">.</span>hs</span><span class="token operator">:</span><span class="token number">3</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">-</span><span class="token number">39</span>
  <span class="token operator">|</span>
<span class="token number">6</span> <span class="token operator">|</span> <span class="token comment">{-@ baz :: Foo -> Bar @-}</span>
  <span class="token operator">|</span>                   <span class="token operator">^^^</span></code></pre></div>
<p>The precise behavior is summarized in a set of <a href="https://ucsd-progsys.github.io/liquidhaskell/specifications/#importexport-of-aliases">explicit rules</a>
that I proposed, which specify how aliases are imported and exported under
this scheme.</p>
<h2 id="the-initial-name-resolution-flow" style="position:relative;"><a href="#the-initial-name-resolution-flow" aria-label="the initial name resolution flow permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The initial name resolution flow</h2>
<p>The project goals were initially put forward on a GitHub <a href="https://github.com/ucsd-progsys/liquidhaskell/issues/2481">issue</a> as a
spin-off from a recent <a href="https://www.tweag.io/blog/2025-02-06-refactoring-lh/">refactoring of the codebase</a> that changed the
internal representation of names to a structured <code class="language-text">LHName</code> type that
distinguishes between resolved and unresolved names and stores information about
where the name originates, so that names are resolved only once for each compiled
module.</p>
<p>Name resolution has many moving parts, but in broad terms its implementation is
divided into two phases: The first handles names corresponding to entities GHC
knows of—data and type constructors, functions, and annotation binders of
<em>aliases</em>, <a href="https://ucsd-progsys.github.io/liquidhaskell/specifications/#specifying-measures">measures</a>, and <a href="https://ucsd-progsys.github.io/liquidhaskell/specifications/#modules-with-code-data">data constructors</a>—and uses its
<a href="https://hackage-content.haskell.org/package/ghc-9.12.2/docs/GHC-Types-Name-Reader.html">global reader environment</a> to look them up. The resolution of logical
entities (<em>i.e.</em> those found in logical expressions) is left for the second
phase, where the names resolved during the first phase are used to build custom
lookup environments.</p>
<p>Occurrences of type and predicate aliases were resolved by looking them up in an
environment indexed by their unqualified name. When two or more dependencies
(possibly transitive) defined the same alias, resolution defaulted to whichever
definition happened to be encountered first during collection. This accidental
choice was effectively irrelevant, however, since a later duplicate-name check
would short-circuit with the aforementioned error. Locally defined aliases
were recorded in the module’s interface file after verification, and LH
assembled the resolution environment by accumulating the aliases from the
interface files of all transitive dependencies.</p>
<p>The reason a module import brings all aliases from transitive dependencies
into scope is that no mechanism exists to declare which aliases a module exports
or imports. Implementing such a mechanism exceeded the project’s allocated time,
so a trade-off was called for. On the importing side, Haskell’s qualifying
directives could be applied, but an explicit defaulting mechanism was needed to
determine what aliases a module exposes. This left us with at least
three possibilities:</p>
<ol>
<li>Export no aliases, so that they would be local to each module alone. This
no-op solution would allow the user to use any names she wants, but quickly
becomes inconvenient as an alias would have to be redefined in each module she
intends to use it.</li>
<li>Export only those locally defined, so that only aliases from direct
dependencies would be in scope for any given module. This could leave out
aliases used to specify re-exported functions, so we would end up in a
similar situation as before.</li>
<li>Export all aliases from transitive dependencies, avoiding the need to ever
duplicate an alias definition.</li>
</ol>
<p>The chosen option (3) reflects the former behavior and, complemented by
the ability qualify and overwrite aliases, it was deemed the most effective
solution.</p>
<h3 id="qualifying-type-aliases" style="position:relative;"><a href="#qualifying-type-aliases" aria-label="qualifying type aliases permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Qualifying type aliases</h3>
<p>Type aliases are resolved during the first phase, essentially because they are
parsed as type constructors, which are resolved uniformly across the input
specification. Two changes had to be made to qualify them: include module import
information in the resolution environment to discern which module aliases can be
used to qualify an imported type alias, and make sure transitively imported
aliases are stored in the interface file along with the locally defined type
aliases.</p>
<p>Careful examination of the code revealed that we could reuse environments built
for other features of LH that could be qualified already! And as a
bonus, their lookup function returns close-match alternatives in case of failure.
Factoring this out almost did the
trick. In addition, I had to add some provisions to give precedence to locally
defined aliases during lookups.</p>
<h3 id="qualifying-predicate-aliases" style="position:relative;"><a href="#qualifying-predicate-aliases" aria-label="qualifying predicate aliases permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Qualifying predicate aliases</h3>
<p>Two aspects of the code made predicate aliases somewhat hard to reason about.
First, predicate aliases are conflated in environments with
Haskell entities lifted by <a href="https://ucsd-progsys.github.io/liquidhaskell/specifications/#inlines"><code class="language-text">inline</code></a> and <a href="https://ucsd-progsys.github.io/liquidhaskell/options/#logical-aliasing"><code class="language-text">define</code></a> annotations.
The rationale is to use a single mechanism to expand these definitions in
logical expressions.</p>
<p>Second, the conflated environments were redundantly gathered twice with different
purposes: to resolve Haskell function names in logical
expressions, and afterwards again to resolve occurrences of predicate aliases.</p>
<p>Both were not straightforward to deduce from the code. These facts,
together with some code comments from the past about predicate aliases being the
last names that remained “unhandled”, pointed the way.</p>
<p>The surgical change, then, was to sieve out predicate aliases from the lifted
Haskell functions as they were stored together in interface files, and include
these predicate aliases in the environment used to resolve qualified names for
other features.</p>
<h3 id="alias-expansion" style="position:relative;"><a href="#alias-expansion" aria-label="alias expansion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Alias expansion</h3>
<p>Although the problem I set out to solve was primarily about name resolution, the
implementation also required revisiting another process: alias expansion. For a
specification to be ready for constraint generation, all aliases must be fully
expanded (or unfolded), since <code class="language-text">liquid-fixpoint</code><sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup> has no notion of aliases.</p>
<p>Uncovering this detail was crucial to advance with the implementation. It
clarified why Haskell functions lifted
with <code class="language-text">inline</code> or <code class="language-text">define</code> are eventually converted into predicate aliases: doing
so allows for every aliasing annotation to be expanded consistently in a single
pass wherever they appear in a specification. With qualified aliases, the
expansion mechanism needed some adjustments, as the alias names were now more
structured (<code class="language-text">LHName</code>).</p>
<p>An additional complication was that the logic to expand type
aliases was shared with predicate aliases, and since I did qualification of type
aliases first, I needed to have different behavior for type and predicate
aliases. In the end, I opted for duplicating the expansion logic for each case
during the transition, and unified it again after implementing qualification of
predicate aliases.</p>
<h2 id="closing-remarks" style="position:relative;"><a href="#closing-remarks" aria-label="closing remarks permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Closing remarks</h2>
<p>My determination to understand implementation details was rewarded by
insights that allowed me to refactor my way to a solution. For perspective,
my contribution consisted of a 210 LOC addition for the feature implementation
alone, after familiarizing myself with 2,150 LOC out of the 25,000 LOC making up
the LH plugin.
The bulk of this work is contained in two merged PRs (<a href="https://github.com/ucsd-progsys/liquidhaskell/pull/2550">#2550</a> and
<a href="https://github.com/ucsd-progsys/liquidhaskell/pull/2566">#2566</a>), which include detailed source documentation and tests.</p>
<p>The qualified aliases support and the explicit <a href="https://ucsd-progsys.github.io/liquidhaskell/specifications/#importexport-of-aliases">rules</a> that govern it
are a modest addition, but hopefully one of a positive impact on user experience.
LH tries to be as close as possible to Haskell, but refinement type aliases
still mark the boundary between both worlds. Perhaps the need for an <em>ad hoc</em>
mechanism for importing and exporting logic entities will be revised in a horizon
where LH gets integrated into GHC (which sounds good to me!).</p>
<p>This project taught me about many language features and introduced me to the
GHC API; knowledge I will apply in future projects and to further contribute to
the Haskell ecosystem. I am grateful to Facundo Domínguez for his generous and
insightful mentoring, which kept on a creative flow throughout the project.
Working on Liquid Haskell was lots of fun!</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">Note that, in this example, the <code class="language-text">inline</code> annotation is used to translate
the Haskell definitions into the logic so Liquid Haskell can unfold calls to these
functions when verifying specifications.<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">It took humanity quite a while to think clearly about a <em>null</em> quantity,
and further still for it to play a fundamental role as a placeholder for
positional number notation.<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-3"><a href="https://github.com/ucsd-progsys/liquid-fixpoint"><code class="language-text">liquid-fixpoint</code></a> is the component of Liquid Haskell that
transforms a module’s specification into a set of constraints for an external
SMT solver.<a href="#fnref-3" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-09-11-qualified-aliases/</link><guid isPermaLink="false">https://tweag.io/blog/2025-09-11-qualified-aliases/</guid><pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Introduction to the dependency graph]]></title><description><![CDATA[<p>This is the first in a series of three companion blog posts about dependency graphs.
These blog posts explore the key terminology, graph theory concepts,
and the challenges of managing large graphs and their underlying complexity.</p>
<ol>
<li><em>Introduction to the dependency graph</em></li>
<li><a href="../2025-09-18-managing-dependency-graph"><em>Managing dependency graph in a large codebase</em></a></li>
<li><a href="../2025-12-04-the-anatomy-of-a-dependency-graph"><em>The anatomy of a dependency graph</em></a></li>
</ol>
<hr>
<p>A dependency graph is a representation of how different parts of a software project rely on each other.
Understanding the dependency graph helps a software engineer see the bigger picture of how their component fits into the whole project
and why certain changes might affect other areas.
It’s a useful tool for organizing, debugging, and improving the source code.</p>
<p>Engineers responsible for managing the development and build environments also benefit greatly
from understanding dependency graph concepts and how they are used by the build system.
This knowledge is crucial for optimizing build times since it allows engineers to identify opportunities
to parallelize and improve the incrementality of builds.
Understanding the dependency graph also helps in troubleshooting build failures, managing changes safely,
and ensuring that updates or refactors do not worsen the overall design of the codebase.</p>
<p>In this blog post, we’ll take a fresh look at dependency graphs, starting from the basic concepts
and building up from there.
You will learn what a dependency graph is, some terminology required to be successful in managing it,
and what it is used for.</p>
<h2 id="what-is-a-dependency-graph" style="position:relative;"><a href="#what-is-a-dependency-graph" aria-label="what is a dependency graph permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What is a dependency graph?</h2>
<p>A dependency graph is a visual map that explains the connectivity between parts of a software project.</p>
<p>Let’s use a contrived example of a dependency graph in a tiny codebase and lay out some key terminology.</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/495bd132cdce18cfad1b418601440185/dcc98/graph1.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABXUlEQVQoz31TiXKCMBD1///PcepRBARrKRA5cievk1VsUOnOMAt7vH1ZXla4m/f+4VnrURQCx4ShLBWGjjIIJVPdkq0mkOmBB5raEFiaMqTHDh2zeK6b9UTDVs8MgwnOobWhd600fS/bDYyYxAxvzZ7Y/VQSgoe4I19VkuJGexgTVuLQ1BptY8HHNwwn2qH5mHDstle0NVWQ3++uyNIwxENJj1OmsV5fcDgM6BiWAYMTwqBtezh3i1sLNM2AcdCP1Rgd9tyDjxbWLAIC1noo5cC5hjHxXkNMELsQF1xDa4X5/l926Ekeh/2A7UeD6uLgnX+ADr1H8jlis6nwdba4CyKS25sjKwl8XwROOcPQTzLBS67vPA2LpTTTIe1gou9Cs3zS3f3gzkFJFeX+5PIi7JDgI1AWEnnekzTipvCHz6VElnVoaje7Wf8C5hmn2xHkEgMGvRUngSRhqKtlwF9wSK1OVYQfOwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/495bd132cdce18cfad1b418601440185/fcda8/graph1.png" srcset="/static/495bd132cdce18cfad1b418601440185/12f09/graph1.png 148w,
/static/495bd132cdce18cfad1b418601440185/e4a3f/graph1.png 295w,
/static/495bd132cdce18cfad1b418601440185/fcda8/graph1.png 590w,
/static/495bd132cdce18cfad1b418601440185/efc66/graph1.png 885w,
/static/495bd132cdce18cfad1b418601440185/c83ae/graph1.png 1180w,
/static/495bd132cdce18cfad1b418601440185/dcc98/graph1.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<h3 id="nodes-and-edges" style="position:relative;"><a href="#nodes-and-edges" aria-label="nodes and edges permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Nodes and edges</h3>
<p>A <em>node</em> in a dependency graph represents an individual item
which can be a software package, a module, or a component.</p>
<p>The <em>edges</em> (connections) between nodes represent dependencies,
meaning one node relies on another to function or build correctly.</p>
<h3 id="dependencies" style="position:relative;"><a href="#dependencies" aria-label="dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dependencies</h3>
<p><code class="language-text">appA</code> depends on <code class="language-text">libX</code> directly therefore <code class="language-text">libX</code> is a <em>direct dependency</em> of <code class="language-text">appA</code>.
For example, if you <code class="language-text">import</code> the <code class="language-text">requests</code> package in your Python module,
this would be that module’s direct dependency.</p>
<p><code class="language-text">appB</code> depends on <code class="language-text">commons</code> via <code class="language-text">libY</code> therefore <code class="language-text">commons</code> is a <em>transitive dependency</em> of <code class="language-text">appB</code>.
For example, if your C++ program depends on <code class="language-text">libcurl</code>, then it also depends (transitively)
on every <a href="https://curl.se/docs/libs.html">external library</a> that <code class="language-text">libcurl</code> depends on
such as OpenSSL or zlib.</p>
<h3 id="dependents" style="position:relative;"><a href="#dependents" aria-label="dependents permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dependents</h3>
<p><code class="language-text">libX</code> and <code class="language-text">libY</code> directly depend on <code class="language-text">commons</code>.
This could also be reversed — <code class="language-text">commons</code> has two direct <em>dependents</em>: <code class="language-text">libX</code> and <code class="language-text">libY</code>.
In fact, the dependents are often called <em>reverse dependencies</em>.
Similarly, <code class="language-text">secrets</code> have two reverse dependencies: one direct - <code class="language-text">appB</code>, and one transitive - <code class="language-text">testB</code>.</p>
<h3 id="shape-and-orientation" style="position:relative;"><a href="#shape-and-orientation" aria-label="shape and orientation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Shape and orientation</h3>
<p>A simple dependency graph can sometimes look like a <em>tree</em>,
with one common base component at the <em>root</em>,
supporting multiple dependents (components pointing back towards the root),
which in turn are depended on by the <em>leaves</em> (components with no further dependents).</p>
<p>However, dependency graphs are usually more complex than trees
and belong to a more general family of graphs
known as <em>directed acyclic graphs</em> (<em>DAG</em>),
where you can only follow the arrows in one direction,
and you can never end up back at the same node you started from.
We’ll talk about the word “acyclic” in more detail later in the post.</p>
<p>When describing this project, we could emphasize that <code class="language-text">commons</code> is foundational -
the root that everything else builds upon.
Libraries and apps become the trunk and branches, with tests as leaves.
Without clearly defining how arrows show dependencies,
we might easily draw all arrows pointing the opposite way (a <em>reverse</em> dependency graph<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>):</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/25c66cdf7c3fe4e5a6ae07559808d20a/dcc98/graph2.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABbElEQVQoz32T6XKbQBCE/f7Pl0RViaRE5qjYXGJhD9jjc+0iIYhjzQ8W5mh6ZnpfAEII8YmSgSI3ZNlI2/jkT6FbrCwMl4ugqfythvW828sWUCvIM8n51HDtAsE/AI2GIlecjjVdG54Dxqq72zuPVmpNfrAE5xxa601sqf2CIRgdaBtLU08MYt/yZAJd66grw9DvW96y3LU8DvDrp+Dwo6J687eWl+RRBE7Hge/f3vhb2hTbkolHfN8x9A60snTdgHPb+QT6XqCVw1oQvWSazMrqTuhTy97DNHmknBJgTIoA4zijtVuL7QxynDHaY+0C9h+Gy5b//FYcjyJtMmZ1TWxV8HoxaLXMMs9mDoeK83lEXL8EhHmCupooiwElo9+nn5TlQF3NiVlkFJdTvWua2iZ9fgLc6ymKWD6G7X36Br/G9/ZQwm6G6/oDXDtPkUuy156iUElC/+btNfqEYTyvbSBLN6YlzzWD2Bc9sw8qTK1NLUi92wAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/25c66cdf7c3fe4e5a6ae07559808d20a/fcda8/graph2.png" srcset="/static/25c66cdf7c3fe4e5a6ae07559808d20a/12f09/graph2.png 148w,
/static/25c66cdf7c3fe4e5a6ae07559808d20a/e4a3f/graph2.png 295w,
/static/25c66cdf7c3fe4e5a6ae07559808d20a/fcda8/graph2.png 590w,
/static/25c66cdf7c3fe4e5a6ae07559808d20a/efc66/graph2.png 885w,
/static/25c66cdf7c3fe4e5a6ae07559808d20a/c83ae/graph2.png 1180w,
/static/25c66cdf7c3fe4e5a6ae07559808d20a/dcc98/graph2.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>This makes terms like “roots” or “leaves” potentially confusing,
but it’s important to be aware of them as you will likely hear them being used
when talking about graphs.</p>
<h2 id="what-is-it-used-for" style="position:relative;"><a href="#what-is-it-used-for" aria-label="what is it used for permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What is it used for?</h2>
<p>Dependency graph concepts have lots of applications:</p>
<ul>
<li>
<p>Dependency resolution techniques such as <a href="https://research.swtch.com/vgo-mvs">Minimal Version Selection</a>
and <a href="https://en.wikipedia.org/wiki/Backjumping#Graph-based_backjumping">Backtracking</a> are used by package managers.</p>
</li>
<li>
<p>In <a href="https://bazel.build/basics/artifact-based-builds">artifact-based build systems</a> such as Bazel,
a dependency graph is used to determine the order in which different parts of a project should be built.
Having access to this allows building only what is necessary and in the correct sequence.</p>
</li>
<li>
<p><a href="https://www.gnu.org/software/make/">GNU Make</a> uses a dependency graph implicitly through its rules:
each target specifies its dependencies, and Make constructs a graph to determine the order in which to build targets.</p>
</li>
<li>
<p>Native programming language build tools use the dependency graph to fetch and build modules in the correct order, e.g.,
in Go, it is used to maintain a <a href="https://pkg.go.dev/cmd/go/internal/test">cache of passing test results</a>
(where <code class="language-text">go test</code> checks whether any of the transitive dependencies of the tests have changed since the last run).</p>
</li>
</ul>
<h2 id="graph-theory-applications" style="position:relative;"><a href="#graph-theory-applications" aria-label="graph theory applications permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Graph theory applications</h2>
<p><a href="https://en.wikipedia.org/wiki/Graph_theory">Graph theory</a> is a branch of mathematics focused on networks of connected items.
Understanding some graph theory ideas can make managing dependencies much smarter.
Being familiar with the terminology also helps to find relevant tooling,
for instance, knowing that part of the graph is called <em>subgraph</em>
would let you find more relevant results when searching for algorithms to extract a part of the graph.</p>
<h3 id="connected-components" style="position:relative;"><a href="#connected-components" aria-label="connected components permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Connected Components</h3>
<p>A <a href="https://en.wikipedia.org/wiki/Component_(graph_theory)">connected component</a> is a group of nodes
where each one can reach every other by following edges in either direction.
In a dependency graph, this means a set of source code modules that are all linked together by a dependency link
(or a reverse dependency link) — what’s important is that there is some sort of connection.</p>
<p>When two applications share modules in the same connected component, they become indirectly connected
which might make it hard to test or deploy them separately.
In a worse scenario, if the modules of these apps actually import from each other,
then code changes in one app can unexpectedly break another.
Applications with isolated dependencies are much easier to extract and move to separate repositories.</p>
<p>In the example below, the configuration is shared among three
applications making them part of the same connected component.
That is, you can’t move any of the applications along with the shared configuration out of the codebase.
This could be refactored by splitting the shared configuration into separate configurations for each application.</p>
<p>Making changes specific to the <code class="language-text">appA</code> in the <code class="language-text">shared-config</code> no longer triggers rebuilds of all applications
and running all their tests.</p>
<p>One connected component:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/63a2974d950e382f54e628101d633f75/dcc98/graph3.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABd0lEQVQoz41T226DMAzl/79sU9lLH7pN7aRJK1DCpaUhCStxzmQz2vSiaUiRTY6P70lw84UQ8N/vkW3yyIClHwPGE4mM9VnGtg8dMjCDRAG18qgrklOpWfJdQFV6kA93PHEYR4qBtgE+PzUWizXSlw+8vpV4en7Het2gqZlwnwjLZL6InR2PGvnWiNOmDihyh2xrRecsi8yhO3S4rU4cxvUPg4MxBn3fw1onZQUCTG/RNnvJiu8G9w2ttdg65657GGgiWWNheiP9u4pMPBQPY6w4jCthjIMxj30wNzm0XkrhpotUBGu8EHpNqMrf+8qL3h9JMGenQXFbGGObtvZI9k1AXoxYpBusVgWUAoyeSLoj7ArCcvmFNN0gzz2O3ZSh6T2qCsLhoeXFSTZAMuS1yLYGqhxFt2ZyyNmokmQoPAi1IwlyzlDxwAbkmRNeW3kk3OTxFEAe58UNdNlH/meMj+h0WZFpyWdsegDRlMMfzypc4fdP7rLYPz0kqOGefhmzAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/63a2974d950e382f54e628101d633f75/fcda8/graph3.png" srcset="/static/63a2974d950e382f54e628101d633f75/12f09/graph3.png 148w,
/static/63a2974d950e382f54e628101d633f75/e4a3f/graph3.png 295w,
/static/63a2974d950e382f54e628101d633f75/fcda8/graph3.png 590w,
/static/63a2974d950e382f54e628101d633f75/efc66/graph3.png 885w,
/static/63a2974d950e382f54e628101d633f75/c83ae/graph3.png 1180w,
/static/63a2974d950e382f54e628101d633f75/dcc98/graph3.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Three connected components:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/4122e054451c94e1f22a09c0d9cf58bd/dcc98/graph4.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABQUlEQVQoz52T2XKDMAxF/f8f2KYseWhICFnAZg020u3IpoF28kALozHSkSwsyYqZ8Urk+Q9TCPj5AkvAVoYVU0wMpsUmvmLz7n9gmJkyFeFSjMhPPfJTh3M+oKnD78taFCNOx3ZmD9QmsLYJLM97nL0MqEqCul8ZaWrwsbt5iaMKupJs5Nc0qfH+dgks1ihvsiHB6MB2uyviqEQUlSjOBGVHAk2AMR2G3mKaAGvJH8tZxuTg7bXpvJ/4r9ljcNBV648/Pig0ZXKMYXCwI/vvUPhQdtEleOjdzHhhE8PaEOvcqily7CTR2Kc1yjsvhWf2utilFOK3ZroE9vsGSVzhWjBImiLAaMbhs8bx2D2L/h0kenZovIjfmrU1kGUtsqyBrlZzSHNWaT3Rz1nzOm9n6pnxxeCuGW9k6tU1+h24lcmGX/gVrDCb+X8+AAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/4122e054451c94e1f22a09c0d9cf58bd/fcda8/graph4.png" srcset="/static/4122e054451c94e1f22a09c0d9cf58bd/12f09/graph4.png 148w,
/static/4122e054451c94e1f22a09c0d9cf58bd/e4a3f/graph4.png 295w,
/static/4122e054451c94e1f22a09c0d9cf58bd/fcda8/graph4.png 590w,
/static/4122e054451c94e1f22a09c0d9cf58bd/efc66/graph4.png 885w,
/static/4122e054451c94e1f22a09c0d9cf58bd/c83ae/graph4.png 1180w,
/static/4122e054451c94e1f22a09c0d9cf58bd/dcc98/graph4.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p><em>Isolated nodes</em> (nodes that don’t have any edges connected) also are connected components
which may represent software units that are no longer needed.
For instance, a program might have once used a third-party library,
but later stopped using its functionality.
If nothing else in the codebase depends on that library, it is now isolated,
and can be removed to avoid rebuilding.</p>
<h3 id="cut-points-and-bridges" style="position:relative;"><a href="#cut-points-and-bridges" aria-label="cut points and bridges permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cut Points and Bridges</h3>
<p>A cut point (also called a “point of connection” or “articulation point”) is a node
that, if removed, would split the graph into separate components.
A bridge is an edge whose removal would produce a new connected component.</p>
<p>In the example below, if we stop depending on the third-party library <code class="language-text">third-party-lib</code>,
we would stop depending transitively on all those third-party libraries
that <code class="language-text">third-party-lib</code> brought into the dependency graph of our project.</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/8e6adc466028359f4681cf97faa25679/dcc98/graph5.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABL0lEQVQoz42QcW+CMBDF9/0/nUKMEvfPgAoUqAKlFundW6hu0+kYTS536d39+vrecDvM7LPRhFIyqtJBCIODMMgy67NIex+yuPgZ3dHD7nTefgOnISmBLBuw3eaIIokwTLDZHLDbFQjDFHGi/UzXLgTmOaMsgaoC6hp43yvEcQ+lrndT5BmjaxYALwP7lyfwlHvNONYWedb4umvdtdcQBjsDfGw8ZmYHa80fPb5HPCv8UUpQNeGoCKpyqMrLrSbYM73ceVJ43zwbQp4RgiDFapVgvU4RBAJCjDD9a3WzwC+FJ8WQhcUh1Tgd2SsczrQc+MrLcRygdTvr3azCb2+IoVuC0UDXOPQdwY38NLdYoXOMImfs9zWiqISUPPvdf4FEjEoSkrhF/NGirth7Owf8BCCDq5JIqkZ4AAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/8e6adc466028359f4681cf97faa25679/fcda8/graph5.png" srcset="/static/8e6adc466028359f4681cf97faa25679/12f09/graph5.png 148w,
/static/8e6adc466028359f4681cf97faa25679/e4a3f/graph5.png 295w,
/static/8e6adc466028359f4681cf97faa25679/fcda8/graph5.png 590w,
/static/8e6adc466028359f4681cf97faa25679/efc66/graph5.png 885w,
/static/8e6adc466028359f4681cf97faa25679/c83ae/graph5.png 1180w,
/static/8e6adc466028359f4681cf97faa25679/dcc98/graph5.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>To remove a “cut point” like <code class="language-text">third-party-lib</code>, you can replace its functionality with an existing dependency or reimplement it yourself.
This can make builds faster (fewer downloads), more secure, and more reliable.
The <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident">npm left-pad incident</a> shows
how third-party dependencies can cause problems.</p>
<p>Creating isolated groups in the dependency graph is often a good thing as it means those modules can now evolve,
be tested, and deployed independently, reducing risk and complexity.
However, in a large dependency graph, the hard part is to identify the best cut points
as often breaking the dependency between two modules might still leave the part of the dependency
graph you are concerned about connected to the rest of the codebase.</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/3595acd6d87f30b7baa02a570d6beb8b/dcc98/graph6.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABCElEQVQoz52SC2/CIBSF/f9/0OjeWpfMaWsrtLwu3wLa6tRs1ZsQSDh898BhwlnFGIe5H7sK3t8anp+2rL8C3iVN/KU9rwkX1YP62lWaunY0taPcakKQm7qbwEEQI6aL7JvAvvE4G5MptJYM7tqRDnPXI7DcCrPZhvm8pCoF72BVGKbTNZvvwIET/wcOYAHTObwTRHIPrAkoZZDAHQ6PnVsdWRWKz1Wb14d9wSer97xhD9QqsvhoKJZqAKZAnHNXtxmdsjWO4GXYE7kGjnB4+GfOwnLR5iDS+iHgSRhzEGXZsasM/dkEtPZO4KXYGotWgtoLrXY5mAccnpx6D8VS8/pSoxV/whLwB+VLs62kznoJAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/3595acd6d87f30b7baa02a570d6beb8b/fcda8/graph6.png" srcset="/static/3595acd6d87f30b7baa02a570d6beb8b/12f09/graph6.png 148w,
/static/3595acd6d87f30b7baa02a570d6beb8b/e4a3f/graph6.png 295w,
/static/3595acd6d87f30b7baa02a570d6beb8b/fcda8/graph6.png 590w,
/static/3595acd6d87f30b7baa02a570d6beb8b/efc66/graph6.png 885w,
/static/3595acd6d87f30b7baa02a570d6beb8b/c83ae/graph6.png 1180w,
/static/3595acd6d87f30b7baa02a570d6beb8b/dcc98/graph6.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Breaking <code class="language-text">appA -> config1</code> (incorrectly assuming that this as a bridge)
would still leave <code class="language-text">appA</code> connected to the rest of the codebase via the <code class="language-text">libX</code> connection.
To identify that <code class="language-text">libX</code> might still lead to the rest of the codebase via a chain of connections is not trivial
and to be able to refactor the dependency graph so that one can reason about it,
it is often required to use advanced dependency graph querying and visualization tooling.
To estimate how much work it would be to break a connection, one can list all <em>paths</em> between your module
and the undesired dependency, which will be discussed later.</p>
<h3 id="subgraphs" style="position:relative;"><a href="#subgraphs" aria-label="subgraphs permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Subgraphs</h3>
<p>A <em>subgraph</em> is just a smaller part of the whole graph, focusing on a subset of nodes and their connections.
Depending on the complexity and shape of your dependency graph, it might only make sense to interact with
a subgraph of it.
Take a look at <a href="https://www.infoq.com/articles/cloud-native-architecture-adoption-part2/">the dependency graphs of the microservices at tech giants</a>
to appreciate the complexity of their dependency management.</p>
<p>Visualizing or analyzing a subgraph (e.g., all dependencies of a single service) helps you zoom in on what matters for your project.
If the dependencies of a program are complicated,
it may make sense to extract only its direct dependencies and their direct dependencies.
In graph theory terms, this means focusing on nodes that are at most two <em>degrees</em> away from the program node.
The degree of a node refers to the number of direct connections (dependencies) it has.
We can extract a subgraph by limiting our view to nodes within a certain <em>depth</em> (in this case, a depth of two).
By controlling the depth, you avoid being overwhelmed by the entire transitive chain of dependencies.</p>
<p>With the same dependency graph we had seen in the very first graph of the post,</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/495bd132cdce18cfad1b418601440185/dcc98/graph1.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABXUlEQVQoz31TiXKCMBD1///PcepRBARrKRA5cievk1VsUOnOMAt7vH1ZXla4m/f+4VnrURQCx4ShLBWGjjIIJVPdkq0mkOmBB5raEFiaMqTHDh2zeK6b9UTDVs8MgwnOobWhd600fS/bDYyYxAxvzZ7Y/VQSgoe4I19VkuJGexgTVuLQ1BptY8HHNwwn2qH5mHDstle0NVWQ3++uyNIwxENJj1OmsV5fcDgM6BiWAYMTwqBtezh3i1sLNM2AcdCP1Rgd9tyDjxbWLAIC1noo5cC5hjHxXkNMELsQF1xDa4X5/l926Ekeh/2A7UeD6uLgnX+ADr1H8jlis6nwdba4CyKS25sjKwl8XwROOcPQTzLBS67vPA2LpTTTIe1gou9Cs3zS3f3gzkFJFeX+5PIi7JDgI1AWEnnekzTipvCHz6VElnVoaje7Wf8C5hmn2xHkEgMGvRUngSRhqKtlwF9wSK1OVYQfOwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/495bd132cdce18cfad1b418601440185/fcda8/graph1.png" srcset="/static/495bd132cdce18cfad1b418601440185/12f09/graph1.png 148w,
/static/495bd132cdce18cfad1b418601440185/e4a3f/graph1.png 295w,
/static/495bd132cdce18cfad1b418601440185/fcda8/graph1.png 590w,
/static/495bd132cdce18cfad1b418601440185/efc66/graph1.png 885w,
/static/495bd132cdce18cfad1b418601440185/c83ae/graph1.png 1180w,
/static/495bd132cdce18cfad1b418601440185/dcc98/graph1.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>we can extract the subgraph containing dependencies with depth of 2 for <code class="language-text">appB</code>:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/0d20ccc03bf2f4278b691fd02811b01c/dcc98/graph7.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABSklEQVQoz52SbW+CMBSF/f+/zU9L5jLfl7FJCygvSim0fRaKCg41y0ia296eHs65904AnHNtoKktSWyJpEGEGilqosiwjy3WdpgL9tE3GYJ01ZLBYrnn5SXwa73OiCOw5h+EpnEcEkOWwldQEElNenBkB+MxLayLt2vIMbkw93/uYlFkNEbf5Pq9u8kNVV8VXpKttUo5TkfjY6Xs9V6Vllq3TvCxPf8uxUihVhYpHPP3hI/tCRk635D2odg5NpuCt5lktcr8uSrtHwhDx3y+Z3uPcF0wexUslynhznqVTwmNcd6OFBl5ptFVXxJ1st6uCFPKU1eSh5b75LmWtqEsj6Mm1HWFUiXjd9xXOGxQrRtkqMhTyDNIIsMhKc84no/NkLi76+xEAhaLPdPpiiBoKPKxgNFg3yO9jFAsLbtvRfB5RArDMbcjJ0PCH1FwrX1tbWBtAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/0d20ccc03bf2f4278b691fd02811b01c/fcda8/graph7.png" srcset="/static/0d20ccc03bf2f4278b691fd02811b01c/12f09/graph7.png 148w,
/static/0d20ccc03bf2f4278b691fd02811b01c/e4a3f/graph7.png 295w,
/static/0d20ccc03bf2f4278b691fd02811b01c/fcda8/graph7.png 590w,
/static/0d20ccc03bf2f4278b691fd02811b01c/efc66/graph7.png 885w,
/static/0d20ccc03bf2f4278b691fd02811b01c/c83ae/graph7.png 1180w,
/static/0d20ccc03bf2f4278b691fd02811b01c/dcc98/graph7.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<h3 id="transitivity" style="position:relative;"><a href="#transitivity" aria-label="transitivity permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Transitivity</h3>
<p>The <em>transitive closure</em> of a node in a graph is the set of all nodes
that can be reached from that node by following edges.
In the context of a dependency graph, the transitive closure<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup> of a module
is the entire “tree” of things required for that module to work.</p>
<p>In this dependency graph,</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/b14b331e2e57402b0f703b3dbf810fd7/dcc98/graph8.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABb0lEQVQoz52T3W6DMAyF+/7Ptql33bpqGqMUxk+AQEggwTlT0hYoZTeLhGwc58vBNjvclrXWP94ni5IR8mxElmpveW2nnCnvZpdr9wj0nvfLAggCgf0+wHcowYoRRPR8+Qq6W77cN5XqEYUVKkaIQo6GA3FUoeEcwzA8wNbw3RpmzAghBKwlEI1oHG2+Elr36KTwe3N0Pv8ElFJiHK/JziqlbocsjLEYeqBXFp0YoQfn05X4l0IHWALdBXcdoiHElxGnU4n3twzhtwLLrW/iP4BA11oP/DgyHA7pNnDdLSlnoIstgVXZIIlLuPKlPzWUJAz9RlOWnVLK1dBMEKe4rmtI2cEY7WNEGkp12Jphr9AuasQKg7ok8MqiYtor5LwFyzVaDj9CadKja838ZcumLOVWpcX53Pth/gpasOIad4KzDDgeC7y8fiJJCKKxT+omhfcNXhOy1CAMG2/dYLu40eSLH18kokggzwhdS5t/yi8Y36uiPorHyQAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/b14b331e2e57402b0f703b3dbf810fd7/fcda8/graph8.png" srcset="/static/b14b331e2e57402b0f703b3dbf810fd7/12f09/graph8.png 148w,
/static/b14b331e2e57402b0f703b3dbf810fd7/e4a3f/graph8.png 295w,
/static/b14b331e2e57402b0f703b3dbf810fd7/fcda8/graph8.png 590w,
/static/b14b331e2e57402b0f703b3dbf810fd7/efc66/graph8.png 885w,
/static/b14b331e2e57402b0f703b3dbf810fd7/c83ae/graph8.png 1180w,
/static/b14b331e2e57402b0f703b3dbf810fd7/dcc98/graph8.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>both <code class="language-text">appA</code> and <code class="language-text">appB</code> depend on <code class="language-text">secrets</code> (directly) and <code class="language-text">cloud</code> (directly and transitively).
In this cluttered visualization of the graph, the direct dependency edge between <code class="language-text">appA</code>/<code class="language-text">appB</code> and <code class="language-text">cloud</code>
could be removed for clarity as we already know that they are connected:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/3c6d22c8f60ff37820ffb1f9a2c1f596/dcc98/graph9.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABOUlEQVQoz6WS227DIBBE8//fFikPeXBSJVVlnMZgxza+xIBZpjIuudVpIxVpBWJXw5ldFphZzjm/q56QZwTBjY9MEPqO7moe1+I3wXNH4Nwh2gis1wnSI6Fr/yGoFaEqHARX+Dy0/qzOV8E50VnBR4JhUGhbGTJP6f4kNGYkcjB6tG+hFaB1IMRrhLdWZElIYoO3bY7tJkPCNMqCvJoDXrccCuvKgcXGi0WRAIu17+NLgiF5S1icJHhagSx8nPIGmSjv6x7EFz/Jwh/sYYy6DCLcD4NG35+vAwoNfUY4/r3yNIZFVUw70URTFdZbnnIOXUPfU58hvPRNOiTJgNXqA7tdCcEBa50XzTNg/y6xXO7BmIIsp4dmLYeLtiHw1IKxBmmqkQuaCMmhyB3SowKLa2TCopZ0sR0EvwCaOK5JmX+2NwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/3c6d22c8f60ff37820ffb1f9a2c1f596/fcda8/graph9.png" srcset="/static/3c6d22c8f60ff37820ffb1f9a2c1f596/12f09/graph9.png 148w,
/static/3c6d22c8f60ff37820ffb1f9a2c1f596/e4a3f/graph9.png 295w,
/static/3c6d22c8f60ff37820ffb1f9a2c1f596/fcda8/graph9.png 590w,
/static/3c6d22c8f60ff37820ffb1f9a2c1f596/efc66/graph9.png 885w,
/static/3c6d22c8f60ff37820ffb1f9a2c1f596/c83ae/graph9.png 1180w,
/static/3c6d22c8f60ff37820ffb1f9a2c1f596/dcc98/graph9.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>The process of simplifying the graph by removing edges that are implied by other edges is called <em>transitive reduction</em>.
Keep in mind that you would not normally want to do this for any other reason than clearer visualization of the graph.</p>
<p>If your build tool tracks node dependencies by reading build metadata (stored in files maintained by engineers),
this information must stay up-to-date so the build system can correctly identify necessary build steps.
Imagine that at some point in time <code class="language-text">appA</code> used to import some code from <code class="language-text">cloud</code>, however, after some refactoring,
it doesn’t depend on it directly any longer:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/9c36716dac89401f5ad92f6ec7e27622/dcc98/graph10.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7UlEQVQoz62T626DMAxG+/6v16qF/Sy0I5cCXYHEYfY3JRKaimi3jkWyYinW0bHsbLBwRCTd3jGsYWgVUhjNcD3f1czP5hlw6BlKCQ6ZxnZbonpn9N0KIHlGWwu08jifupT74Ru4BF0Ezg3G0aPrrtPLQ7sfDUOIRoJAsf1PkAeIJkP83nAqvLaM8hjwllvkmUFZENqaE03wR2BxDMgzi+xgUBSEZg0wEIMccPsgWHNLrcdBvdzyfCjMI5zr/2EoxGgujLYR1HZMeVz2l9dmKoy/Qitgv6+w251RVbJusaPNxQhOZZ/CGoEbngO/AL3FsdygRxgoAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/9c36716dac89401f5ad92f6ec7e27622/fcda8/graph10.png" srcset="/static/9c36716dac89401f5ad92f6ec7e27622/12f09/graph10.png 148w,
/static/9c36716dac89401f5ad92f6ec7e27622/e4a3f/graph10.png 295w,
/static/9c36716dac89401f5ad92f6ec7e27622/fcda8/graph10.png 590w,
/static/9c36716dac89401f5ad92f6ec7e27622/efc66/graph10.png 885w,
/static/9c36716dac89401f5ad92f6ec7e27622/c83ae/graph10.png 1180w,
/static/9c36716dac89401f5ad92f6ec7e27622/dcc98/graph10.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Now, what if in the build metadata files, the direct dependencies of <code class="language-text">appA</code> are still <code class="language-text">[cloud, secrets]</code>?
The stale build metadata information such as a redundant declaration of the direct dependency won’t be an issue
from the build systems perspective: <code class="language-text">cloud</code> will ultimately end up in the transitive closure of <code class="language-text">appA</code>.</p>
<p>However, if after further refactorings, <code class="language-text">appA</code> no longer depends on <code class="language-text">secrets</code>, we end up with this graph used by the build system:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/4010d49837616f53cb560bb30fd8841c/dcc98/graph11.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABbUlEQVQoz41Ta2/CMAzM//9tmwT7tGkbq3iFpGlh9F0c3+SUQnhqlSq5Od+dazsKDx5mvj75F6YOPaNrGX2H8EosZyOxaz36Ts4Q4husHTHR8FB5Sthoj+WiwWrZQq8J28wHV08Ms/ZYr3rMkzLk5Y5OmN1E2MbDGYLKnYexHtO3OabTObQm7HIOJCJGaoHZbIfJJIGxPJjxgLkUSJIi8GwKOOuhdhkhSxnLeQW9auEs4zc/VuE5uBotVRRwKWOXnyvMLMEaGjDrIX+rxoZWVYk8z+4Opu87OCnnOIB4JnVdXWBKwDOxx35foK4bENGJJHHT1JEgo+s6lGWJqqqP5oOOGsXiVZBkcd5mJVJDyByHX5LWWD2YisFoKtyRr+KPOA5tKCg0/v3D4uX1G4tlHYYk/YvbEvPVdb/ihLr0MJqR/BT4+txCrw8wmo6Cl0KnxX52S4TYNj4sLh2G5ZVlvif0VPA2mS+u1yMxEfwDA9WqZsfWwrYAAAAASUVORK5CYII='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/4010d49837616f53cb560bb30fd8841c/fcda8/graph11.png" srcset="/static/4010d49837616f53cb560bb30fd8841c/12f09/graph11.png 148w,
/static/4010d49837616f53cb560bb30fd8841c/e4a3f/graph11.png 295w,
/static/4010d49837616f53cb560bb30fd8841c/fcda8/graph11.png 590w,
/static/4010d49837616f53cb560bb30fd8841c/efc66/graph11.png 885w,
/static/4010d49837616f53cb560bb30fd8841c/c83ae/graph11.png 1180w,
/static/4010d49837616f53cb560bb30fd8841c/dcc98/graph11.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>Since <code class="language-text">appA</code> depends on <code class="language-text">cloud</code>, it becomes dependent on the transitive closure of <code class="language-text">cloud</code>
which might lead to slower build times (all resources that <code class="language-text">cloud</code> depends on now need to be downloaded to build <code class="language-text">appA</code>).</p>
<h3 id="paths" style="position:relative;"><a href="#paths" aria-label="paths permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Paths</h3>
<p>Finding <em>paths</em> between arbitrary modules in a dependency graph helps understand
how different parts of your system are connected.
In this context, we are primarily interested in finding <em>simple paths</em> — paths where all nodes visited are distinct.</p>
<p>By finding a path from module A to module B, you can see if changes in A might affect B (or vice versa).
This helps estimate the risk of changes and debug issues that propagate through dependencies.
For example, if a module contains source code under a specific license,
you might want to ensure no paths from applications with incompatible licenses lead to it,
preventing its inclusion in the application bundle.</p>
<p>With this contrived example of a dependency graph,</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/fead39b4d93ff64ea6a53d93f9f4db45/dcc98/graph12.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABHklEQVQoz52SC2+CQBCE/f//r03amKooPuoDRO5YuNv7mkNFa9FoN5ANs5O52WMG3FQIoX0hIDawWnrG4z1JcmCzDqiGjtdXg1ux626NMk1KJpOcyThnnlbE0eXQv8KDPofRXaxaBKmq0wCMMXjvzsxel70OvQ/kmWf9XVEeIq5YGzFtsX2uuOYJh+dhJM9Tx+dHxmqpBFX2WWA4LHh/W5HOGmp5WjC0T1U58qwkbhjx2I1xZLuSpgH1LwjGVotiTYNz1z9LaRp5/Q7jyov5aeWF76LivaeWujcRDwXVw27rSGcFZQHhJKiqiMjzOfxNDIhYnHNdbKSS7vtfwY59u3GkqWlDfSj8Q7G7Do/846Vnu8DoK2eaWGx5we85/AGFT7I/NJb8cwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/fead39b4d93ff64ea6a53d93f9f4db45/fcda8/graph12.png" srcset="/static/fead39b4d93ff64ea6a53d93f9f4db45/12f09/graph12.png 148w,
/static/fead39b4d93ff64ea6a53d93f9f4db45/e4a3f/graph12.png 295w,
/static/fead39b4d93ff64ea6a53d93f9f4db45/fcda8/graph12.png 590w,
/static/fead39b4d93ff64ea6a53d93f9f4db45/efc66/graph12.png 885w,
/static/fead39b4d93ff64ea6a53d93f9f4db45/c83ae/graph12.png 1180w,
/static/fead39b4d93ff64ea6a53d93f9f4db45/dcc98/graph12.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>there are two paths from <code class="language-text">appA</code> to <code class="language-text">commons</code>:</p>
<ul>
<li><code class="language-text">appA</code> -> <code class="language-text">libX</code> -> <code class="language-text">libY</code> -> <code class="language-text">commons</code></li>
<li><code class="language-text">appA</code> -> <code class="language-text">secrets</code> -> <code class="language-text">commons</code></li>
</ul>
<p>In a large, highly connected dependency graph, there may be hundreds of paths between two modules.</p>
<p>When listing paths, <em>shortest paths</em> help to understand the minimal set of dependencies connecting two modules.
In contrast, the <em>longest path</em> between two modules tells you how deep the dependency chains are.
The higher the average number of nodes in all paths in the graph, the more interconnected your codebase is.
Having a very interconnected dependency graph might be problematic because it becomes hard to reason about
how changes will propagate and a change in a low-level module can ripple through many layers,
increasing the risk of unexpected breakages.</p>
<h3 id="topological-sort" style="position:relative;"><a href="#topological-sort" aria-label="topological sort permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Topological sort</h3>
<p><em>Topological sort</em> (or <em>order</em>) is a way of ordering the nodes in a dependency graph
so that every node comes after all the nodes it depends on.
A build system might use <em>topological sort</em> to determine what must be built first
and which targets can be built in parallel.</p>
<p>Having access to this contrived dependency graph,</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/2d60b6e1ee98d3e93a2b76d55864ef4a/dcc98/graph13.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABdElEQVQoz42T226rMBBF8/8/d6KmeUj7UgJtuIUQwFw9s45sSEKVnqOONAIzs5f22HijqigulOoq5JmQJiNJPJClk1+3RnyHq6eJkKVLPRPqq3itKjjW5g5UpbwoUTSwe/1kt4vY70/EsdLUs6gqIQwHX3t5iXzvtfQkz3gAZ6IXloVyzkaiY8W1hCK39N08Q5pUfIY1RT4RhRXlBZrafnfIQmYZ24XIROXsLCEiGGPoutb3WBmp6vKu0dmeX21usNvTWqVrHcAy9NCaiaYxTHby9aGf6y5dfejlbuXucA10jfFJOBxygg9Dngoqi3Mr5KlyDFr2ryeOQUeRK2tTz0AzA98OZz48UJEbUNSv3fftNiT4DdBOwjhAkpTU1ejHWgv6bh7V7cB/R35AF6edQXVabbyuO1e56G6H8tPIl7NLy6WY38U+hD/lGvwErCvl60v4sw14fy9IYphG4fvv9e94AprGXS9LeKyJT70/ZTvpr4F/AdsxqhsdhZQLAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/2d60b6e1ee98d3e93a2b76d55864ef4a/fcda8/graph13.png" srcset="/static/2d60b6e1ee98d3e93a2b76d55864ef4a/12f09/graph13.png 148w,
/static/2d60b6e1ee98d3e93a2b76d55864ef4a/e4a3f/graph13.png 295w,
/static/2d60b6e1ee98d3e93a2b76d55864ef4a/fcda8/graph13.png 590w,
/static/2d60b6e1ee98d3e93a2b76d55864ef4a/efc66/graph13.png 885w,
/static/2d60b6e1ee98d3e93a2b76d55864ef4a/c83ae/graph13.png 1180w,
/static/2d60b6e1ee98d3e93a2b76d55864ef4a/dcc98/graph13.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>and oversimplifying what a modern build system would do with this dependency graph,
we could produce a parallelizable list of build actions.</p>
<p>In order to build a particular node (say, produce a binary executable), we need to first build all nodes
that this node depends on (transitively).
For instance, let’s say we want to build <code class="language-text">appA</code>:</p>
<ol>
<li>To build <code class="language-text">appA</code>, we need to first build its direct dependency, <code class="language-text">libX</code>.</li>
<li>To build <code class="language-text">libX</code>, we need to first build its direct dependencies, <code class="language-text">commons</code> and <code class="language-text">secrets</code>.</li>
<li><code class="language-text">commons</code> and <code class="language-text">secrets</code> can be built immediately as they do not have any dependencies.</li>
</ol>
<p>This means that our dependency graph nodes would be sorted like this:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">[secrets,commons],libX,appA</code></pre></div>
<p><code class="language-text">secrets</code> and <code class="language-text">commons</code> can be built in parallel, and once both of them are built,
we can start building <code class="language-text">libX</code>, and, thereafter, <code class="language-text">appA</code>.</p>
<p>Parallelism emerges only when the graph has branches, that is, multiple independent subgraphs
that can be built concurrently once their dependencies are satisfied.
Practically, this means that flattening overly nested or serial dependencies can unlock better
parallelism leading to faster builds.</p>
<p>In an extreme case, if your graph is in the shape of a linked list
such as <code class="language-text">app -> lib -> secrets -> commons</code>, no parallelism can be achieved
because every node would need to wait for its dependency to be built first.
However, even when components must be built sequentially due to their dependencies,
parallelism can still occur within each component,
for instance, compiling multiple source files simultaneously within a single library.</p>
<h3 id="cycles" style="position:relative;"><a href="#cycles" aria-label="cycles permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cycles</h3>
<p>Cycles in a dependency graph mean that some components depend on each other in a loop,
making it impossible to determine the order in the dependency chain.
Build systems like Bazel require the dependency graph to be a directed graph without cycles
(commonly known as <em>Directed Acyclic Graph</em>, or <em>DAG</em>)
because cycles would lead to infinite build loops
and prevent the system from knowing which component to build first.</p>
<p>With this graph having a cycle (<code class="language-text">libA</code> -> <code class="language-text">libB</code> -> <code class="language-text">libC</code>), it is unclear
in what order dependencies of <code class="language-text">app</code> should be built:</p>
<span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="/static/fcbac7925043b206a08dc59f4952ee17/dcc98/graph14.png" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAzElEQVQoz6WTbWvDMAyE+///3wbrly1bwhJnjdPmpXZs6RlJ6EpbysA5MJIxOo47eccTqOpfbRvlM+uoqsDk9eb9HrtnZJeDQm0cWXbCVFMa4XVgHRr6frm7s0NE0hTOiFFpbVwU9p3i3EbC4JXvIvD6UmNK4bxN4ewhjGPA2h6J4H0i4b2H4+DwTrBNR4ySnvKl/zHC/s2Sf3m825TyWudAiuJEc5iIgTTC1cOr0mVlYnywI0lha4U8H6hN2LrYa7UH4eP9SFn+/1N+AUXrtW+qzbLAAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="Dependency Graph" title="Dependency Graph" src="/static/fcbac7925043b206a08dc59f4952ee17/fcda8/graph14.png" srcset="/static/fcbac7925043b206a08dc59f4952ee17/12f09/graph14.png 148w,
/static/fcbac7925043b206a08dc59f4952ee17/e4a3f/graph14.png 295w,
/static/fcbac7925043b206a08dc59f4952ee17/fcda8/graph14.png 590w,
/static/fcbac7925043b206a08dc59f4952ee17/efc66/graph14.png 885w,
/static/fcbac7925043b206a08dc59f4952ee17/c83ae/graph14.png 1180w,
/static/fcbac7925043b206a08dc59f4952ee17/dcc98/graph14.png 2613w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async">
  </a>
    </span>
<p>When adopting a build system that needs to construct a DAG out of your dependency graph,
you might need to make refactorings in the codebase to break cycles.
This is particularly true for legacy codebases written in Python, JavaScript, or Ruby
where native build tools might tolerate cycles in the dependency graph.</p>
<p>A DAG is a very common data structure used by various build systems such as <a href="https://bazel.build/">Bazel</a>,
<a href="https://www.pantsbuild.org/">Pants</a>, and <a href="https://buck2.build/">Buck2</a>,
process orchestration software such as <a href="https://dagster.io/">Dagster</a>, <a href="https://flyte.org/">Flyte</a>, and <a href="https://airflow.apache.org/">AirFlow</a>,
and software engineering tooling such as <a href="https://git-scm.com/docs/gitglossary/2.48.0#def_DAG">Git</a>.</p>
<hr>
<p>In this post, we have reviewed the basic principles related to graph theory and talked about dependency graphs
that consist of modules in a codebase.
In sophisticated build systems, you’ll find that more kinds of graphs exist, with differences between them.
In Bazel, there is a build graph (what we have called dependency graph in this post for simplicity)
and an action graph that breaks down each component into specific actions (like compiling a file or generating code)
that need to be executed.
There are some more advanced kinds of graphs you might run into
such as the evaluation graph (Skyframe graph) representing Bazel’s internal state
(see <a href="https://github.com/tweag/skyscope">skyscope</a> to learn more)
and the shadow dependency graph which is created
when <a href="https://bazel.build/versions/8.1.0/extending/aspects?hl=en#aspect_basics">aspects</a> are used.</p>
<p>In the next blog post, we will cover common problems associated with managing project dependencies
and share best practices for keeping a large dependency graph healthy over time.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">The reversed dependency graph concept is useful in scenarios like impact analysis
(e.g., “If changes are made to this core library, what other components will be affected?”).<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">You won’t see this term often, but the transitive closure that also includes the node itself
from which we start the search is called a <em>reflexive transitive closure</em>.<a href="#fnref-2" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-09-04-introduction-to-dependency-graph/</link><guid isPermaLink="false">https://tweag.io/blog/2025-09-04-introduction-to-dependency-graph/</guid><pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[CodeQL: code organization, metadata, and running in CI]]></title><description><![CDATA[<p>In the <a href="../2025-08-07-codeql-intro/">previous blog post</a> of this series, I talked about CodeQL,
a static analyzer from GitHub that performs semantic search queries on source code to extract structured data.
I described how I wrote my first CodeQL query and how I executed it locally.
In this second blog post, I want to go beyond that.</p>
<p>I will cover aspects that are required for putting custom queries into production. I’ll explain:</p>
<ul>
<li>how CodeQL sources are organized,</li>
<li>what query metadata is,</li>
<li>how to run CodeQL in GitHub Actions, and</li>
<li>how to visualize results.</li>
</ul>
<p>While the first two topics are specific to teams that need to write their own queries,
the last two are applicable both to teams that write their own queries
and to teams relying on the default queries shipped with CodeQL (which do capture a vast number of issues already).</p>
<p>I won’t dive deep on any topic,
but rather give an overview of the features you will most likely need to put your own CodeQL queries into production.
I’ll often link to GitHub’s official documentation,
so that you have quick access to the documentation most useful to you.
Finding what you need can be a bit of a challenge,
because CodeQL’s documentation is spread over both <a href="https://docs.github.com/en/code-security">https://docs.github.com/en/code-security</a>
and <a href="https://codeql.github.com/docs/">https://codeql.github.com/docs/</a>.</p>
<h2 id="structure-of-codeql-sources" style="position:relative;"><a href="#structure-of-codeql-sources" aria-label="structure of codeql sources permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Structure of CodeQL sources</h2>
<p>There are four main types of CodeQL file:</p>
<ul>
<li>
<p><code class="language-text">*.ql</code> files are query files. A query is an executable request and a query file must contain exactly one query.
I will describe the query syntax below. A query file cannot be imported by other files.</p>
</li>
<li>
<p><code class="language-text">*.qll</code> files are library files. A library file can contain types and predicates, but it cannot contain a query. Library files can be imported.</p>
</li>
<li>
<p><code class="language-text">*.qls</code> files are YAML files describing query suites. They are used to select queries, based on various filters such as a query’s filename, name, or metadata. Query suites are documented in detail in the <a href="https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-codeql-query-suites">official documentation</a>.</p>
</li>
<li>
<p><code class="language-text">*.qlpack</code> files are YAML files describing packs. Packs are containers for the three previous kind of files. A pack can either be a query pack, containing queries to be run; a library pack, containing code to be reused; or a model pack, which is an experimental kind of pack meant to extend existing CodeQL rules. Packs are described in detail <a href="https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs">here</a>.</p>
<p>When developing custom queries, I need to wrap them in a query pack in order to declare on what parts of the CodeQL standard library my queries depend (here’s an example to show how to depend on the <a href="https://github.com/tweag/java-security-codeql/blob/c858cb2f1833e4c0a47cc133da471123eaa94136/codeql-queries/qlpack.yml#L7">Java standard library</a>).</p>
</li>
</ul>
<p>Queries in <code class="language-text">*.ql</code> files have the following structure (as explained in more detail in the <a href="https://codeql.github.com/docs/ql-language-reference/queries/">official documentation</a>):</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">from /* ... variable declarations ... */
where /* ... logical formula ... */
select /* ... expressions ... */</code></pre></div>
<p>This can be understood like an SQL query:</p>
<ul>
<li>First, the <code class="language-text">from</code> clause declares typed variables that can be referenced in the rest of the query.
Because types define predicates, this clause already constrains the possible instances returned
by the <code class="language-text">where</code> clause that follows.</li>
<li>The <code class="language-text">where</code> clause constrains the query to only return the variables that satisfy the logical
formula it contains. It can be omitted, in which case all instances of variables with the type
specified in the <code class="language-text">from</code> clause are returned.</li>
<li>The <code class="language-text">select</code> clause limits the query to operate on the variables
declared in the <code class="language-text">from</code> clause. The <code class="language-text">select</code> clause can also contain
<a href="https://codeql.github.com/docs/writing-codeql-queries/defining-the-results-of-a-query/">formatting instructions</a>,
so that the results of the query are more human readable.</li>
</ul>
<p>To give an example of a query, if I need to write a query to track
<a href="https://codeql.github.com/docs/codeql-language-guides/analyzing-data-flow-in-java/#using-global-taint-tracking">tainted data in Java</a>, in a file named <code class="language-text">App.java</code>, I’ll write this to start somewhere
and will refine the <code class="language-text">where</code> clause iteratively, based on the query’s result:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java">from <span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> node <span class="token comment">// A node in the syntax tree</span>
where node<span class="token punctuation">.</span><span class="token function">getLocation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getFile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"App"</span>  <span class="token comment">// .java extension is stripped</span>
select node<span class="token punctuation">,</span> <span class="token string">"node in App"</span></code></pre></div>
<p><code class="language-text">select</code> clauses must obey the following constraints with respect to the number of columns selected:</p>
<ul>
<li>A <em>problem</em> query (see below) must select an even number of columns.
The format is supposed to be: <code class="language-text">select var1, formatting_for_var1, var2, formatting_for_var2, ...</code>
where <code class="language-text">formatting_for_var*</code> must be an expression returning a string, as described earlier in the <code class="language-text">select</code> paragraph.
If you omit the formatting, the query is executed, but a warning is issued.</li>
<li>A <em>path-problem</em> query must select four columns, the first three referring to syntax nodes and the fourth
one a string describing the issue. This assumption is required by the <code class="language-text">CodeQL Query Results</code> view in VSCode
to show the results as paths (using the <em>alerts</em> style in the drop down):</li>
</ul>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/cdad7760558eff5c6a7c878a9abe8d6a/9f82e/codeql-path-problem-2-paths.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 60.13513513513513%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABpElEQVQoz32T247aMBRF+ZBCDPHdScg9GSCAOq36/1+0KpsZ1AGmDyuWreN99tlyVkII8jxnt9uhhaC1ltJarLU459L6L59nWuuE2EmEDgjl2Gw2rLxzmBCYhOAkJUWWoTKB2G7Z/odooO97TGp0a2KMZlWWFctyZjkemeaZbhwZx5GubWnqhqZpX9I2DdM0YbRGKYV2AW09q7gZhoHjcmY+LjTdkCjrDuv808j30UOB9cU9rtwEcm1ZxbllnmObN+z0junO2P6KqQakUumClPIL+Ue9689kYst6vWaz/pFYZVmWwvXtAT//wg8X/PyOKWpkvkvjPBJFXXdKxKyjqagTSQ5jka1H3HC5FfZnTNUl51KqJ4cR2x6SSyEeBOP83nv27UDZjGhfYUKFtiFd/M6hCXt0qIjP7otgPDDGULQzxXjBNjOufcPWE9o41CvR6LDqU82Tw7jRSuE+MxyvadVhf8/w1ch+uOL65XWG0WHoT4TDH8L0k/D2G1dPN3cf7+yR1Hi4PAvGT8xRugpV9sjQIENLrh1bIb79U6SvkX5P9pDhX9Y/YCkb3bJ1AAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Result of a path query"
        title="Result of a path query"
        src="/static/cdad7760558eff5c6a7c878a9abe8d6a/fcda8/codeql-path-problem-2-paths.png"
        srcset="/static/cdad7760558eff5c6a7c878a9abe8d6a/12f09/codeql-path-problem-2-paths.png 148w,
/static/cdad7760558eff5c6a7c878a9abe8d6a/e4a3f/codeql-path-problem-2-paths.png 295w,
/static/cdad7760558eff5c6a7c878a9abe8d6a/fcda8/codeql-path-problem-2-paths.png 590w,
/static/cdad7760558eff5c6a7c878a9abe8d6a/9f82e/codeql-path-problem-2-paths.png 820w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<h2 id="query-metadata" style="position:relative;"><a href="#query-metadata" aria-label="query metadata permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Query metadata</h2>
<p>The header of a query defines a set of properties called <em>query metadata</em>:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token comment">/**
 * @name Code injection
 * @description Interpreting unsanitized user input as code allows a malicious user to perform arbitrary
 *              code execution.
 * @kind path-problem
 * @problem.severity error
 * ...
 */</span></code></pre></div>
<p>Query metadata is documented in detail in <a href="https://codeql.github.com/docs/writing-codeql-queries/metadata-for-codeql-queries/">CodeQL’s official documentation</a>. I don’t want to repeat GitHub’s documentation here,
so I’m focusing on the important information:</p>
<ul>
<li><code class="language-text">@kind</code> can take two values: <code class="language-text">problem</code> and <code class="language-text">path-problem</code>. The former is for queries that
flag one specific location, while the latter is for queries that track tainted data flow from a source to a sink.</li>
<li>Severity of issues is defined through two means, depending on whether the query is considered a security-related
one or not 🤷
<ul>
<li><code class="language-text">@problem.severity</code> is used for queries that don’t have <code class="language-text">@tags security</code>. <code class="language-text">@problem.severity</code> can be one
of <code class="language-text">error</code>, <code class="language-text">warning</code>, or <code class="language-text">recommendation</code>.</li>
<li><code class="language-text">@security-severity</code> is a score between <code class="language-text">0.0</code> and <code class="language-text">10.0</code>, for queries with <code class="language-text">@tags security</code>.</li>
</ul>
</li>
</ul>
<p>Metadata is most useful for filtering queries in <code class="language-text">qls</code> files.
This is used extensively in queries shipped with CodeQL itself, as visible for example in
<a href="https://github.com/github/codeql/blob/5c394a9371603540fbff980a7c9abd09d08807a4/misc/suite-helpers/security-experimental-selectors.yml">security-experimental-selectors.yml</a><sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>. To give an idea of the filtering capability, here is an excerpt of this file that declares filtering criteria:</p>
<div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token punctuation">-</span> <span class="token key atrule">include</span><span class="token punctuation">:</span>
    <span class="token key atrule">kind</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> problem
      <span class="token punctuation">-</span> path<span class="token punctuation">-</span>problem
    <span class="token key atrule">precision</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> high
      <span class="token punctuation">-</span> very<span class="token punctuation">-</span>high
    <span class="token key atrule">tags contain</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> security
<span class="token punctuation">-</span> <span class="token key atrule">exclude</span><span class="token punctuation">:</span>
    <span class="token key atrule">query path</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> Metrics/Summaries/FrameworkCoverage.ql
      <span class="token punctuation">-</span> /Diagnostics/Internal/.<span class="token important">*/</span>
<span class="token punctuation">-</span> <span class="token key atrule">exclude</span><span class="token punctuation">:</span>
    <span class="token key atrule">tags contain</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> modeleditor
      <span class="token punctuation">-</span> modelgenerator</code></pre></div>
<p>To smooth the introduction of CodeQL (and security tools in general), I recommend starting small and only
reporting the most critical alerts at first (in other words: filtering aggressively).
This helps to convince teammates that CodeQL reports useful insights, and
it doesn’t make the task of fixing security vulnerabilities look insurmountable.</p>
<p>Once the most critical alerts are fixed, I advise loosening the filtering,
so that pressing — but not critical — issues can be addressed.</p>
<h2 id="running-codeql-in-github-actions" style="position:relative;"><a href="#running-codeql-in-github-actions" aria-label="running codeql in github actions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Running CodeQL in GitHub Actions</h2>
<p>The following GitHub Actions are required to run CodeQL:</p>
<ol>
<li><code class="language-text">github/codeql-action/init</code> installs CodeQL and creates the database. It can be customized
to specify the list of programming languages to analyze, as well as many other options.
Customization is done in the YAML workflow file, or via an external YAML configuration file, as explained in
the <a href="https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning">customize advanced setup</a> documentation.</li>
<li><code class="language-text">github/codeql-action/autobuild</code> is required if you are analyzing a compiled language
(such as C# or Java, as opposed to Python). This action can either work out of the box, guessing
what to do based on the presence of the build files that are idiomatic in your programming
language’s ecosystem. I must admit this is not very principled — you need to look up the
<a href="https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages">corresponding documentation</a>
to see how CodeQL is going to behave for your programming language and platform.
If the automatic behavior doesn’t work out of the box,
you can manually specify the build commands to perform.</li>
<li><code class="language-text">github/codeql-action/analyze</code> runs the queries. Its results are used
to populate the <em>Security</em> tab, as shown below.</li>
</ol>
<p>Since the actions work out of the box on GitHub, replicating them in another CI/CD system
is non-trivial: you will have to build your own solution.</p>
<h2 id="visualizing-results" style="position:relative;"><a href="#visualizing-results" aria-label="visualizing results permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Visualizing results</h2>
<p>Once CodeQL executes successfully in CI, GitHub’s UI picks up its results automatically
and shows them in the <em>Security</em> tab:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/228cb70d8b95af5db4a069819cd9af96/eafa0/codeql-ui-security.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 54.72972972972974%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB6UlEQVQoz0VS2W7bMBBUm8CJbUkkxZsUdVq207hAH9r+/59NsWs7fRiQwg5nd3ZUKZNwutzwflRolYMyEdpl7PYtqm87VC/vqL6/feH1rca+7vD63vCdePTdSIff1z+o9keJfU1ingskaHzPxFoY7PYCjbQ4NB2OrQbxre8hO4e3g4A0kbnEc2FEFccrhvUK3y/QocDEET7PjFhWxLIg9AvaLjCUjshpQAwFQnlIm9Eoj1o61MqjSvMHptMP2DSxmA4DXJ5R5gvyuGFcP9BPZxyFRS3vDw/C4igdCx1awzUC2a7oURpOXwU6hU6YzzfM2yfSsHEDZTOjcz1DmoSmC0jjGf18ZVC9CmXlCdVjdII0Gf10Ydv0WLvhLkh3X/4LKo80buyGwIImDIjDiYm8B+lYkAjUKM8burFAugTRRdTqziF7bRfR2AmNndG4Ba3OqPr5gtuvv/yYLAsdeYJh/WDLZbnATrTfgZs+A6Ad1l2AtAXCjoxWJ1SUHJFI6Ekma7TD9fIT/Xx+CI4w/i5qw4gUBiiaOJwg4gbp1/uET5v1IzW2bDOm0ycLztsNfllgygDtCjez5EBHROmwCQOnAmpTeAXVM4gnSFCYxP8gJUyh2TzCpBE2TjCBTrKYUasATSHqDBk3CNPjH+/tFVB4uNx1AAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="GitHub Security UI"
        title="GitHub Security UI"
        src="/static/228cb70d8b95af5db4a069819cd9af96/fcda8/codeql-ui-security.png"
        srcset="/static/228cb70d8b95af5db4a069819cd9af96/12f09/codeql-ui-security.png 148w,
/static/228cb70d8b95af5db4a069819cd9af96/e4a3f/codeql-ui-security.png 295w,
/static/228cb70d8b95af5db4a069819cd9af96/fcda8/codeql-ui-security.png 590w,
/static/228cb70d8b95af5db4a069819cd9af96/efc66/codeql-ui-security.png 885w,
/static/228cb70d8b95af5db4a069819cd9af96/eafa0/codeql-ui-security.png 939w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>You may wonder why you cannot see the <em>Security</em> tab on
<a href="https://github.com/tweag/java-security-codeql">the repository</a> used to create this post’s screenshots yourself.
This is because, as <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository#granting-access-to-security-alerts">GitHub’s documentation</a> explains, security alerts are only visible to people
with the necessary rights to the repository. The required rights depend on whether the repository is owned
by a user or an organisation. In any case, security alerts cannot be made visible to people
who do not have at least some rights to the relevant repository.
Clicking on <em>View alerts</em> brings up the main CodeQL view:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/565d2686931372556a47d70af4a1cc7d/58bb7/codeql-ui-code-scanning.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 37.83783783783784%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABJUlEQVQoz5WSb0/DIBDG0Wjipt1aKJQ/vVLWsm7W+GLf/7s9Bto5jSbGF788DwfhjjuY1oSnTYFNwVGUNbY7AS4t7h+3uHvY/BsW44y2G0H9hEo6vJQKe2Gy/sXzXmauPilr4wTbR9AwQRhCIRoU1d/suAZXbS4iFSAayjEmggd3BE4E3i2UKh2y2Nd20Sv1LVZKB+cjlDtA6A7dcM6XM00RhiJsN6HWPbjyELr/Fd54cJ32ffaVIlSqA2+61ROYj2/oj+9oaIQNZ8h2hHTDD1Q7goYZLpxhfGrTKePC66evTQBrwwnH+YI4X3AIE8h4VM2S9Ssp+3dP4GssraUb8ytYtfaCwgTnApzxkLaHTGo8dHtArT12vMlTLCq1Duamy89YevsBQp/JYLuRt5IAAAAASUVORK5CYII='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="GitHub CodeQL UI"
        title="GitHub CodeQL UI"
        src="/static/565d2686931372556a47d70af4a1cc7d/fcda8/codeql-ui-code-scanning.png"
        srcset="/static/565d2686931372556a47d70af4a1cc7d/12f09/codeql-ui-code-scanning.png 148w,
/static/565d2686931372556a47d70af4a1cc7d/e4a3f/codeql-ui-code-scanning.png 295w,
/static/565d2686931372556a47d70af4a1cc7d/fcda8/codeql-ui-code-scanning.png 590w,
/static/565d2686931372556a47d70af4a1cc7d/efc66/codeql-ui-code-scanning.png 885w,
/static/565d2686931372556a47d70af4a1cc7d/58bb7/codeql-ui-code-scanning.png 985w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>As visible in the screenshot, this view allows you to filter the alerts in multiple ways,
as well as to select the branch from which the alerts are shown.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>In this post, I covered multiple aspects that you need to know to put your custom queries
in production. I described how CodeQL codebases are organized and the constraints that individual queries
must obey. I described queries’ metadata and how metadata is used.
I concluded by showing how to run queries in CI and how everyone in a team can visualize the
alerts found. Equipped with this knowledge, I think you are ready to experiment with CodeQL
and later pitch it to your stakeholders, as part of your security posture 😉</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">The file doesn’t have the <code class="language-text">qls</code> extension, but its content is valid <code class="language-text">qls</code> content;
because it is <a href="https://github.com/github/codeql/blob/5c394a9371603540fbff980a7c9abd09d08807a4/java/ql/src/codeql-suites/java-security-and-quality.qls#L3"><code class="language-text">apply</code></a>ied: the <code class="language-text">apply</code> clause simply inserts
a file within a <code class="language-text">qls</code> file.<a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-08-28-codeql-part-two/</link><guid isPermaLink="false">https://tweag.io/blog/2025-08-28-codeql-part-two/</guid><pubDate>Thu, 28 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Performance Testing, Part 1: The Road to Continuous Performance Testing]]></title><description><![CDATA[<p>The performance of a system is critical for the user experience. Whether it’s a website, mobile app, or service, users demand fast response times and seamless functionality.
Performance testing is a non-functional testing technique that evaluates the speed, responsiveness, and stability of a system under different workloads for different purposes.
The primary goal of performance testing is to identify and eliminate performance bottlenecks to ensure that the system meets the expected performance criteria.
It is crucial for understanding the performance of the system under various conditions and ensuring that it can handle real-world usage scenarios effectively.
From my experience, performance testing is usually underestimated and overlooked, as it is generally only run after big feature releases, architectural changes, or when preparing for promotional events.
In this post, I want to explain the foundations of performance testing for the wider engineering community.
In a future post, I’ll talk about <strong>continuous</strong> performance testing.</p>
<h3 id="performance-testing-helps-in" style="position:relative;"><a href="#performance-testing-helps-in" aria-label="performance testing helps in permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Performance testing helps in:</h3>
<ul>
<li><strong>Validating System Performance:</strong> Ensuring that the system performs well under expected load conditions.</li>
<li><strong>Identifying Bottlenecks:</strong> Detecting performance issues that could degrade the user experience.</li>
<li><strong>Ensuring Scalability:</strong> Verifying that the system can scale to accommodate increased load, and also decreasing load.</li>
<li><strong>Improving User Experience:</strong> Providing a smooth and responsive experience constantly for end-users to increase loyalty.</li>
</ul>
<h2 id="performance-testing-process" style="position:relative;"><a href="#performance-testing-process" aria-label="performance testing process permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Performance Testing process</h2>
<p>Like other software development activities, for performance testing to be effective it should be done through a process.
The process requires collaboration with other teams such as business, DevOps, system, and development teams.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/61dd84f299bf50ed75426c2cac9eb309/59b4b/Performance_Testing_Process.jpg"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 31.756756756756754%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAGABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAIF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB2ZAD/8QAGRAAAwADAAAAAAAAAAAAAAAAAAESERMh/9oACAEBAAEFAowW0bO//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGRABAAIDAAAAAAAAAAAAAAAAAQAREiEi/9oACAEBAAY/AuXGUhZHU//EABwQAQACAQUAAAAAAAAAAAAAAAEAMREhQVFhkf/aAAgBAQABPyHmuhXk16mCmLC2uLn/2gAMAwEAAgADAAAAEPPP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxABAAMAAwEAAAAAAAAAAAAAAQARITFBUaH/2gAIAQEAAT8QS7cvdFtust9iogErA21wjCyU2d/k/9k='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Performance Testing Process"
        title="Performance Testing Process"
        src="/static/61dd84f299bf50ed75426c2cac9eb309/1c72d/Performance_Testing_Process.jpg"
        srcset="/static/61dd84f299bf50ed75426c2cac9eb309/a80bd/Performance_Testing_Process.jpg 148w,
/static/61dd84f299bf50ed75426c2cac9eb309/1c91a/Performance_Testing_Process.jpg 295w,
/static/61dd84f299bf50ed75426c2cac9eb309/1c72d/Performance_Testing_Process.jpg 590w,
/static/61dd84f299bf50ed75426c2cac9eb309/a8a14/Performance_Testing_Process.jpg 885w,
/static/61dd84f299bf50ed75426c2cac9eb309/fbd2c/Performance_Testing_Process.jpg 1180w,
/static/61dd84f299bf50ed75426c2cac9eb309/59b4b/Performance_Testing_Process.jpg 3538w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Let’s explain the process with a real-world scenario.
Imagine Wackadoo Corp wants to implement performance testing because they’ve noticed their e-commerce platform slows down dramatically during peak sales events, leading to frustrated customers and lost revenue.
When this issue is raised to the performance engineers, they suspect it could be due to inadequate server capacity or inefficient database queries under heavy load and recommend running performance tests to pinpoint the problem.
The engineers begin by <strong>gathering requirements</strong>, such as simulating 10,000 concurrent users while maintaining response times under 2 seconds, and then create test scripts to mimic real user behavior, like browsing products and completing checkouts.</p>
<p>A <strong>testing environment</strong> mirroring production is set up, and <strong>the scripts are executed</strong> while the system is <strong>closely monitored</strong> to ensure it handles the expected load.
After the first test run, the engineers <strong>analyze the results</strong> and identify slow database queries as the primary bottleneck.
They <strong>optimize the queries</strong>, add <strong>caching</strong>, and <strong>re-run the tests</strong>, repeating this process until the system meets all performance criteria.
Once satisfied, they <strong>publish the final results</strong>, confirming the platform can now handle peak traffic smoothly, improving both customer experience and sales performance.</p>
<h2 id="how-to-apply-performance-testing" style="position:relative;"><a href="#how-to-apply-performance-testing" aria-label="how to apply performance testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to Apply Performance Testing</h2>
<p>Like functional testing, performance testing should be integrated at every level of the system, starting from the unit level up.
The <a href="https://en.wikipedia.org/wiki/Test_automation#Testing_at_different_levels">test pyramid</a> traditionally illustrates functional testing, with unit tests at the base, integration tests in the middle, and end-to-end or acceptance tests at the top.
However, the non-functional aspect of testing—such as performance testing—often remains less visible within this structure. It is essential to apply appropriate non-functional tests at each stage to ensure a comprehensive evaluation.
By conducting tailored performance tests across different levels, we can obtain early and timely feedback, enabling continuous assessment and improvement of the system’s performance.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/80e8ee86ec5ec05cf641e6b07674d6ea/d41ab/test-pyramid-with-non-functional-tests.jpg"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 72.2972972972973%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAC/9oADAMBAAIQAxAAAAHuRNS1FP/EABgQAQEAAwAAAAAAAAAAAAAAAAEAAhEx/9oACAEBAAEFAsstSrDsely//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFREBAQAAAAAAAAAAAAAAAAAAAhD/2gAIAQIBAT8BU//EABcQAAMBAAAAAAAAAAAAAAAAAAABEBH/2gAIAQEABj8Cmjv/xAAZEAEAAwEBAAAAAAAAAAAAAAABABEhMVH/2gAIAQEAAT8hsAFrAMa8lWINnVgtoyAcT//aAAwDAQACAAMAAAAQdM//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPxAn/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAx/9oACAECAQE/EA6T/8QAGxABAAIDAQEAAAAAAAAAAAAAAQARITFRYUH/2gAIAQEAAT8QHPia4djSpb9MMhVmTj9JbnAB4GiXyU3DEG231n//2Q=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Performance Testing for Test Levels "
        title="Performance Testing for Test Levels "
        src="/static/80e8ee86ec5ec05cf641e6b07674d6ea/1c72d/test-pyramid-with-non-functional-tests.jpg"
        srcset="/static/80e8ee86ec5ec05cf641e6b07674d6ea/a80bd/test-pyramid-with-non-functional-tests.jpg 148w,
/static/80e8ee86ec5ec05cf641e6b07674d6ea/1c91a/test-pyramid-with-non-functional-tests.jpg 295w,
/static/80e8ee86ec5ec05cf641e6b07674d6ea/1c72d/test-pyramid-with-non-functional-tests.jpg 590w,
/static/80e8ee86ec5ec05cf641e6b07674d6ea/a8a14/test-pyramid-with-non-functional-tests.jpg 885w,
/static/80e8ee86ec5ec05cf641e6b07674d6ea/fbd2c/test-pyramid-with-non-functional-tests.jpg 1180w,
/static/80e8ee86ec5ec05cf641e6b07674d6ea/d41ab/test-pyramid-with-non-functional-tests.jpg 2353w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<h2 id="types-of-performance-testing" style="position:relative;"><a href="#types-of-performance-testing" aria-label="types of performance testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Types of Performance Testing</h2>
<p>There are several types of performance tests, each designed to evaluate different aspects of system performance.
We can basically categorize performance testing with three main criteria:</p>
<ul>
<li>Load; for example, the number of virtual users</li>
<li>The strategy for varying the load over time</li>
<li>How long we apply performance testing</li>
</ul>
<p>The following illustrates the different types of performance testing with regards to the three main criteria.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/55ad1e6d87e0e15671b23e7721c95840/0e758/three-criteria-types-of-performance-testing.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 54.05405405405405%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABsUlEQVQoz3VSa3OiQBDk//+lfLlcvqSuKqYSNQioKAKCyPuxLPvoq12UkNylqa5hh55hYNqQUkJBRSEE+oFqDmzQec45+DCAEwLeK/YQjGn9nfd6BWNKSgnGGY5xCNPb45TESPMMJI5AiwK1u0dl26h2W9AihypXA4yDjFT3xvc3KSRljoYSrN5eIAjRxRPlLYqvNXcY80PXEwgp4YQefMuEvd2MD6TQnaQQU+v7VVctjm4I73DGQAcYSZLg1+MjfN+HHXpa7lhreMc9tlGANK80q6bFQBmagqCtCJpynLwtchze3+B/rNFXFQzGGOqmAWccpu+CphmcjzWiIseracFyAninC85Rhq6hOLsZgl2Ka1iCUY6iavG6dLDeHEAo/fxkLiXck4vl0wLPiw3+LEw8/H5BnXXoyl5PVuUd6qJDfYt6Si4hqIQYZktR6PkA+30Fy/IQ5gmC6wWmd0BW1ONvxL8L+A6lmRq2bYPl8wK74IQou+J4Oeu42rpTgfKksorgYow328w5NVQbIl2vvcg4B7uZt2wabCwbcRxr3f+afDH2/PATwjBEmqb4STvP/QVVvFHyLpAHSgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Performance Testing Types"
        title="Performance Testing Types"
        src="/static/55ad1e6d87e0e15671b23e7721c95840/fcda8/three-criteria-types-of-performance-testing.png"
        srcset="/static/55ad1e6d87e0e15671b23e7721c95840/12f09/three-criteria-types-of-performance-testing.png 148w,
/static/55ad1e6d87e0e15671b23e7721c95840/e4a3f/three-criteria-types-of-performance-testing.png 295w,
/static/55ad1e6d87e0e15671b23e7721c95840/fcda8/three-criteria-types-of-performance-testing.png 590w,
/static/55ad1e6d87e0e15671b23e7721c95840/efc66/three-criteria-types-of-performance-testing.png 885w,
/static/55ad1e6d87e0e15671b23e7721c95840/c83ae/three-criteria-types-of-performance-testing.png 1180w,
/static/55ad1e6d87e0e15671b23e7721c95840/0e758/three-criteria-types-of-performance-testing.png 2502w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>The three main criteria are a good starting point, but they don’t completely characterize the types performance tests.
For example, we can also vary the type of load (for example, to test CPU-bound or I/O-heavy tasks) or the testing environment (for example, whether the system is allowed to scale up the number of instances).</p>
<h3 id="load-testing" style="position:relative;"><a href="#load-testing" aria-label="load testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Load Testing</h3>
<p>Load testing is a basic form of performance testing that evaluates how a system behaves when subjected to a specific level of load.
This specific load represents the optimal or expected amount of usage the system is designed to handle under normal conditions.
The primary goal of load testing is to verify whether the system can deliver the expected responses while maintaining stability over an extended period.
By applying this consistent load, performance engineers can observe the system’s performance metrics, such as response times, resource utilization, and throughput, to ensure it functions as intended.</p>
<ul>
<li>Basic and widely known form of performance testing</li>
<li>Load tests are run under the optimum load of the system</li>
<li>Load tests give a result that real users might face in production</li>
<li>Easiest type to run in a CI/CD pipeline</li>
</ul>
<p>Let’s make it clearer by again looking at Wackadoo Corp.
Wackadoo Corp wants to test that a new feature is performing similarly to the system in production.
The business team and performance engineers have agreed that the new feature should meet the following requirements while handling <strong>5,000 concurrent users</strong>:</p>
<ul>
<li>It can handle <strong>1,000 requests</strong> per second (rps)</li>
<li>95% of the response times are less than <strong>1,000 ms</strong></li>
<li>Longest responses are less then <strong>2,000 ms</strong></li>
<li><strong>0%</strong> error rate</li>
<li>The test server is not exceeding <strong>70% of CPU</strong> usage with <strong>4GB of RAM</strong></li>
</ul>
<p>With these constraints in place, Wackadoo Corp can deploy the new
feature in a testing environment and observe how it performs.</p>
<h3 id="stress-testing" style="position:relative;"><a href="#stress-testing" aria-label="stress testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stress Testing</h3>
<p>Stress testing evaluates a system’s upper limits by pushing it beyond normal operation to simulate extreme conditions like high traffic or data processing.
It identifies breaking points and assesses the system’s ability to recover from failures.
This testing uncovers weaknesses, ensuring stability and performance during peak demand, and improves reliability and fault tolerance.</p>
<ul>
<li>Tests the upper limits of the system</li>
<li>Requires more resources than load testing, to create more virtual users, etc.</li>
<li>The boundary of the system should be investigated during the stress test</li>
<li>Stress tests can break the system</li>
<li>Stress tests can give us an idea about the performance of the system under heavy loads, such as promotional events like Black Friday</li>
<li>Hard to run in a CI/CD pipeline since the system is intentionally prone to fail</li>
</ul>
<p>Wackadoo Corp wants to investigate the system behavior when exceeding the optimal users/responses so it decides to run a <strong>stress test</strong>.
Performance engineers have the metrics for the upper limit of the system, so during the tests the load will be increased gradually until the peak level.
The system can handle up to <strong>10,000 concurrent users</strong>.
The expectation is that the system will continue to respond, but the response metrics will degrade within the following <strong>expected limits</strong>:</p>
<ul>
<li>It can handle <strong>800 requests</strong> per second (rps)</li>
<li>95% of the response times are less than <strong>2,500 ms</strong></li>
<li>Longest responses are less then <strong>5,000 ms</strong></li>
<li><strong>10%</strong> error rate</li>
<li>The test server is around <strong>95% of CPU</strong> usage with <strong>4GB of RAM</strong></li>
</ul>
<p>If any of these limits are exceeded when monitoring in the test
environment, then Wackadoo Corp knows it has a decision to make
about resource scaling and its associated costs, if no further
efficiencies can be made.</p>
<h3 id="spike-testing" style="position:relative;"><a href="#spike-testing" aria-label="spike testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Spike Testing</h3>
<p>A spike test is a type of performance test designed to evaluate how a system behaves when there is a sudden and significant increase or decrease in the amount of load it experiences.
The primary objective of this test is to identify potential system failures or performance issues that may arise when the load changes unexpectedly or reaches levels that are outside the normal operating range.</p>
<p>By simulating these abrupt fluctuations in load, the spike test helps to uncover weaknesses in the system’s ability to handle rapid changes in demand.
This type of testing is particularly useful for understanding how the system responds under stress and whether it can maintain stability and functionality when subjected to extreme variations in workload.
Ultimately, the spike test provides valuable insights into the system’s resilience and helps ensure it can manage unexpected load changes without critical failures.</p>
<ul>
<li>Spike tests give us an idea about the behavior of the system under unexpected increases and decreases in load</li>
<li>We can get an idea about how fast the system can scale-up and scale-down</li>
<li>They can require additional performance testing tools, as not all tools support this load profile</li>
<li>Good for some occasions like simulating push notifications, or critical announcements</li>
<li>Very hard to run in a CI/CD pipeline since the system is intentionally prone to fail</li>
</ul>
<p>Let’s look at an example again, Wackadoo Corp wants to send push notifications to <strong>20% of the mobile users</strong> at 3pm for Black Friday.
They want to investigate the system behavior when the number of users increase and decrease suddenly so they want to run a <strong>spike test</strong>.
The system can handle up to <strong>10,000 concurrent users</strong>, so the load will be increased to this amount in <strong>10 seconds</strong> and then decreased to <strong>5,000 users</strong> in <strong>10 seconds</strong>.
The expectation is that the system keeps responding, but the response metrics increase within the following <strong>expected limits</strong>:</p>
<ul>
<li>Maximum latency is <strong>500ms</strong></li>
<li>95% of the response times are less than <strong>5,000 ms</strong></li>
<li>Longest responses are less then <strong>10,000 ms</strong></li>
<li><strong>15%</strong> error rate</li>
<li>The test server is around <strong>95% of CPU</strong> usage but it should decrease when the load decreases</li>
</ul>
<p>Again, if any of these expectations are broken, it may suggest to
Wackadoo Corp that its resources are not sufficient.</p>
<h3 id="endurance-testing-soak-testing" style="position:relative;"><a href="#endurance-testing-soak-testing" aria-label="endurance testing soak testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Endurance Testing (Soak Testing)</h3>
<p>An endurance test focuses on evaluating the upper boundary of a system over an extended period of time.
This test is designed to assess how the system behaves under sustained high load and whether it can maintain stability and performance over a prolonged duration.</p>
<p>The goal is to identify potential issues such as memory leaks, resource exhaustion, or degradation in performance that may occur when the system is pushed to its limits for an extended time.
By simulating long-term usage scenarios, endurance testing helps uncover hidden problems that might not be evident during shorter tests.
This approach ensures that the system remains reliable and efficient even when subjected to continuous high demand over an extended period.</p>
<ul>
<li>Soak tests run for a prolonged time</li>
<li>They check the system stability when the load does not decrease for a long time</li>
<li>Soak testing can give a better idea about the performance of the system for campaigns like Black Friday than the other tests, hence the need for a diverse testing strategy</li>
<li>Hard to run in a CI/CD pipeline since it aims to test for a long period, which goes against the expected short feedback loop</li>
</ul>
<p>This time, Wackadoo Corp wants to send push notifications to <strong>10% of users</strong> at every hour, starting from 10am until 10pm, for Black Friday to increase sales for a one-day 50%-off promotion.
They want to investigate the system behavior when the number of users increase, but the load stays stable between nominal and the upper-boundary for a long time so they want to run an <strong>endurance test</strong>.
The system can handle up to <strong>10,000 concurrent users</strong>, so the load will be increased to <strong>8,000</strong> users in <strong>30 seconds</strong> and it will be kept busy.
The expectation is that the system keeps responding, but the response metrics increase within the following <strong>expected limits</strong>:</p>
<ul>
<li>Maximum latency is <strong>300ms</strong></li>
<li>95% of the response times are less than <strong>2,000 ms</strong></li>
<li>Longest responses are less then <strong>3,000 ms</strong></li>
<li><strong>5%</strong> error rate</li>
<li>The test server is around <strong>90% of CPU</strong> usage</li>
</ul>
<h3 id="scalability-testing" style="position:relative;"><a href="#scalability-testing" aria-label="scalability testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Scalability Testing</h3>
<p>Scalability testing is a critical type of performance testing that evaluates how effectively a system can manage increased load by incorporating additional resources, such as servers, databases, or other infrastructure components.
This testing determines whether the system can efficiently scale up to accommodate higher levels of demand as user activity or data volume grows.</p>
<p>By simulating scenarios where the load is progressively increased, scalability testing helps identify potential bottlenecks, resource limitations, or performance issues that may arise during expansion.
This process ensures that the system can grow seamlessly to meet future requirements without compromising performance, stability, or user experience.
Ultimately, scalability testing provides valuable insights into the system’s ability to adapt to growth, helping organizations plan for and support increasing demands over time.</p>
<ul>
<li>Scalability tests require collaboration for system monitoring and scaling</li>
<li>They can require more load generators, depending of the performance testing tools (i.e. load the system, then spike it)</li>
<li>They aim to check the behavior of the system during the scaling</li>
<li>Very hard to run in a CI/CD pipeline since it requires the scaling to be orchestrated</li>
</ul>
<p>Performance engineers at Wackadoo Corp want to see how the system scales when the loads exceed the upper boundary, so they perform a <strong>scalability test</strong>.
The system can handle up to <strong>10,000 concurrent users</strong> for one server, so this time the load will be increased gradually starting from <strong>5,000</strong> users, and every 2 minutes <strong>1,000</strong> users will join the system.
The expectation is that the system keeps responding, but the response metrics increase with the load (as before) until after <strong>10,000</strong> users, when a new server should join the system. At which point, we should observe the response metrics starting to decrease.
Once scaling up is tested, we can continue with testing the scaling down by decreasing the number of users under the upper limit.</p>
<h3 id="volume-testing" style="position:relative;"><a href="#volume-testing" aria-label="volume testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Volume Testing</h3>
<p>Volume testing assesses the system’s behavior when it is populated with a substantial amount of data.
The purpose of this testing is to evaluate how well the system performs and maintains stability under conditions of high data volume.
By simulating scenarios where the system is loaded with large datasets, volume testing helps identify potential issues related to data handling, storage capacity, and processing efficiency.</p>
<p>This type of testing is particularly useful for uncovering problems such as slow response times, data corruption, or system crashes that may occur when managing extensive amounts of information. Additionally, volume testing ensures that the system can effectively store, retrieve, and process large volumes of data without compromising its overall performance or reliability.</p>
<ul>
<li>Volume tests simulate the system behavior when huge amounts of data are received</li>
<li>They check if databases have any issue with indexing data</li>
<li>For example, in a Black Friday sale scenario, with a massive surge of new users accessing the website simultaneously, they ensure that no users experience issues such as failed transactions, slow response times, or an inability to access the system</li>
<li>Very hard to run in a CI/CD pipeline since the system is intentionally prone to fail</li>
</ul>
<p>Wackadoo Corp wants to increase customers, so they implemented an “invite your friend” feature. The company plans to give a voucher to both members and invited members, which will result in a huge amount of database traffic.
Performance engineers want to run a <strong>volume test</strong>, which mostly includes scenarios like inviting, registering, checking voucher code state, and loading the checkout page.
During the test, the load will increase to <strong>5,000 users</strong> by adding <strong>1,000 users</strong> every <strong>2 minutes</strong> and they should simulate normal user behaviors.
After that heavy write operations can start.
As a result, we should expect the following:</p>
<ul>
<li>Maximum latency is <strong>500ms</strong></li>
<li>95% of the response times are less than <strong>3,000 ms</strong></li>
<li>Longest responses are less then <strong>5,000 ms</strong></li>
<li><strong>0%</strong> error rate</li>
<li>The test server is around <strong>90% of CPU</strong> usage</li>
</ul>
<p>A failure here might suggest to Wackadoo Corp that its database
service is a bottleneck.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>Performance testing plays a crucial role in shaping the overall user experience because an application that performs poorly can easily lose users and damage its reputation.
When performance problems are not detected and resolved early, the cost of fixing them later can increase dramatically, impacting both time and resources.</p>
<p>Moreover, collaboration between multiple departments, including development, operations, and business teams, is essential to ensure that the testing process aligns with real-world requirements and produces meaningful, actionable insights.
Without this coordinated effort and knowledge base, performance testing may fail to deliver valuable outcomes or identify critical issues.</p>
<p>There are many distinct types of performance testing, each designed to assess the system’s behavior from a specific angle and under different conditions.
Load testing can be easily adapted to the CI/CD pipeline; the other performance testing types can be more challenging, but they can still provide a lot of benefits.</p>
<p>In my next blog post, I will talk about my experiences on how we can apply performance testing continuously.</p>]]></description><link>https://tweag.io/blog/2025-08-14-performance-testing/</link><guid isPermaLink="false">https://tweag.io/blog/2025-08-14-performance-testing/</guid><pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Getting started with CodeQL, GitHub's declarative static analyzer for security]]></title><description><![CDATA[<p><a href="https://codeql.github.com/">CodeQL</a> is a declarative static analyzer owned by GitHub, whose purpose
is to discover security vulnerabilities. <em>Declarative</em> means that, to use CodeQL, you write
rules describing the vulnerabilities you want to catch, and you let an engine check your rules
against your code. If there is a match, an alert is raised. <em>Static</em> means that it
checks your source code, as opposed to checking specific runs. <em>Owned by GitHub</em> means that
CodeQL’s engine is not open-source: it’s free to use only on research and open-source code. If you want
to use CodeQL on proprietary code, you need a
<a href="https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security">GitHub Advanced Security</a> license.
CodeQL rules, that model specific programming languages and libraries, however, are open-source.</p>
<p>CodeQL is designed to do two things:</p>
<ol>
<li>Perform all kinds of quality and compliance checks. CodeQL’s query language is expressive enough to describe
a variety of patterns (e.g., “find any loop, enclosed in a function named <code class="language-text">foo</code>, when the loop’s body contains
a call to function <code class="language-text">bar</code>”). As such, it enables complex, semantic queries over codebases, which can uncover a
wide range of issues and patterns.</li>
<li>Track the flow of tainted data. Tainted data is data provided by a potentially malicious user.
If tainted data is sent to critical operations (database requests, custom processes) without being sanitized,
it can have catastrophic consequences, such as data loss, a data breach, arbitrary code execution, etc.
Statements of your source code from where tainted data originates are called <em>sources</em>, while
statements of your source code where tainted data is consumed are called <em>sinks</em>.</li>
</ol>
<p>This tutorial is targeted at software and security engineers that want to try out CodeQL, focusing on the second
use case from above.
I explain how to setup CodeQL, how to write your first taint tracking query, and give a methodology for doing so.
To dig deeper, you can check out the <a href="../2025-08-28-codeql-part-two">second article</a> in this CodeQL series.</p>
<h2 id="writing-the-vulnerable-code" style="position:relative;"><a href="#writing-the-vulnerable-code" aria-label="writing the vulnerable code permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Writing the vulnerable code</h2>
<p>First, I need to write some code to execute my query against. As the attack surface,
I’m choosing calls to the <a href="https://github.com/vsajip/sarge">sarge</a> Python library, for three reasons:</p>
<ul>
<li>It is <a href="https://pypi.org/project/sarge/">available on PyPI</a>, so it is easy to install.</li>
<li>It is niche enough that it is not already modeled in <a href="https://github.com/github/codeql/tree/main/python/ql/lib/semmle/python/frameworks">CodeQL’s Python standard library</a>,
so out of the box queries from CodeQL won’t catch vulnerabilities that use <code class="language-text">sarge</code>. We need to write our own rules.</li>
<li>It performs calls to <a href="https://docs.python.org/3/library/subprocess.html#popen-constructor">subprocess.Popen</a>,
which is a data <em>sink</em>. As a consequence, code calling <code class="language-text">sarge</code> is prone to having command injection vulnerabilities.</li>
</ul>
<p>For my data <em>source</em>, I use <a href="https://flask.palletsprojects.com/en/stable/">flask</a>.
That’s because HTTP requests contain user-provided data, and as such,
they are modeled as data sources in CodeQL’s standard library.
With both <code class="language-text">sarge</code> and <code class="language-text">flask</code> in place, we can write the following vulnerable code:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token keyword">from</span> flask <span class="token keyword">import</span> Flask<span class="token punctuation">,</span> request

<span class="token keyword">import</span> sarge

app <span class="token operator">=</span> Flask<span class="token punctuation">(</span>__name__<span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">"POST"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">user_to_sarge_run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""This function shows a vulnerability: it forwards user input (through a POST request) to sarge.run."""</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"/ handler"</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> request<span class="token punctuation">.</span>method <span class="token operator">!=</span> <span class="token string">"POST"</span><span class="token punctuation">:</span>
        <span class="token keyword">return</span> <span class="token string">"Method not allowed"</span>
    default_value <span class="token operator">=</span> <span class="token string">"default"</span>
    received<span class="token punctuation">:</span> <span class="token builtin">str</span> <span class="token operator">=</span> request<span class="token punctuation">.</span>form<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"key"</span><span class="token punctuation">,</span> <span class="token string">"default"</span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Received: </span><span class="token interpolation"><span class="token punctuation">{</span>received<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
    sarge<span class="token punctuation">.</span>run<span class="token punctuation">(</span>received<span class="token punctuation">)</span>  <span class="token comment"># Unsafe, don't do that!</span>
    <span class="token keyword">return</span> <span class="token string">"Called sarge"</span></code></pre></div>
<p>To run the application locally, execute in one terminal:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">> flask --debug run</code></pre></div>
<p>In another terminal, trigger the vulnerability as follows:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token operator">></span> <span class="token function">curl</span> <span class="token parameter variable">-X</span> POST http://localhost:5000/ <span class="token parameter variable">-d</span> <span class="token string">"key=ls"</span></code></pre></div>
<p>Now observe that in the terminal running the app, the <code class="language-text">ls</code> command (provided by the user! 💣) was executed:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">/ handler
Received: <span class="token function">ls</span>
app.py	__pycache__  README.md	requirements.txt</code></pre></div>
<p>Wow, pretty scary right! What if I had passed the string <code class="language-text">rm -Rf ~/*</code>? Now let’s see how to catch this vulnerability with CodeQL.</p>
<h2 id="running-codeql-on-the-cli" style="position:relative;"><a href="#running-codeql-on-the-cli" aria-label="running codeql on the cli permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Running CodeQL on the CLI</h2>
<p>To run CodeQL on the CLI, I need to download the CodeQL binaries from the <a href="https://github.com/github/codeql-cli-binaries">github/codeql-cli-binaries</a> repository.
At the time of writing, there are CodeQL binaries for the three major platforms. Where I clone this repository doesn’t matter,
as long as the <code class="language-text">codeql</code> binary ends up in <code class="language-text">PATH</code>.
Then, because I am going to write my own queries (as opposed to solely using the queries shipped with CodeQL),
I need to clone CodeQL’s standard library: <a href="https://github.com/github/codeql">github/codeql</a>.
I recommend putting this repository in a folder that is a sibling of the repository being analyzed.
In this manner, the <code class="language-text">codeql</code> binary will find it automatically.</p>
<p>Before I write my own query, let’s run standard CodeQL queries for Python. First, I need to create a database. Instead of analyzing code
at each run, CodeQL’s way of operating is to:</p>
<ol>
<li>Store the code in a database,</li>
<li>Then run one or many queries on the database.</li>
</ol>
<p>While I develop a query, and so iterate on step 2 above, having the two steps distinct saves computing time. As long as the code being analyzed doesn’t change,
there is no need to rebuild the database. Let’s build the codebase as follows:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token operator">></span> codeql database create <span class="token parameter variable">--language</span><span class="token operator">=</span>python codeql-db --source-root<span class="token operator">=</span>.</code></pre></div>
<p>Now that the database is created, let’s call the <code class="language-text">python-security-and-quality</code> (a set of default queries for Python, provided
by CodeQL’s standard library) queries<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup>:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token operator">></span> codeql database analyze codeql-db python-security-and-quality <span class="token parameter variable">--format</span><span class="token operator">=</span>sarif-latest <span class="token parameter variable">--output</span><span class="token operator">=</span>codeql.sarif
<span class="token comment"># Now, transform the SARIF output into CSV, for better human readibility; using https://pypi.org/project/sarif-tools/</span>
<span class="token operator">></span> sarif csv codeql.sarif
<span class="token operator">></span> <span class="token function">cat</span> codeql.csv
Tool,Severity,Code,Description,Location,Line
CodeQL,note,py/unused-local-variable,Variable default_value is not used.,app.py,12</code></pre></div>
<p>Indeed, in the snippet above, it looks like the developer intended to use a variable to store the value <code class="language-text">"default"</code> but forgot to use it in the end.
This is not a security vulnerability, but it exemplifies the kind of programming mistakes that CodeQL’s default rules find.
Note that the vulnerability of passing data from the <code class="language-text">POST</code> request to the <code class="language-text">sarge.run</code> call is not yet caught. That is because
<code class="language-text">sarge</code> is not in <code class="language-text">CodeQL</code>’s <a href="https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/lib/semmle/python/frameworks">list of supported Python libraries</a>.</p>
<h2 id="writing-a-query-to-model-sargerun-modeling-the-source" style="position:relative;"><a href="#writing-a-query-to-model-sargerun-modeling-the-source" aria-label="writing a query to model sargerun modeling the source permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Writing a query to model <code class="language-text">sarge.run</code>: modeling the source</h2>
<p>The <a href="https://docs.red-dove.com/sarge/tutorial.html#common-usage-patterns"><code class="language-text">sarge.run</code></a> function executes a command, like
<a href="https://docs.python.org/3/library/subprocess.html">subprocess</a> does. As such it is a <em>sink</em> for tainted data:
one should make sure that data passed to <code class="language-text">sarge.run</code> is controlled.</p>
<p>CodeQL performs a modular analysis: it doesn’t inspect the source code of your dependencies. As a consequence,
you need to model your dependencies’ behavior for them to be treated correctly by CodeQL’s analysis.
Modeling tainted sources and sinks is done by implementing the
<a href="https://github.com/github/codeql/blob/d17c931939fdba4ffb7c0c4e33d3ebe4c6cdd8b6/shared/dataflow/codeql/dataflow/DataFlow.qll#L360"><code class="language-text">DataFlow::ConfigSig</code></a> interface:</p>
<!-- [DataFlow::ConfigSig](https://github.blog/security/vulnerability-research/codeql-zero-to-hero-part-3-security-research-with-codeql/#new-taint-tracking-api) interface: -->
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token comment">/** An input configuration for data flow. */</span>
signature <span class="token keyword">module</span> <span class="token class-name">ConfigSig</span> <span class="token punctuation">{</span>
  <span class="token comment">/** Holds if `source` is a relevant data flow source. */</span>
  predicate <span class="token function">isSource</span><span class="token punctuation">(</span><span class="token class-name">Node</span> source<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">/** Holds if `sink` is a relevant data flow sink. */</span>
  predicate <span class="token function">isSink</span><span class="token punctuation">(</span><span class="token class-name">Node</span> sink<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>In this snippet, a <code class="language-text">predicate</code> is a function returning a Boolean, while <code class="language-text">Node</code> is a class modeling statements in the source code.
So to implement <code class="language-text">isSource</code> I need to capture the <code class="language-text">Node</code> that we deem relevant sources of tainted data w.r.t. <code class="language-text">sarge.run</code>.
Since any source of tainted data is dangerous if you send its content to <code class="language-text">sarge.run</code>, I implement <code class="language-text">isSource</code> as follows:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java">predicate <span class="token function">isSource</span><span class="token punctuation">(</span><span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> source<span class="token punctuation">)</span> <span class="token punctuation">{</span> source <span class="token keyword">instanceof</span> <span class="token class-name">ActiveThreatModelSource</span> <span class="token punctuation">}</span></code></pre></div>
<p><a href="https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-python/#threat-models">Threat models</a>
control which sources of data are considered dangerous. Usually, only remote sources (data in an HTTP request,
packets from the network) are considered dangerous. That’s because, if local sources (content of local files, content passed by the user in the terminal)
are tainted, it means an attacker has already such a level of control on your software that you are doomed.
That is why, by default, CodeQL’s default threat model is to only consider remote sources.<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>
In <code class="language-text">isSource</code>, by using <code class="language-text">ActiveThreatModelSource</code>, we declare that the sources of interest are the sources of the current active threat model.</p>
<p>To make sure that <code class="language-text">ActiveThreatModelSource</code> works correctly on my codebase, I write the following test query in file <code class="language-text">Scratch.ql</code>:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">import</span> <span class="token namespace">python</span>
<span class="token keyword">import</span> <span class="token namespace">semmle<span class="token punctuation">.</span>python<span class="token punctuation">.</span></span><span class="token class-name">Concepts</span>

from <span class="token class-name">ActiveThreatModelSource</span> src
select src<span class="token punctuation">,</span> <span class="token string">"Tainted data source"</span></code></pre></div>
<p>Because this file depends on the <code class="language-text">python</code> APIs of CodeQL, I need to put a <code class="language-text">qlpack.yml</code> file close to <code class="language-text">Scratch.ql</code>, as follows:</p>
<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> smelc/sarge<span class="token punctuation">-</span>queries
<span class="token key atrule">version</span><span class="token punctuation">:</span> 0.0.1
<span class="token key atrule">extractor</span><span class="token punctuation">:</span> python
<span class="token key atrule">library</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
<span class="token key atrule">dependencies</span><span class="token punctuation">:</span>
  <span class="token key atrule">codeql/python-queries</span><span class="token punctuation">:</span> <span class="token string">"*"</span></code></pre></div>
<p>I can now execute <code class="language-text">Scratch.ql</code> as follows:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token operator">></span> codeql database analyze codeql-db queries/Scratch.ql <span class="token parameter variable">--format</span><span class="token operator">=</span>sarif-latest <span class="token parameter variable">--output</span><span class="token operator">=</span>codeql.sarif
<span class="token operator">></span> sarif csv codeql.sarif
<span class="token operator">></span> <span class="token function">cat</span> codeql.csv
Tool,Severity,Code,Description,Location,Line
CodeQL,note,py/get-remote-flow-source,Tainted data source,app.py,1</code></pre></div>
<p>This seems correct: something is flagged. Let’s make it more visual by running the query in VSCode.
For that I need to install the <a href="https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-codeql">CodeQL extension</a>.
To run queries within <code class="language-text">vscode</code>, I first need to specify the database to use. It is the <code class="language-text">codeql-db</code> folder which
we created with <code class="language-text">codeql database create</code> above:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/afce56a2fb3a127ef31d9060dd579ddf/bca35/codeql-vscode-db-select.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 81.75675675675677%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABq0lEQVQoz32Si27iMBBF/Q+J5+XxMwl0k26h0MKy0qrb//+pVRwhKIW9Ohollq9z4xmj/VMqZej7lGJKKZ7lvc85L898JUREmAuLmK5kVZ2mqZQMYPFKALBspKplKcYQU4gxirBRH3zuN6/73dshlkF8IufJha94FI3danzZbn+ddofj/nBaTxsTUwIfV9b+ttaKCD2UOFdS+lBNIXivofQmpUwalGgEaInwzHdZotHayVqo+ckFk1NmjYq4RkyViCh4aweiDvEHYsO8XABrNF3Xk48ZcUJ8Afhs279tmxDhyslEjijUqvX1YhYfAaytqz3CAFC//IVT2/5pm4+m+Wyacd4MrMHEmCQUDYlFgbhladmhKF0BopndSDKwrFmiOBTHIZs8D0fe7t7W45RLX7rBaSBx9M1vzyxHs/OmlOKcU9Wc0oII32QmQsYLVH+KmcwwDKo6jxIsXEbq/2JmMw8az9e/gA+4b845s0hTO0lEDjEgeqKIqLU9nsjfyzKbh9Uqi2ysfapzUmq3nwH21j4DvAL8BJgA+K75/Xhcqe7aNtbjsUa4qY9i/wNxs2XsvOkeGgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Selecting the CodeQL database in vscode"
        title="Selecting the CodeQL database in vscode"
        src="/static/afce56a2fb3a127ef31d9060dd579ddf/fcda8/codeql-vscode-db-select.png"
        srcset="/static/afce56a2fb3a127ef31d9060dd579ddf/12f09/codeql-vscode-db-select.png 148w,
/static/afce56a2fb3a127ef31d9060dd579ddf/e4a3f/codeql-vscode-db-select.png 295w,
/static/afce56a2fb3a127ef31d9060dd579ddf/fcda8/codeql-vscode-db-select.png 590w,
/static/afce56a2fb3a127ef31d9060dd579ddf/bca35/codeql-vscode-db-select.png 683w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Now I run the query by right-clicking in its opened file:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/f1ea067d9c99de0b825e3b4aba90c4bf/f1d1f/codeql-run-query.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 115.54054054054055%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAIAAACEf/j0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACNElEQVQ4y41TS24VMRB8d0BCCcrY/XHbbn/GM/OCEkgUCRArdpyBKCxYcxZQEAtOimYc3gZIXslqt0YqV3XZswOWMu3TOFugk5OT0+NgjAGAHRHVUmrNUxuTplqqqg7DYB4FACDirnfsJZTmY/DRMzMAWLAA2/7QgP0LO2stIZKEsFz7cQxVWZAcshBLr0SeSPDfZGYi8bq/DlW1JYmBmJxfabhBRGKM3VEHEa0zG2txU47nN75dSt37oi5sgg4BLBEv87Lf71NKxhhrrd/wxzYROYnTuS/JZx9HkcjsEAmMscycc+YNPSderW7K1oITQXbIjEIWrF1P7zmtDSLmnEspOWcAcM7FGJ1znWydE5cXV5t7OWFOoBGTQvRmMJ2cUkLEwyX1upEBRDzFupJL1nks57NOVccSQjTGENGyrDOXUpxz/Tjv/RaYMUgE7EkCMXumKOwdeSZHaLZEpmma57m1JiLWWlWNMa6BDcOwTmaGbZlhW+ZP7XG21rpUN5JSCiGsyg/k/4OZW2vzPKtqz2mLE44iE1FPu9ba9Q94moyIrbVpmpi5D9LrscopJWY+uCUiETmWrKpdrcsy88PzPCawZVn6I+lf1l94e6rH2lbVEMLByLFXRWCyhpJiSdE7guGMwKAdwJqnyMbY2Jbr9+OrN9Prt/PVu3pxUy9u/HI1cHiUDABnL559+CpffuHtd3d3T3f38vmHu/12+unn88uPT9sGRE05Ro2aNGXx/mwwZru13wijvw9h2QXKAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Running the debug query in vscode"
        title="Running the debug query in vscode"
        src="/static/f1ea067d9c99de0b825e3b4aba90c4bf/fcda8/codeql-run-query.png"
        srcset="/static/f1ea067d9c99de0b825e3b4aba90c4bf/12f09/codeql-run-query.png 148w,
/static/f1ea067d9c99de0b825e3b4aba90c4bf/e4a3f/codeql-run-query.png 295w,
/static/f1ea067d9c99de0b825e3b4aba90c4bf/fcda8/codeql-run-query.png 590w,
/static/f1ea067d9c99de0b825e3b4aba90c4bf/f1d1f/codeql-run-query.png 739w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Doing so opens the CodeQL results view:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/80f0b2e61c93365290bb780e5bde8306/1c1a4/codeql-debug-query-result.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 41.891891891891895%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABgUlEQVQoz2WQy47TUBAF8xUzie37fl/bie3ECzQEDSAWIDSC//+YQrFngWBR6m6pVeo+h5QztVRiTKSYeczrxTFWhzIO53astXjvMdZtO7kUYko4Z7FGI7oO0bUcjDHUVBmHkZorOWU+XC2fiqY3Du39hnIOHxOl9kgdaIWlkw6hA9JEjq3m2KhdmGMip4SPEaUUx0ZgWsFLc2JtGua2ZWobBiG5nCu/v5033r72/Lg73l4jPz8Xvr8Ou7DUQq2BXDJlHCnRE41majvm7p22ZVCaoU98nBruq+LXdOLL/Mx9PnJfWl6ujwutI4TA7VJZLz3LkOmjZ0iB/E7NkRg8575seT+dJE+NQp4k4Sh5bvZ3j43kYK3DaM11XliXhfk8sgw9Q4p4Ywh2xyiFd46SM0oKtFZUKVi7lkl0XESHUe9C6xzXZeY2nVmXmXW9MZ17QjCE4PDWIIRgyzvnrX9kraRE/lUfHLQxGGMpMVMf76WANgqp1Va10fuylP8L/5E++ANUAuXv3OQo7gAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Result of running the debug query"
        title="Result of running the debug query"
        src="/static/80f0b2e61c93365290bb780e5bde8306/fcda8/codeql-debug-query-result.png"
        srcset="/static/80f0b2e61c93365290bb780e5bde8306/12f09/codeql-debug-query-result.png 148w,
/static/80f0b2e61c93365290bb780e5bde8306/e4a3f/codeql-debug-query-result.png 295w,
/static/80f0b2e61c93365290bb780e5bde8306/fcda8/codeql-debug-query-result.png 590w,
/static/80f0b2e61c93365290bb780e5bde8306/efc66/codeql-debug-query-result.png 885w,
/static/80f0b2e61c93365290bb780e5bde8306/1c1a4/codeql-debug-query-result.png 1046w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>I see that the import of <code class="language-text">request</code> is flagged as a potential data source. This is correct: in my program,
tainted data can come through usages of this package.</p>
<h2 id="writing-a-query-to-model-sargerun-modeling-the-sink" style="position:relative;"><a href="#writing-a-query-to-model-sargerun-modeling-the-sink" aria-label="writing a query to model sargerun modeling the sink permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Writing a query to model <code class="language-text">sarge.run</code>: modeling the sink</h2>
<p>This is where things gets more interesting. As per the <code class="language-text">ConfigSig</code> interface above, I need to implement <code class="language-text">isSink(Node sink)</code>,
so that it captures calls to <code class="language-text">sarge.run</code>. Because <code class="language-text">CodeQL</code> is a declarative<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup> object-oriented language, this means <code class="language-text">isSink</code> must return <code class="language-text">true</code>
for subclasses of <code class="language-text">Node</code> that represent calls to <code class="language-text">sarge.run</code>. Let me describe a methodology to discover how to do that.
First, modify the <code class="language-text">Scratch.ql</code> query to find out all instances of <code class="language-text">Node</code> in my application:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">import</span> <span class="token namespace">python</span>
<span class="token keyword">import</span> <span class="token namespace">semmle<span class="token punctuation">.</span>python<span class="token punctuation">.</span>dataflow<span class="token punctuation">.</span>new<span class="token punctuation">.</span></span><span class="token class-name">DataFlow</span>

from <span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> src
select src<span class="token punctuation">,</span> <span class="token string">"DataFlow::Node"</span></code></pre></div>
<p>Executing this query in VSCode yields the following results:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/b198b04917f4e7a10df2dd41b30412a7/636c2/codeql-all-nodes.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 147.97297297297297%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAIAAACjcKk8AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACXklEQVQ4y52UXW/TMBSG8xtgm+jKlqRJ7Ng+dr6bxEmcrCtb23UIJDQJcbMPbpEQ//8COS0DIaS5vDoXR5GO/OQ9r225rhsEvud5juMAAKWEc2CMAQdKKQBjMIpzjBClxA8CB8GMJo4fWphw18O263sBOZ06k6k9mdpvTu1dMxm/nL51TiZnKATCItfDXhD6iMx8ZN0sku1FtBnE7WW8VnSt6PYC3l+KXf9cq458eJd8vEq2F3y7ELfLdNly635z9uOL+P4Zvt3hp/Xx4+roaX38dXPyuDr6qx6uX99fvX64enW/mT7c5Z/WYAWYi7jgUU4gsWf43MXnLjpz0Nj8qxzkBNyLWpekluDQq06pTsoK4wAh/4UKvJAyXi9JUloAIKWs67ooCoRQ8KJ8D1NBq2WY1BaltCiKPM+TJDEb9hFhJK0xpBbnvO97pVRd1wbDKPBmmGfQrHE0YpdlOZ/P0zQ1xWYRnQ9YZBZjbDecZZn5MCl6jS2EGIah73spJcbYDDuF9kYbxhjLRx1gGCYklVjk+1VJKcuyxBijFxX4IcSsWuphznnXdW3blmVp7HYKzSqMq9/YcRybYoeUpA3mqcbuRlVVZeg2IqCxo0JjHxxPJmi5wNFcx/NgtwkjWYMg1icPw6CUklKaD0OzwnG1j2dZlsYJ04bRQulsHxzPcWEk7xAk+3jusA3iuT8Z2jV+3nOWZaZ7HjUaluh/bppmF0/TYYRYtcBi3LNSqm1b45Bot5m8DlP5H9j7W4X+jKfZM7THBnmNxfzw11OD+yRr9a1yHGf2S7apzm3bdtzZT4siKmfKSfQrAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Result of querying for all nodes"
        title="Result of querying for all nodes"
        src="/static/b198b04917f4e7a10df2dd41b30412a7/fcda8/codeql-all-nodes.png"
        srcset="/static/b198b04917f4e7a10df2dd41b30412a7/12f09/codeql-all-nodes.png 148w,
/static/b198b04917f4e7a10df2dd41b30412a7/e4a3f/codeql-all-nodes.png 295w,
/static/b198b04917f4e7a10df2dd41b30412a7/fcda8/codeql-all-nodes.png 590w,
/static/b198b04917f4e7a10df2dd41b30412a7/efc66/codeql-all-nodes.png 885w,
/static/b198b04917f4e7a10df2dd41b30412a7/636c2/codeql-all-nodes.png 911w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Wow, that’s a lot of results! In a real codebase with multiple files, this would be unmanageable.
Fortunately code completion works in CodeQL, so I can filter the results using the <code class="language-text">where</code> clause, discovering
the methods to call by looking at completions on the <code class="language-text">.</code> symbol. Since the call to <code class="language-text">sarge.run</code> I am looking for is at line 17,
I can refine the query as follows:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java">from <span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> src<span class="token punctuation">,</span> <span class="token class-name">Location</span> loc
where src<span class="token punctuation">.</span><span class="token function">getLocation</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> loc
  and loc<span class="token punctuation">.</span><span class="token function">getFile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getBaseName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"app.py"</span>
  and loc<span class="token punctuation">.</span><span class="token function">getStartLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">17</span>
select src<span class="token punctuation">,</span> <span class="token string">"DataFlow::Node"</span></code></pre></div>
<p>With these constraints, the query returns only a handful of results:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/7ca7472c492ffe99831c878981e949e7/1d553/codeql-app-17-nodes.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 48.64864864864865%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABmElEQVQoz4WSSY7cMBRDfYwuD5oly3bJdrmmOKlscv87vUBypxu9SLIgCAgixU/9Kk4TPnjatuVUn2jqmtPpxOntjbquP5DPtNaEED4gpSy6pmnKnabtqKZp4n67E8aBc5yI5wk/DPh+QApRRJm1Nu/QKKUKfAj0fY9SEiE1etyorAukccaOA9Z7VBYYgzbmMCsQCKkIfWRZFmKMjOOIcw7vfUmb2TtLZawjm+rgMH1fErRtQ9d1iJLwSClEV4T7vhds25WUEpfLxrquzOuF9fmTylpLnM4M0RGCI6aZoe/prTmSiQNaSpy1PB4PXq8Xuar8eNYbrfBDYnj8ygk9cRz5cUvsW+L7fWE7Ry5jZI6BZegLpxiQSpUxc1LzpRKJ0gZlA5XJRUtZulnmlWWeGYNlCg6tVHndKFU+RCpdqvhTxyfkO3dHh9oatufO9Xrjfrtxeew89500eZzVxGDxVn9J9DdU2jiyaVoX1jSWgr95x9Q7rNdoqwvnNTk+R/zb8NgzieyOfVNCYERHbFpMHi+P0n2a/M/wN6dWG71km8DZAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Results of querying some nodes"
        title="Results of querying some nodes"
        src="/static/7ca7472c492ffe99831c878981e949e7/fcda8/codeql-app-17-nodes.png"
        srcset="/static/7ca7472c492ffe99831c878981e949e7/12f09/codeql-app-17-nodes.png 148w,
/static/7ca7472c492ffe99831c878981e949e7/e4a3f/codeql-app-17-nodes.png 295w,
/static/7ca7472c492ffe99831c878981e949e7/fcda8/codeql-app-17-nodes.png 590w,
/static/7ca7472c492ffe99831c878981e949e7/efc66/codeql-app-17-nodes.png 885w,
/static/7ca7472c492ffe99831c878981e949e7/1d553/codeql-app-17-nodes.png 1159w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Still, there are 4 hits on line 17. Let’s see how I can disambiguate those. For this, CodeQL provides the <code class="language-text">getAQlClass</code> predicate
that returns the most specific type a variable has (as explained in
<a href="https://github.blog/security/vulnerability-research/codeql-zero-to-hero-part-3-security-research-with-codeql/">CodeQL zero to hero part 3</a>):</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java">from <span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> src<span class="token punctuation">,</span> <span class="token class-name">Location</span> loc
where src<span class="token punctuation">.</span><span class="token function">getLocation</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> loc
  and loc<span class="token punctuation">.</span><span class="token function">getFile</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getBaseName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"app.py"</span>
  and loc<span class="token punctuation">.</span><span class="token function">getStartLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">17</span>
select src<span class="token punctuation">,</span> src<span class="token punctuation">.</span><span class="token function">getAQlClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"DataFlow::Node"</span></code></pre></div>
<p>See how the <code class="language-text">select</code> clause now includes <code class="language-text">src.getAQlClass()</code> as second element. This makes the <em>CodeQL Query Results</em> show it
in the central column:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/4d1ab328e673a34cdf591969867ac0f0/681f1/codeql-getaqlclass.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 94.5945945945946%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACp0lEQVQ4y4WS25LjNgxE/RvjKVuWCd4pUhKvIiVbE683mcnm/38nRWnj2uQlp/CG6kKjgQPGmDFGCKGUtm37tnE8Ht/+zXHj1Tq+vb2/vx/atr1erxhjhFDf90IIpZQQQkrJGJO/wDYuTdMSgXQ8N80BACil5/P5dDq17fX8f5xOp3NzAcqv1+vBOZdL6bpOd9po3XVKb6iuq6WUNsb0vTZmr07rvh+C91KpA6VE2akvz748h/zQLksppFJpmpZlmed5XVdjDOdc1IaMKd3u92maMMYHSjC3RS5/yfIl0pP1CVALANOU5nkehsFaq5SqYs4RQsH7+/2ec0YIDgQD01bYhY+FjxMzHm/0fe82vPd93xtjtNYYY2OM934YBoRQnSzGSU7f5fSU8TeZHpgQDJBSney9jzHybSZseO+XZUkpAcAuzmr+kvl3ER8ibmKMY4ylFO99CMFuDMOAMXbO/Somm+3Cx8KMZyZiQjHG+7b1ClpTSvddAMAY45z7aZszKmxR85cqnyJ9k+WTcAmAcqkQQgAA/wMAhBDWdc0518mcUT4kOX0X6SHCXaZvhEu87bbv/B+xtTbnHEKoYkarbW7nantI3BZCOQaonxCCc67ruv1598z2K4zjWG1LwYWb1fJDzX/K6amWH0QoAJTS9PHxcbvdHo/HPM/GGKUUAMQY13Utpew7Mz5EEdZa/i7cwvuICbHWxhhDCN77ruuklEopQsgwDDFGa+1PsRjzJrsJv3I3M+0AkHM+5xxj3NN+3Xkcx5TSnkW1LcNdlc8aeM38j/onADnn3XaMUSmFMd6Tf9l+pR25W4S/CX/j48THjAkdxzHGuHtmjL3SftmuYkAIuAY5ghxAWSR6EP2lbaWUxphuA2PcNM3lcmmahnOutZZSXi6XvwFDO8k3LK9EiQAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Results of getAQlClass"
        title="Results of getAQlClass"
        src="/static/4d1ab328e673a34cdf591969867ac0f0/fcda8/codeql-getaqlclass.png"
        srcset="/static/4d1ab328e673a34cdf591969867ac0f0/12f09/codeql-getaqlclass.png 148w,
/static/4d1ab328e673a34cdf591969867ac0f0/e4a3f/codeql-getaqlclass.png 295w,
/static/4d1ab328e673a34cdf591969867ac0f0/fcda8/codeql-getaqlclass.png 590w,
/static/4d1ab328e673a34cdf591969867ac0f0/efc66/codeql-getaqlclass.png 885w,
/static/4d1ab328e673a34cdf591969867ac0f0/681f1/codeql-getaqlclass.png 899w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>There are many more results, and that is because entries that were indistinguishable before are now disambiguated by the class.
If in doubt, one can consult <a href="https://codeql.github.com/codeql-standard-libraries/python/">the list of class</a> of CodeQL’s standard Python library
to understand what each class is about. In our case, I had read the
official documentation on <a href="https://codeql.github.com/docs/codeql-language-guides/using-api-graphs-in-python/#calls-and-class-instantiations">using CodeQL for Python</a>,
and I recognize the <code class="language-text">CallNode</code> class from this list.</p>
<p>As the documentation explains, there is actually an API to retrieve <code class="language-text">CallNode</code> instances corresponding to functions imported from a distant module, using
the <code class="language-text">moduleImport</code> function. Let’s use it to restrict our <code class="language-text">Node</code>s to be instances of <code class="language-text">CallNode</code> (using a cast) <em>and</em>
this call being a call to <code class="language-text">sarge.run</code>:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">import</span> <span class="token namespace">python</span>
<span class="token keyword">import</span> <span class="token namespace">semmle<span class="token punctuation">.</span>python<span class="token punctuation">.</span>dataflow<span class="token punctuation">.</span>new<span class="token punctuation">.</span></span><span class="token class-name">DataFlow</span>
<span class="token keyword">import</span> <span class="token namespace">semmle<span class="token punctuation">.</span>python<span class="token punctuation">.</span></span><span class="token class-name">ApiGraphs</span>

from <span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> src
where src<span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token constant">API</span><span class="token operator">::</span><span class="token class-name">CallNode</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token constant">API</span><span class="token operator">::</span><span class="token function">moduleImport</span><span class="token punctuation">(</span><span class="token string">"sarge"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getMember</span><span class="token punctuation">(</span><span class="token string">"run"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getACall</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
select src<span class="token punctuation">,</span> <span class="token string">"CallNode calling sarge.run"</span></code></pre></div>
<p>Executing this query yields the only result we want:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/5dddbd59da30841d3de401f4988d113f/8de58/codeql-scratch-final-version.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 41.21621621621622%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABYUlEQVQoz42R22obMRRF5ytaHM9opNHlSBqPL3XHdkJpHNNSh17+/29WkZIY8lDowwIJpKWztxqJkZwS3nvECxICIYS6N8bc0FoTgienSIyCswPDoFGqY7lsadsXGqMN4oScMuIDzrkqtNbS9/2N8qh1gbvW8HGhaJWl04Flp+m6lq7rKs0wDMQQSWVKifRa15eUUjdZWee84vHLzJ/va56fVvy8rPl9DjzdZz7tZ+Z5Zr//TGOKMCdS8kgM5GlCvMMaTadUpQglJrL0XE4956PmfDR8O9xxWCuCJGKMNVkzWEsQ4bAbOWwLK9YpMMXASjyj+LqexsyyVTXuGx8WmsWyr5FLqtfIL11Nqw27acdmHNkUWfAYrXHG1GlLJeWc1j26f6PUot513QzWkcTx67Ll+XHk+nXL6XTkMO/IKRBjIIkn5/yu13/RmMFW4fXBc73X/HiQenlwFvtK+bj/Ff4FAO/l3lf4RygAAAAASUVORK5CYII='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Result of final debug query"
        title="Result of final debug query"
        src="/static/5dddbd59da30841d3de401f4988d113f/fcda8/codeql-scratch-final-version.png"
        srcset="/static/5dddbd59da30841d3de401f4988d113f/12f09/codeql-scratch-final-version.png 148w,
/static/5dddbd59da30841d3de401f4988d113f/e4a3f/codeql-scratch-final-version.png 295w,
/static/5dddbd59da30841d3de401f4988d113f/fcda8/codeql-scratch-final-version.png 590w,
/static/5dddbd59da30841d3de401f4988d113f/efc66/codeql-scratch-final-version.png 885w,
/static/5dddbd59da30841d3de401f4988d113f/c83ae/codeql-scratch-final-version.png 1180w,
/static/5dddbd59da30841d3de401f4988d113f/8de58/codeql-scratch-final-version.png 1219w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<p>Putting this all together, I can finalize the implementation of <code class="language-text">ConfigSig</code> as shown below.
The <code class="language-text">getArg(0)</code> suffix models that the tainted data flows into <code class="language-text">sarge.run</code>’s first argument:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">module</span> <span class="token class-name">SargeConfig</span> <span class="token keyword">implements</span> <span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">ConfigSig</span> <span class="token punctuation">{</span>
  predicate <span class="token function">isSource</span><span class="token punctuation">(</span><span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    source <span class="token keyword">instanceof</span> <span class="token class-name">ActiveThreatModelSource</span>
  <span class="token punctuation">}</span>

  predicate <span class="token function">isSink</span><span class="token punctuation">(</span><span class="token class-name">DataFlow</span><span class="token operator">::</span><span class="token class-name">Node</span> sink<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    sink <span class="token operator">=</span> <span class="token constant">API</span><span class="token operator">::</span><span class="token function">moduleImport</span><span class="token punctuation">(</span><span class="token string">"sarge"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getMember</span><span class="token punctuation">(</span><span class="token string">"run"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getACall</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getArg</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Following the <a href="https://codeql.github.com/docs/writing-codeql-queries/creating-path-queries/#constructing-a-path-query">official template</a>
for queries tracking tainted data, I write the query as follows:</p>
<div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">module</span> <span class="token class-name">SargeFlow</span> <span class="token operator">=</span> <span class="token class-name">TaintTracking</span><span class="token operator">::</span><span class="token class-name">Global</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SargeConfig</span><span class="token punctuation">></span></span><span class="token punctuation">;</span>

from <span class="token class-name">SargeFlow</span><span class="token operator">::</span><span class="token class-name">PathNode</span> source<span class="token punctuation">,</span> <span class="token class-name">SargeFlow</span><span class="token operator">::</span><span class="token class-name">PathNode</span> sink
where <span class="token class-name">SargeFlow</span><span class="token operator">::</span><span class="token function">flowPath</span><span class="token punctuation">(</span>source<span class="token punctuation">,</span> sink<span class="token punctuation">)</span>
select sink<span class="token punctuation">.</span><span class="token function">getNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> source<span class="token punctuation">,</span> sink<span class="token punctuation">,</span> <span class="token string">"Tainted data passed to sarge"</span></code></pre></div>
<p>Executing this query in VSCode returns the paths (list of steps) along which the vulnerability takes place:</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <a
    class="gatsby-resp-image-link"
    href="/static/f7c49ed458bf1c80c111ea6fa2312ecf/78958/codeql-path-final-result.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 37.83783783783784%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABfklEQVQoz1WS64rbMBBG/RiFxLYs62ZZkrOOnYuTzVKWFpamKfT93+UUK+1Cfxy+mZH4ZpCmcNbSNA2tbJFSItun1nWNqOusmUbSKkUIIWOtpSzLzHa7ZbPZZApvPDFE+s7T9Y6YElprhBC50cozlllXVpO1vt5TWucBnuYVhVEmd1PWIGTNttzmw6qq/mOtua7jfDpxmCfG3UCKgRh6QuiJf+PC9x2+01jnsD5glaIRNeU/o0/TmrZteRlnDtd3hvmVtF/ohiPKRrRLmH6ksM4y7QLzLjANnuQM0Wl6ozJrvmqwmrISTLHm9/3A/ceF+8eRx7eex/ULj3fLz4+ZQkrFr6+e75fEfjcwxp7oO6zWWGNwxqCVyu9V1YLeNVxfZ5bbwuVtYTklTqnMulwPFEopbuc9h33iOE2cj0dehoBWEqsVRklEU39+iDYWP92w+zf07oryI6Jpaf2ITieKdV2CjyTvMq1qaFqREfJJzuXTcB3AOZc1r9lK03zGfwA7QervwcjlEgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Result of the final query"
        title="Result of the final query"
        src="/static/f7c49ed458bf1c80c111ea6fa2312ecf/fcda8/codeql-path-final-result.png"
        srcset="/static/f7c49ed458bf1c80c111ea6fa2312ecf/12f09/codeql-path-final-result.png 148w,
/static/f7c49ed458bf1c80c111ea6fa2312ecf/e4a3f/codeql-path-final-result.png 295w,
/static/f7c49ed458bf1c80c111ea6fa2312ecf/fcda8/codeql-path-final-result.png 590w,
/static/f7c49ed458bf1c80c111ea6fa2312ecf/efc66/codeql-path-final-result.png 885w,
/static/f7c49ed458bf1c80c111ea6fa2312ecf/c83ae/codeql-path-final-result.png 1180w,
/static/f7c49ed458bf1c80c111ea6fa2312ecf/78958/codeql-path-final-result.png 1320w"
        sizes="(max-width: 590px) 100vw, 590px"
        style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
        loading="lazy"
        decoding="async"
      />
  </a>
    </span></p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>I have demonstrated how to use CodeQL to model a Python library, covering the setup and steps a developer must do to write his/her first CodeQL query.
I gave a methodology to be able to write instances of CodeQL interfaces, even when one is lacking intimate knowledge of CodeQL APIs. I believe this
is important, as the CodeQL ecosystem is small and the number of resources is limited: users of CodeQL often have to find out what to write on their own,
with limited support from both the tooling and from generative AI tools (probably because the number of resources on CodeQL is small, so the results of generative
AI systems are poor too).</p>
<p>To dive deeper, I recommend to:</p>
<ul>
<li>read the official <a href="https://codeql.github.com/docs/codeql-language-guides/codeql-for-python/">CodeQL for Python</a> resource,</li>
<li>join the <a href="ghsecuritylab.slack.com">GitHub Security Lab Slack</a> to get support from CodeQL users and developers, and</li>
<li>read the <a href="../2025-07-09-codeql-deep-dive">second article</a> in this CodeQL series.</li>
</ul>
<p>And remember that this tutorial’s material is available at <a href="https://github.com/tweag/sarge-codeql-minimal">tweag/sarge-codeql-minimal</a> if you want to experiment
with this tutorial yourself!</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-3">In this <a href="../2026-01-15-codeql-wrapper">third post</a> of our series, we provide a tool dedicated to handling CodeQL’s SARIF output in monorepos.<a href="#fnref-3" class="footnote-backref">↩</a></li>
<li id="fn-1">The default threat model can be overridden by command line flags and by configuration files.<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">CodeQL belongs to the <a href="https://en.wikipedia.org/wiki/Datalog">Datalog</a> family of languages.<a href="#fnref-2" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-08-07-codeql-intro/</link><guid isPermaLink="false">https://tweag.io/blog/2025-08-07-codeql-intro/</guid><pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Integrating Nix and Buck2]]></title><description><![CDATA[<p><a href="https://buck2.build/">Buck2</a> is a new open source build system developed by Meta (Facebook) which we already looked at before in some depth, see <a href="https://www.tweag.io/blog/2023-07-06-buck2/">A Tour Around Buck2, Meta’s New Build System</a>. Since then, Buck2 has gained significant improvements in user experience and language support, making it an increasingly attractive option in the build systems space.</p>
<p>At Tweag, we adhere to high standards for reproducible builds, which Buck2 doesn’t fully uphold in its vanilla configuration. In this post, we will introduce our ruleset that provides integration with Nix. I’ll demonstrate how it can be used, and you will gain insights into how to leverage Nix to achieve more reliable and reproducible builds with Buck2.</p>
<h2 id="reproducibility-anyone" style="position:relative;"><a href="#reproducibility-anyone" aria-label="reproducibility anyone permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Reproducibility, anyone?</h2>
<p>In short, Buck2 is a fast, polyglot build tool very similar to <a href="https://bazel.build/">Bazel</a>. Notably, it also provides fine-grained distributed caching and even speaks (in its open source variant) the same remote caching and execution protocols used by Bazel. This means you’re able to utilize the same Bazel <a href="https://github.com/bazelbuild/remote-apis?tab=readme-ov-file#servers">services</a> available for caching and remote execution.</p>
<p>However, in contrast to Bazel, Buck2 uses a remote first approach and does not restrict build actions using a sandbox on the local machine. As a result build actions can be <em>non-hermetic</em>, meaning their outcome might depend on what files or programs happen to be present on the local machine. This lack of <em>hermeticity</em> can lead to non-reproducible builds, which is a critical concern for the effective caching of build artifacts.</p>
<p>Non-hermeticity issues can be elusive, often surfacing unexpectedly for new developers which effects on-boarding new team members, or open source contributors. If left undetected, they can even cause problems down the line in production, which is why we think reproducible builds are important!</p>
<h2 id="achieving-reproducibility-with-nix" style="position:relative;"><a href="#achieving-reproducibility-with-nix" aria-label="achieving reproducibility with nix permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Achieving Reproducibility with Nix</h2>
<p>If we want reproducible builds, we must not rely on anything installed on the local machine. We need to precisely control every compiler and build tool which is used in our project. Although defining each and every one of these inside the Buck2 build itself is possible, it also would be a lot of work. The solution to this problem can be Nix.</p>
<p>Nix is a package manager and build system for Linux and Unix-like operating systems. With <a href="https://search.nixos.org/packages"><code class="language-text">nixpkgs</code></a>, there is a very large and comprehensive collection of software packaged using Nix, which is extensible and can be adapted to one’s needs. Most importantly, Nix already strictly enforces hermeticity for its package builds and the <code class="language-text">nixpkgs</code> collection goes to great lengths to achieve <a href="https://reproducible.nixos.org/">reproducible builds</a>.</p>
<p>So, using Nix to provide compilers and build tools for Buck2 is a way to benefit from that preexisting work and introduce hermetic toolchains into a Buck2 build.</p>
<p>Let’s first quickly look into the Nix setup and proceed with how we can integrate it into Buck2 later.</p>
<h3 id="nix-with-flakes" style="position:relative;"><a href="#nix-with-flakes" aria-label="nix with flakes permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Nix with flakes</h3>
<p>After <a href="https://nixos.org/download/">installing Nix</a>, the <code class="language-text">nix</code> command is available, and we can start declaring dependencies on packages from <code class="language-text">nixpkgs</code> in a <code class="language-text">nix</code> file. The Nix tool uses the <a href="https://nix.dev/tutorials/nix-language">Nix language</a>, a domain-specific, purely functional and lazily evaluated programming language to define packages and declare dependencies. The language has some wrinkles, but don’t worry; we’ll only use basic expressions without delving into the more advanced concepts.</p>
<p>For example, here is a simple <code class="language-text">flake.nix</code> which provides the Rust compiler as a package output:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  inputs <span class="token operator">=</span> <span class="token punctuation">{</span>
    nixpkgs<span class="token punctuation">.</span>url <span class="token operator">=</span> <span class="token string">"github:nixos/nixpkgs?ref=nixos-unstable"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  outputs <span class="token operator">=</span> <span class="token punctuation">{</span> self<span class="token punctuation">,</span> nixpkgs <span class="token punctuation">}</span><span class="token punctuation">:</span>
    <span class="token punctuation">{</span>
      packages <span class="token operator">=</span> <span class="token punctuation">{</span>
        aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>rustc <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>rustc<span class="token punctuation">;</span>
        x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>rustc <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>rustc<span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p><em>Note: While flakes have been widely used for a long time, the feature still needs to be enabled explicitly by setting <code class="language-text">extra-experimental-features = nix-command flakes</code> in the configuration. See the <a href="https://nixos.wiki/wiki/flakes">wiki</a> for more information.</em></p>
<p>In essence, a Nix flake is a Nix expression following a specific schema. It defines its inputs (usually other flakes) and outputs (e.g. packages) which depend on the inputs. In this example the <code class="language-text">rustc</code> package from <code class="language-text">nixpkgs</code> is re-used for the output of this flake, but more complex expressions could be used just as well.</p>
<p>Inspecting this flake shows the following output:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix flake show --all-systems
path:/source/project?lastModified=1745857313&amp;narHash=sha256-e1sxfj1DZbRjhHWF7xfiI3wc1BpyqWQ3nLvXBKDya%2Bg%3D
└───packages
    ├───aarch64-darwin
    │   └───rustc: package &#39;rustc-wrapper-1.86.0&#39;
    └───x86_64-linux
        └───rustc: package &#39;rustc-wrapper-1.86.0&#39;</code></pre></div>
<p>In order to build the <code class="language-text">rustc</code> package output, we can call Nix in the directory of the <code class="language-text">flake.nix</code> file like this: <code class="language-text">nix build '.#rustc'</code>. This will either fetch pre-built artifacts of this package from a binary cache if available, or directly build the package if not. The result is the same in both cases: the <code class="language-text">rustc</code> package output will be available in the local nix store, and from there it can be used just like other software on the system.</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix build --print-out-paths &#39;.#rustc&#39;
/nix/store/ssid482a107q5vw18l9millwnpp4rgxb-rustc-wrapper-1.86.0-man
/nix/store/szc39h0qqfs4fvvln0c59pz99q90zzdn-rustc-wrapper-1.86.0</code></pre></div>
<p>The output displayed above illustrates that a Nix build of a single package can produce multiple outputs. In this case the <code class="language-text">rustc</code> package was split into a default output and an additional, separate output for the man pages.</p>
<p>The default output contains the main binaries such as the Rust compiler:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ /nix/store/szc39h0qqfs4fvvln0c59pz99q90zzdn-rustc-wrapper-1.86.0/bin/rustc --version
rustc 1.86.0 (05f9846f8 2025-03-31) (built from a source tarball)</code></pre></div>
<p>It is also important to note that the output of a Nix package depends on the specific <code class="language-text">nixpkgs</code> revision stored in the <code class="language-text">flake.lock</code> file, rather than any changes in the local environment. This ensures that each developer checking out the project at any point in time will receive the exact same (reproducible) output no matter what.</p>
<h3 id="using-buck2" style="position:relative;"><a href="#using-buck2" aria-label="using buck2 permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Using Buck2</h3>
<p>As part of our work for <a href="https://mercury.com/">Mercury</a>, a company providing financial services, we developed rules for Buck2 which can be used to integrate packages provided by a nix flake as part of a project’s build. Recently, we have been able to publish these rules, called <a href="https://github.com/tweag/buck2.nix"><code class="language-text">buck2.nix</code></a>, as open source under the Apache 2 license.</p>
<p>To use these rules, you need to make them available in your project first. Add the following configuration to your <code class="language-text">.buckconfig</code>:</p>
<div class="gatsby-highlight" data-language="ini"><pre class="language-ini"><code class="language-ini"><span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">cells</span><span class="token punctuation">]</span></span>
  <span class="token key attr-name">nix</span> <span class="token punctuation">=</span> <span class="token value attr-value">none</span>

<span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">external_cells</span><span class="token punctuation">]</span></span>
  <span class="token key attr-name">nix</span> <span class="token punctuation">=</span> <span class="token value attr-value">git</span>

<span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">external_cell_nix</span><span class="token punctuation">]</span></span>
  <span class="token key attr-name">git_origin</span> <span class="token punctuation">=</span> <span class="token value attr-value">https://github.com/tweag/buck2.nix.git</span>
  <span class="token key attr-name">commit_hash</span> <span class="token punctuation">=</span> <span class="token value attr-value">accae8c8924b3b51788d0fbd6ac90049cdf4f45a # change to use a different version</span></code></pre></div>
<p>This configures a <em>cell</em> called <code class="language-text">nix</code> to be fetched from the specified repository on GitHub. Once set up, you can refer to that <em>cell</em> in your <code class="language-text">BUCK</code> files and load rules from it.</p>
<p><em>Note: for clarity, I am going to indicate the file name in the top most comment of a code block when it is not obvious from the context already</em></p>
<p>To utilize a Nix package from Buck2, we need to introduce a new target that runs <code class="language-text">nix build</code> inside of a build action producing a symbolic link to the nix store path as the build output. Here is how to do that using <a href="https://github.com/tweag/buck2.nix"><strong>buck2.nix</strong></a>:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># BUCK</span>

load<span class="token punctuation">(</span><span class="token string">"@nix//flake.bzl"</span><span class="token punctuation">,</span> <span class="token string">"flake"</span><span class="token punctuation">)</span>

flake<span class="token punctuation">.</span>package<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"rustc"</span><span class="token punctuation">,</span>
    binary <span class="token operator">=</span> <span class="token string">"rustc"</span><span class="token punctuation">,</span>
    path <span class="token operator">=</span> <span class="token string">"nix"</span><span class="token punctuation">,</span> <span class="token comment"># path to a nix flake</span>
    package <span class="token operator">=</span> <span class="token string">"rustc"</span><span class="token punctuation">,</span> <span class="token comment"># which package to build, default is the value of the `name` attribute</span>
    output <span class="token operator">=</span> <span class="token string">"out"</span><span class="token punctuation">,</span> <span class="token comment"># which output to build, this is the default</span>
<span class="token punctuation">)</span></code></pre></div>
<p><em>Note: this assumes the <code class="language-text">flake.nix</code> and accompanying <code class="language-text">flake.lock</code> file is found alongside the <code class="language-text">BUCK</code> file in the <code class="language-text">nix</code> subdirectory</em></p>
<p>With this build file in place, a new target called <code class="language-text">rustc</code> is made available which builds the output called <code class="language-text">out</code> of the <code class="language-text">rustc</code> package of the given flake. This target can be used as a dependency of other rules in order to generate an output artifact:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># BUCK</span>

genrule<span class="token punctuation">(</span>
   name <span class="token operator">=</span> <span class="token string">"rust-info"</span><span class="token punctuation">,</span>
   out <span class="token operator">=</span> <span class="token string">"rust-info.txt"</span><span class="token punctuation">,</span>
   cmd <span class="token operator">=</span> <span class="token string">"$(exe :rustc) --version > ${OUT}"</span>
<span class="token punctuation">)</span></code></pre></div>
<p><em>Note: Buck2 supports expanding references in string parameters using <a href="https://buck2.build/docs/rule_authors/string_parameter_macros/">macros</a>, such as the <code class="language-text">$(exe )</code> part in the <code class="language-text">cmd</code> parameter above which expands to the path of the executable output of the <code class="language-text">:rustc</code> target</em></p>
<p>Using Buck2 (from <code class="language-text">nixpkgs</code> of course!) to build the <code class="language-text">rust-info</code> target yields:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix run nixpkgs#buck2 -- build --show-simple-output :rust-info
Build ID: f3fec86b-b79f-4d8e-80c7-acea297d4a64
Loading targets.   Remaining     0/10                                                                                    24 dirs read, 97 targets declared
Analyzing targets. Remaining     0/20                                                                                    5 actions, 5 artifacts declared
Executing actions. Remaining     0/5                                                                                     9.6s exec time total
Command: build.    Finished 2 local
Time elapsed: 10.5s
BUILD SUCCEEDED
buck-out/v2/gen/root/904931f735703749/__rust-info__/out/rust-info.txt

$ cat buck-out/v2/gen/root/904931f735703749/__rust-info__/out/rust-info.txt
rustc 1.86.0 (05f9846f8 2025-03-31) (built from a source tarball)</code></pre></div>
<p>For this one-off command we just ran <code class="language-text">buck2</code> from the <code class="language-text">nixpkgs</code> flake on the current system. This is nice for illustration, but it is also not reproducible, and you’ll probably end up with a different Buck2 version when you try this on your machine.</p>
<p>In order to provide the same Buck2 version consistently, let’s add another Nix flake to our project:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># flake.nix</span>

<span class="token punctuation">{</span>
  inputs <span class="token operator">=</span> <span class="token punctuation">{</span>
    nixpkgs<span class="token punctuation">.</span>url <span class="token operator">=</span> <span class="token string">"github:nixos/nixpkgs?ref=nixos-unstable"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  outputs <span class="token operator">=</span> <span class="token punctuation">{</span> self<span class="token punctuation">,</span> nixpkgs <span class="token punctuation">}</span><span class="token punctuation">:</span>
    <span class="token punctuation">{</span>
      devShells<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>default <span class="token operator">=</span>
        nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>mkShellNoCC <span class="token punctuation">{</span>
          name <span class="token operator">=</span> <span class="token string">"buck2-shell"</span><span class="token punctuation">;</span>
          packages <span class="token operator">=</span> <span class="token punctuation">[</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>buck2 <span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>

      devShells<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>default <span class="token operator">=</span>
        nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>mkShellNoCC <span class="token punctuation">{</span>
          name <span class="token operator">=</span> <span class="token string">"buck2-shell"</span><span class="token punctuation">;</span>
          packages <span class="token operator">=</span> <span class="token punctuation">[</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>buck2 <span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

  nixConfig<span class="token punctuation">.</span>bash<span class="token operator">-</span>prompt <span class="token operator">=</span> <span class="token string">"(nix) \\$ "</span><span class="token punctuation">;</span> <span class="token comment"># visual clue if inside the shell</span>
<span class="token punctuation">}</span></code></pre></div>
<p>This flake defines a default development environment, or dev shell for short. It uses the <code class="language-text">mkShellNoCC</code> function from <code class="language-text">nixpkgs</code> which creates an environment where the programs from the given <code class="language-text">packages</code> are available in <code class="language-text">PATH</code>.</p>
<p>After entering the shell by running <code class="language-text">nix develop</code> in the directory of the <code class="language-text">flake.nix</code> file, the <code class="language-text">buck2</code> command has the exact same version for everyone working on the project as long as the committed <code class="language-text">flake.lock</code> file is not changed. For convenience, consider using <a href="https://direnv.net/">direnv</a> which automates entering the dev shell as soon as changing into the project directory.</p>
<h3 id="hello-rust" style="position:relative;"><a href="#hello-rust" aria-label="hello rust permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Hello Rust</h3>
<p>With all of that in place, let’s have a look at how to build something more interesting, like a Rust project.</p>
<p>Similar to the <code class="language-text">genrule</code> above, it would be possible to define custom rules utilizing the <code class="language-text">:rustc</code> target to compile real-world Rust projects. However, Buck2 already ships with rules for various languages in its <em>prelude</em>, including rules to build Rust <a href="https://buck2.build/docs/prelude/globals/#rust_library">libraries</a> and <a href="https://buck2.build/docs/prelude/globals/#rust_binary">binaries</a>.</p>
<p>In a default project setup with Rust these rules would simply use whatever Rust compiler is installed in the system, which may cause build failures due to version mismatches.</p>
<p>To avoid this non-hermeticity, we’re going to instruct the Buck2 rules to use our pinned Rust version from <code class="language-text">nixpkgs</code>.</p>
<p>Let’s start by preparing such a default setup for the infamous “hello world” example in Rust:</p>
<div class="gatsby-highlight" data-language="rust"><pre class="language-rust"><code class="language-rust"># src<span class="token operator">/</span>hello<span class="token punctuation">.</span>rs

<span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token macro property">println!</span><span class="token punctuation">(</span><span class="token string">"Hello, world!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># src/BUCK</span>

rust_binary<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"hello"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"hello.rs"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<h4 id="toolchains" style="position:relative;"><a href="#toolchains" aria-label="toolchains permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Toolchains</h4>
<p>What’s left to do to make these actually work is to provide a Rust <em>toolchain</em>. In this context, a toolchain is a configuration that specifies a set of tools for building a project, such as the compiler, the linker, and various command-line tools. In this way, toolchains are decoupled from the actual rule definitions and can be easily changed to suit one’s needs.</p>
<p>In Buck2, toolchains are expected to be available in the <code class="language-text">toolchains</code> <em>cell</em> under a specific name. Conventionally, the <code class="language-text">toolchains</code> cell is located in the <code class="language-text">toolchains</code> directory of a project. For example, all the Rust rules depend on the target <code class="language-text">toolchains//:rust</code> which is defined in <code class="language-text">toolchains/BUCK</code> and must provide Rust specific toolchain information.</p>
<p>Luckily, we do not need to <a href="https://buck2.build/docs/rule_authors/writing_toolchains/">define a <em>toolchain rule</em> ourselves</a> but can re-use the <code class="language-text">nix_rust_toolchain</code> rule from <a href="https://github.com/tweag/buck2.nix/blob/main/toolchains/rust.bzl"><code class="language-text">buck2.nix</code></a>:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token comment"># toolchains/BUCK</span>

load<span class="token punctuation">(</span><span class="token string">"@nix//toolchains:rust.bzl"</span><span class="token punctuation">,</span> <span class="token string">"nix_rust_toolchain"</span><span class="token punctuation">)</span>

flake<span class="token punctuation">.</span>package<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"clippy"</span><span class="token punctuation">,</span>
    binary <span class="token operator">=</span> <span class="token string">"clippy-driver"</span><span class="token punctuation">,</span>
    path <span class="token operator">=</span> <span class="token string">"nix"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

flake<span class="token punctuation">.</span>package<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"rustc"</span><span class="token punctuation">,</span>
    binaries <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"rustdoc"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    binary <span class="token operator">=</span> <span class="token string">"rustc"</span><span class="token punctuation">,</span>
    path <span class="token operator">=</span> <span class="token string">"nix"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

nix_rust_toolchain<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"rust"</span><span class="token punctuation">,</span>
    clippy <span class="token operator">=</span> <span class="token string">":clippy"</span><span class="token punctuation">,</span>
    default_edition <span class="token operator">=</span> <span class="token string">"2021"</span><span class="token punctuation">,</span>
    rustc <span class="token operator">=</span> <span class="token string">":rustc"</span><span class="token punctuation">,</span>
    rustdoc <span class="token operator">=</span> <span class="token string">":rustc[rustdoc]"</span><span class="token punctuation">,</span>
    visibility <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"PUBLIC"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>The <code class="language-text">rustc</code> target is defined almost identically as before, but the <code class="language-text">nix_rust_toolchain</code> rule also expects the <code class="language-text">rustdoc</code> attribute to be present. In this case, the <code class="language-text">rustdoc</code> binary is available from the <code class="language-text">rustc</code> Nix package as well and can be referenced using the sub-target syntax <code class="language-text">:rustc[rustdoc]</code> which refers to the corresponding item of the <code class="language-text">binaries</code> attribute given to the <code class="language-text">flake.package</code> rule.</p>
<p>Additionally, we need to pass in the <code class="language-text">clippy-driver</code> binary, which is available from the <code class="language-text">clippy</code> package in the <code class="language-text">nixpkgs</code> collection. Thus, the <code class="language-text">flake.nix</code> file needs to be changed by adding the <code class="language-text">clippy</code> package outputs:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># toolchains/nix/flake.nix</span>

<span class="token punctuation">{</span>
  inputs <span class="token operator">=</span> <span class="token punctuation">{</span>
    nixpkgs<span class="token punctuation">.</span>url <span class="token operator">=</span> <span class="token string">"github:nixos/nixpkgs?ref=nixos-unstable"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  outputs <span class="token operator">=</span>
    <span class="token punctuation">{</span>
      self<span class="token punctuation">,</span>
      nixpkgs<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">:</span>
    <span class="token punctuation">{</span>
      packages <span class="token operator">=</span> <span class="token punctuation">{</span>
        aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>rustc <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>rustc<span class="token punctuation">;</span>
        aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>clippy <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>clippy<span class="token punctuation">;</span>
        x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>rustc <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>rustc<span class="token punctuation">;</span>
        x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>clippy <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>clippy<span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>At this point we are able to successfully build and run the target <code class="language-text">src:hello</code>:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">(nix) $ buck2 run src:hello
Build ID: 530a4620-bfb2-454d-bae1-e937ae9e764f
Analyzing targets. Remaining     0/53                                                                                    75 actions, 101 artifacts declared
Executing actions. Remaining     0/11                                                                                    1.1s exec time total
Command: run.      Finished 3 local
Time elapsed: 0.7s
BUILD SUCCEEDED
Hello, world!</code></pre></div>
<p>Building a real-world Rust project would be a bit more involved. <a href="https://www.tweag.io/blog/2023-07-27-building-rust-workspace-with-bazel/">Here</a> is an interesting article how one can do that using Bazel.</p>
<p>Note that <a href="https://github.com/tweag/buck2.nix"><code class="language-text">buck2.nix</code></a> currently also provides toolchain rules for C/C++ and Python. Have a look at the <a href="https://github.com/tweag/buck2.nix/tree/main/examples">example</a> project provided by <a href="https://github.com/tweag/buck2.nix"><code class="language-text">buck2.nix</code></a>, which you can directly use as a template to start your own project:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix flake new --template github:tweag/buck2.nix my-project</code></pre></div>
<p>A big thank you to Mercury for their support and for encouraging us to share these rules as open source! If you’re looking for a different toolchain or have other suggestions, feel free to open a <a href="https://github.com/tweag/buck2.nix/issues/new/choose">new issue</a>. Pull requests are very welcome, too!</p>
<p>If you’re interested in exploring a more tightly integrated solution, you might want to take a look at the <a href="https://github.com/thoughtpolice/buck2-nix">buck2-nix</a> project, which also provides Nix integration. Since it defines an <em>alternative</em> prelude that completely replaces Buck2’s built-in rules, we could not use it in our project but drew good inspiration from it.</p>
<h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2>
<p>With the setup shown, we saw that all that is needed really is Nix (pun intended<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>):</p>
<ul>
<li>we provide the <code class="language-text">buck2</code> binary with Nix as part of a development environment</li>
<li>we leverage Nix inside Buck2 to provide build tools such as compilers, their required utilities and third-party libraries in a reproducible way</li>
</ul>
<p>Consequently, onboarding new team members no longer means following seemingly endless and quickly outdated installation instructions. Installing nix is easy; entering the dev shell is fast, and you’re up and running in no time!</p>
<p>And using Buck2 gives us fast, incremental builds by only building the minimal set of dependencies needed for a specific target.</p>
<p>Next time, I will delve into how we seamlessly integrated the Haskell toolchain libraries from Nix and how we made it fast as well.</p>
<!-- Footnotes -->
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">The name Nix is derived from the Dutch word <em>niks</em>, meaning nothing; build actions don’t see anything that hasn’t been explicitly declared as an input<a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2025-07-31-buck2-nix/</link><guid isPermaLink="false">https://tweag.io/blog/2025-07-31-buck2-nix/</guid><pubDate>Thu, 31 Jul 2025 00:00:00 GMT</pubDate></item></channel></rss>