<?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 - Nix 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>Wed, 15 Apr 2026 21:18:29 GMT</lastBuildDate><item><title><![CDATA[Adding algebraic data types to Nickel]]></title><description><![CDATA[<p>Our <a href="https://github.com/tweag/nickel/">Nickel</a> language is a configuration language. It’s also a
functional programming language. Functional programming isn’t a well-defined
term: it can encompass anything from being vaguely able to pass functions as
arguments and to call them (in that respect, C and JavaScript are functional) to
being a statically typed, pure and immutable language based on the
lambda-calculus, like Haskell.</p>
<p>However, if you ask a random developer, I can guarantee that one aspect will be
mentioned every time: algebraic data types (ADTs) and pattern matching. They are
the bread and butter of typed functional languages. ADTs are relatively easy to
implement (for language maintainers) and easy to use. They’re part of the 20% of
the complexity that makes for 80% of the joy of functional programming.</p>
<p>But Nickel didn’t have ADTs until recently. In this post, I’ll tell the story of
Nickel and ADTs, starting from why they were initially lacking, the exploration
of different possible solutions and the final design leading to the eventual
retro-fitting of proper ADTs in Nickel. This post is intended for Nickel users,
for people interested in configuration management, but also for anyone interested
in programming language design and functional programming. It doesn’t require
prior Nickel knowledge.</p>
<h2 id="a-quick-primer-on-nickel" style="position:relative;"><a href="#a-quick-primer-on-nickel" aria-label="a quick primer on nickel 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 quick primer on Nickel</h2>
<p>Nickel is a gradually typed, functional, configuration language. From this
point, we’ll talk about Nickel <em>before</em> the introduction of ADTs in the 1.5
release, unless stated otherwise. The core language features:</p>
<ul>
<li>let-bindings: <code class="language-text">let extension = ".ncl" in "file.%{extension}"</code></li>
<li>first-class functions: <code class="language-text">let add = fun x y => x + y in add 1 2</code></li>
<li>records (JSON objects): <code class="language-text">{name = "Alice", age = 42}</code></li>
<li>static typing: <code class="language-text">let mult : Number -> Number -> Number = fun x y => x * y</code>. By
default, expressions are dynamically typed. A static type annotation makes a
definition or an inline expression typechecked statically.</li>
<li>contracts look and act almost like types but are evaluated at runtime:
<code class="language-text">{ port | Port = 80 }</code>. They are used to validate configurations against
potentially complex schemas.</li>
</ul>
<p>The lifecycle of a Nickel configuration is to be 1) written, 2) evaluated and 3) serialized, typically to JSON, YAML or TOML. An important guideline that we set
first was that <em>every native data structure (record, array, enum, etc.) should
be trivially and straightforwardly serializable to JSON</em>. In consequence, Nickel
started with the JSON data model: records (objects), arrays, booleans, numbers
and strings.</p>
<p>There’s one last primitive value: enums. As in C or in JavaScript, an enum in
Nickel is just a tag. An enum value is an identifier with a leading <code class="language-text">'</code>, such as
in <code class="language-text">{protocol = 'http, server = "tweag.io"}</code>. An enum is serialized as a string:
the previous expression is exported to JSON as <code class="language-text">{"protocol": "http", "server": "tweag.io"}</code>.</p>
<p>So why not just using strings? Because enums can better represent a finite set
of alternatives. For example, the enum type <code class="language-text">[| 'http, 'ftp, 'sftp |]</code> is the
type of values that are either <code class="language-text">'http</code>, <code class="language-text">'ftp</code> or <code class="language-text">'sftp</code>. Writing <code class="language-text">protocol : [| 'http, 'ftp, 'sftp |]</code> will <em>statically</em> (at typechecking time) ensure that
<code class="language-text">protocol</code> doesn’t take forbidden values such as <code class="language-text">'https</code>. Even without static
typing, using an enum conveys to the reader that a field isn’t a free-form
string.</p>
<p>Nickel has a <code class="language-text">match</code> which corresponds to C or JavaScript’s <code class="language-text">switch</code>:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">h</span><span class="token operator">t</span><span class="token operator">t</span><span class="token operator">p</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">h</span><span class="token operator">t</span><span class="token operator">t</span><span class="token operator">p</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">t</span><span class="token operator">p</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">f</span><span class="token operator">t</span><span class="token operator">p</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 class-name">Bool</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">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"> </span><span class="token operator">'</span><span class="token operator">h</span><span class="token operator">t</span><span class="token operator">t</span><span class="token operator">p</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator">></span><span class="token operator"> </span><span class="token constant">true</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 constant">false</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>As you might notice, there are no ADTs in sight yet.</p>
<h2 id="adts-in-a-configuration-language" style="position:relative;"><a href="#adts-in-a-configuration-language" aria-label="adts in a configuration language 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>ADTs in a configuration language</h2>
<p>While Nickel is a functional language, it’s first and foremost a configuration
language, which comes with specific design constraints.</p>
<p>Because we’re telling the story of ADTs before they landed in Nickel, we can’t really
use a proper Nickel syntax yet to provide examples. In what follows, we’ll use a
Rust-like syntax to illustrate the examples: <code class="language-text">enum Foo&lt;T> { Bar(i32), Baz(bool, T) }</code> is an ADT parametrized by a generic type <code class="language-text">T</code> with two constructors <code class="language-text">Bar</code>
and <code class="language-text">Baz</code>, where the first one takes an integer as an argument and the other
takes a pair of a boolean and a <code class="language-text">T</code>. Concrete values are written as <code class="language-text">Bar(42)</code> or
<code class="language-text">Baz(true, "hello")</code>.</p>
<h3 id="an-unexpected-obstacle-serialization" style="position:relative;"><a href="#an-unexpected-obstacle-serialization" aria-label="an unexpected obstacle serialization 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>An unexpected obstacle: serialization</h3>
<p>As said earlier, we want values to be straightforwardly serializable to the JSON
data model.</p>
<p>Now, take a simple ADT such as <code class="language-text">enum Foo&lt;T,U> = { SomePair(T,U), Nothing }</code>. You
can find reasonable serializations for <code class="language-text">SomePair(1,2)</code>, such as <code class="language-text">{"tag": "SomePair", "a": 1, "b": 2}</code>. But why not <code class="language-text">{"flag": "SomePair", "0": 1, "1": 2}</code> or <code class="language-text">{"mark": "SomePair", "data": [1, 2]}</code>? While those representations are isomorphic, it’s hard to know
the right choice for the right use-case beforehand, as it depends on the
consumer of the resulting JSON. We really don’t want to make an arbitrary choice
on behalf of the user.</p>
<p>Additionally, while ADTs are natural for a classical typed functional language,
they might not entirely fit the configuration space. A datatype like <code class="language-text">enum Literal { String(String), Number(Number) }</code> that can store either a string or a
number is usually represented directly as an <em>untagged</em> union in a
configuration, that <code class="language-text">{"literal": 5}</code> or <code class="language-text">{"literal": "hello"}</code>, instead of the
less natural <em>tagged</em> union (another name for ADTs) <code class="language-text">{"literal": {"tag": "Number", "value": 5}}</code>.</p>
<p>This led us to look at (untagged) union types instead. Untagged unions have the
advantage of not making any choice about the serialization: they aren’t a new
data structure, as are ADTs, but rather new types (and contracts) to classify
values that are already representable.</p>
<h2 id="the-road-of-union-types" style="position:relative;"><a href="#the-road-of-union-types" aria-label="the road of union 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>The road of union types</h2>
<p>A union type is a type that accepts different alternatives. We’ll use the
fictitious <code class="language-text">\/</code> type combinator to write a union in Nickel (<code class="language-text">|</code> is commonly used
elswhere but it’s already taken in Nickel). Our previous example of a <code class="language-text">literal</code>
that can be either a string or a number would be <code class="language-text">{literal: Number \/ String}</code>.
Those types are broadly useful independently of ADTs. For example, JSON Schema
features unions through the core combinator <code class="language-text">any_of</code>.</p>
<p>Our hope was to kill two birds with one stone by adding unions both as a way to
better represent existing configuration schemas, but also as a way to emulate
ADTs. Using unions lets users represent ADTs directly as plain records using
their preferred serialization scheme. Together with <a href="https://en.wikipedia.org/wiki/Flow-sensitive_typing">flow-sensitive
typing</a>, we can get as expressive as ADTs while letting
the user decide on the encoding. Here is an example in a hypothetical Nickel
enhanced with unions and flow-sensitive typing:</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 operator">s</span><span class="token operator">u</span><span class="token operator">m</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">tag</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">o</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator">P</span><span class="token operator">a</span><span class="token operator">i</span><span class="token operator">r</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 class-name">Number</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">b</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 property">tag</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">N</span><span class="token operator">o</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>
<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 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 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"> </span><span class="token operator">{</span><span class="token property">tag</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">o</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator">P</span><span class="token operator">a</span><span class="token operator">i</span><span class="token operator">r</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">b</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">a</span><span class="token operator"> </span><span class="token operator">+</span><span class="token operator"> </span><span class="token operator">b</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">tag</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">N</span><span class="token operator">o</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><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 operator"> </span><span class="token operator"> </span><span class="token operator">}</span></code></pre></div>
<p>Using unions and flow-sensitive typing as ADTs is the approach taken by
TypeScript, where the previous example would be:</p>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">Foo</span> <span class="token operator">=</span> <span class="token punctuation">{</span> tag<span class="token operator">:</span> <span class="token string">"SomePair"</span><span class="token punctuation">;</span> a<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> b<span class="token operator">:</span> <span class="token builtin">number</span> <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">"Nothing"</span> <span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">sum</span><span class="token punctuation">(</span>value<span class="token operator">:</span> Foo<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span>
  <span class="token keyword">switch</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span>tag<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">case</span> <span class="token string">"SomePair"</span><span class="token operator">:</span>
      <span class="token keyword">return</span> value<span class="token punctuation">.</span>a <span class="token operator">+</span> value<span class="token punctuation">.</span>b
    <span class="token keyword">case</span> <span class="token string">"Nothing"</span><span class="token operator">:</span>
      <span class="token keyword">return</span> <span class="token number">0</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>In Nickel, any type must have a contract counter-part. Alas <a href="https://www.tweag.io/blog/2022-04-28-union-intersection-contracts/">union and
intersection contracts are hard</a> (in fact, union
types alone are also not a trivial feat to implement!). In the linked blog post,
we hint at possible pragmatic solutions for union contracts that <a href="https://github.com/tweag/nickel/pull/1995">we finally got
to implement for Nickel 1.8</a>. While sufficient for
practical union contracts, this is far from the general union types that could
subsume ADTs. This puts a serious stop to the idea of using union types to
represent ADTs.</p>
<h2 id="what-are-adts-really-good-for" style="position:relative;"><a href="#what-are-adts-really-good-for" aria-label="what are adts really good 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 are ADTs really good for?</h2>
<p>As we have been writing more and more Nickel, we realized that we have been missing ADTs a
lot for library functions - typically the types <code class="language-text">enum Option&lt;T> { Some(T), None }</code> and <code class="language-text">Result&lt;T,E> = { Ok(T), Error(E) }</code> - where we don’t care about
serialization. Those ADTs are “internal” markers that wouldn’t leak out to the
final exported configuration.</p>
<p>Here are a few motivating use-cases.</p>
<h3 id="stdstringfind" style="position:relative;"><a href="#stdstringfind" aria-label="stdstringfind 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><code class="language-text">std.string.find</code></h3>
<p><code class="language-text">std.string.find</code> is a function that searches for a substring in a string. Its
current type is:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token class-name">String</span>
<span class="token operator">-></span><span class="token operator"> </span><span class="token class-name">String</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">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">e</span><span class="token operator">d</span><span class="token operator"> </span><span class="token operator">:</span><span class="token operator"> </span><span class="token class-name">String</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">d</span><span class="token operator">e</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 class-name">Number</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">g</span><span class="token operator">r</span><span class="token operator">o</span><span class="token operator">u</span><span class="token operator">p</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 class-name">String</span><span class="token operator"> </span><span class="token operator">}</span></code></pre></div>
<p>If the substring isn’t found, <code class="language-text">{matched = "", index = -1, groups []}</code> is
returned, which is error-prone if the consumer doesn’t defend against such
values. We would like to return a proper ADT instead, such as <code class="language-text">Found {matched : String, index : Number, groups : Array String}</code> or <code class="language-text">NotFound</code>, which
would make for a better and a safer interface<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>.</p>
<h3 id="contract-definition" style="position:relative;"><a href="#contract-definition" aria-label="contract definition 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>Contract definition</h3>
<p>Contracts are a powerful validation system in Nickel. The ability to plug in
your own custom contracts is crucial.</p>
<p>However, the general interface to define custom contracts can seem bizarre.
Custom contracts need to set error reporting data on a special <code class="language-text">label</code> value and
use the exception-throwing-like <code class="language-text">std.contract.blame</code> function. Here is a
simplified definition of <code class="language-text">std.number.Nat</code> which checks that a value is natural
number:</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">l</span><span class="token operator">a</span><span class="token operator">b</span><span class="token operator">e</span><span class="token operator">l</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 operator"> </span><span class="token operator"> </span><span class="token keyword">if</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">t</span><span class="token operator">y</span><span class="token operator">p</span><span class="token operator">e</span><span class="token operator">o</span><span class="token operator">f</span><span class="token operator"> </span><span class="token property">value</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 class-name">Number</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 keyword">if</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">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 number">0</span><span class="token operator"> </span><span class="token operator">&amp;&amp;</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 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 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">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 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"> </span><span class="token operator"> </span><span class="token keyword">let</span><span class="token operator"> </span><span class="token property">label</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">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">a</span><span class="token operator">b</span><span class="token operator">e</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 operator">m</span><span class="token operator">e</span><span class="token operator">s</span><span class="token operator">s</span><span class="token operator">a</span><span class="token operator">g</span><span class="token operator">e</span><span class="token operator"> </span><span class="token string">"not a natural"</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 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">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">b</span><span class="token operator">l</span><span class="token operator">a</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">l</span><span class="token operator">a</span><span class="token operator">b</span><span class="token operator">e</span><span class="token operator">l</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 keyword">let</span><span class="token operator"> </span><span class="token property">label</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">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">l</span><span class="token operator">a</span><span class="token operator">b</span><span class="token operator">e</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 operator">m</span><span class="token operator">e</span><span class="token operator">s</span><span class="token operator">s</span><span class="token operator">a</span><span class="token operator">g</span><span class="token operator">e</span><span class="token operator"> </span><span class="token string">"not a number"</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 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">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">b</span><span class="token operator">l</span><span class="token operator">a</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator"> </span><span class="token operator">l</span><span class="token operator">a</span><span class="token operator">b</span><span class="token operator">e</span><span class="token operator">l</span></code></pre></div>
<p>There are good (and bad) reasons for this situation, but if we had ADTs, we
could cover most cases with an alternative interface where custom contracts
return a <a href="https://doc.rust-lang.org/std/result/"><code class="language-text">Result&lt;T,E></code></a>, which is simpler and more natural:</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">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 operator"> </span><span class="token operator"> </span><span class="token keyword">if</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">t</span><span class="token operator">y</span><span class="token operator">p</span><span class="token operator">e</span><span class="token operator">o</span><span class="token operator">f</span><span class="token operator"> </span><span class="token property">value</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 class-name">Number</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 keyword">if</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">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 number">0</span><span class="token operator"> </span><span class="token operator">&amp;&amp;</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 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 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">k</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">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"> </span><span class="token operator"> </span><span class="token operator">E</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator">(</span><span class="token string">"not a natural"</span><span class="token operator">)</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">E</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator">(</span><span class="token string">"not a number"</span><span class="token operator">)</span></code></pre></div>
<p>Of course, we could just encode this using a record, but it’s just not as nice.</p>
<h3 id="let-it-go-let-it-go" style="position:relative;"><a href="#let-it-go-let-it-go" aria-label="let it go let it go 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 it go, let it go!</h3>
<p>The list of other examples of using ADTs to make libraries nicer is endless.</p>
<p><strong>Thus, for the first time, we decided to introduce a native data
structure that isn’t serializable</strong>.</p>
<p>Note that this doesn’t break any existing code and is forward-compatible with
making ADTs serializable in the future, should we change our mind and settle on
one particular encoding. Besides, <a href="https://github.com/tweag/nickel/issues/1170#issuecomment-1461637685">another feature</a> is
independently explored to make serialization more customizable through metadata,
which would let users use custom (de)serializer for ADTs easily.</p>
<p>Ok, let’s add the good old-fashioned ADTs to Nickel!</p>
<h2 id="the-design" style="position:relative;"><a href="#the-design" aria-label="the design 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 design</h2>
<h3 id="structural-vs-nominal" style="position:relative;"><a href="#structural-vs-nominal" aria-label="structural vs nominal 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>Structural vs nominal</h3>
<p>In fact, we won’t exactly add the old-fashioned version. ADTs are traditionally
implemented in their <em>nominal</em> form.</p>
<p>A nominal type system (such as C, Rust, Haskell, Java, etc.) decides if two
types are equal based on their name and definition. For example, values of <code class="language-text">enum Alias1 { Value(String) }</code> and <code class="language-text">enum Alias2 { Value(String) }</code> are entirely
interchangeable in practice, but Rust still doesn’t accept <code class="language-text">Alias1::Value(s)</code>
where a <code class="language-text">Alias2</code> is expected, because those types have distinct definitions.
Similarly, you can’t swap a class for another in Java just because they have
exactly the same fields and methods.</p>
<p>A structural type system, on the other hand, only cares about the shape of data.
TypeScript has a structural type system. For example, the types <code class="language-text">interface Ball { diameter: number; }</code> and <code class="language-text">interface Sphere { diameter: number; }</code> are entirely
interchangeable, and <code class="language-text">{diameter: 42}</code> is both a <code class="language-text">Ball</code> and a <code class="language-text">Sphere</code>. Some
languages, like OCaml<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup> or Go<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup>,
mix both.</p>
<p>Nickel’s current type system is structural because it’s better equipped to
handle arbitrary JSON-like data. Because ADTs aren’t serializable, this
consideration doesn’t weight as much for our motivating use-cases, meaning ADTs
could still be either nominal or structural.</p>
<p>However, nominal types aren’t really usable without some way of exporting and
importing type definitions, which Nickel currently lacks. Structural ADTs look
like the better choice for Nickel. We can build, typecheck, and match on ADTs
locally without having to know or to care about any type declaration. Structural
ADTs are a natural extension of Nickel (structural) enums, syntactically,
semantically, and on the type level, as we will see.</p>
<p>While less common, structural ADTs do exist in the wild and they are pretty
cool. OCaml has both nominal ADTs and structural ADTs, the latter being known as
<em><a href="https://ocaml.org/manual/5.2/polyvariant.html">polymorphic variants</a></em>. They are an especially powerful way to represent a non
trivial hierarchy of data types with overlapping, such as <a href="https://github.com/ocsigen/tyxml">abstract syntax
trees</a> or sets of error values.</p>
<h3 id="syntax" style="position:relative;"><a href="#syntax" aria-label="syntax 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>Syntax</h3>
<p>C-style enums are just a special case of ADTs, namely ADTs where constructors
don’t have any argument. The dual conclusion is that ADTs are enums with
arguments. We thus write the ADT <code class="language-text">Some("hello")</code> as an enum with an argument in
Nickel: <code class="language-text">'Some "hello"</code>.</p>
<p>We apply the same treatment to types. <code class="language-text">[| 'Some, 'None |]</code> was a valid enum
type, and now <code class="language-text">[| 'Some String, 'None |]</code> is also a valid type (which would
correspond to Rust’s <code class="language-text">Option&lt;String></code>).</p>
<p>There is a subtlety here: what should be the type inferred for <code class="language-text">'Some</code> now? In
a structural type system, <code class="language-text">'Some</code> is just a free-standing symbol. The typechecker
can’t know if it’s a constant that will stay as it is - and thus has the type
<code class="language-text">[| 'Some |]</code> - or a constructor that will be eventually applied, of type <code class="language-text">a -> [| 'Some a |]</code>. This difficulty just doesn’t exist in a nominal type system like
Rust: there, <code class="language-text">Option::Some</code> refers to a unique, fixed ADT constructor that is
known to require precisely one argument.</p>
<p>To make it work, <code class="language-text">'Ok 42</code> isn’t actually a normal function application in
Nickel: it’s an ADT constructor application, and it’s parsed differently. We
just repurpose the function application syntax<sup id="fnref-4"><a href="#fn-4" class="footnote-ref">4</a></sup>
in this special case. <code class="language-text">'Ok</code> isn’t a function, and <code class="language-text">let x = 'Ok in x 42</code> is an
error (applying something that isn’t a function).</p>
<p>You can still recover Rust-style constructors that can be applied by defining a
function (<em>eta-expanding</em>, in the functional jargon): <code class="language-text">let ok = fun x => 'Ok x</code>.</p>
<p>We restrict ADTs to a single argument. You can use a record to emulate multiple
arguments: <code class="language-text">'Point {x = 1, y = 2}</code>.</p>
<p>ADTs also come with pattern matching. The basic switch that was <code class="language-text">match</code> is now a
powerful pattern matching construct, with support for ADTs but also arrays,
records, constant, wildcards, or-patterns and guards (<code class="language-text">if</code> side-conditions).</p>
<h3 id="typechecking" style="position:relative;"><a href="#typechecking" aria-label="typechecking 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>Typechecking</h3>
<p>Typechecking structural ADTs is a bit different from nominal ADTs. Take the
simple example (the enclosing <code class="language-text">: _</code> annotation is required to make the example
statically typed in Nickel)</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 keyword">let</span><span class="token operator"> </span><span class="token property">data</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">k</span><span class="token operator"> </span><span class="token number">42</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 keyword">let</span><span class="token operator"> </span><span class="token property">process</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><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"> </span><span class="token operator">'</span><span class="token operator">O</span><span class="token operator">k</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 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 operator"> </span><span class="token operator">'</span><span class="token operator">E</span><span class="token operator">r</span><span class="token operator">r</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 number">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 keyword">in</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">o</span><span class="token operator">c</span><span class="token operator">e</span><span class="token operator">s</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">d</span><span class="token operator">a</span><span class="token operator">t</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></code></pre></div>
<p><code class="language-text">process</code> is inferred to have type <code class="language-text">[| 'Ok Number, 'Error |] -> Number</code>. What
type should we infer for <code class="language-text">data = 'Ok 42</code>? The most obvious one is <code class="language-text">[| 'Ok Number |]</code>. But then <code class="language-text">[| 'Ok Number |]</code> and <code class="language-text">[| 'Ok Number, 'Error |]</code> don’t match and
<code class="language-text">process data</code> doesn’t typecheck! This is silly, because this example should
be perfectly valid.</p>
<p>One possible solution is to introduce subtyping, which is able to express this
kind of inclusion relation: here that <code class="language-text">[| 'Ok Number |]</code> is included in <code class="language-text">[| 'Ok Number, 'Error |]</code>. However, subtyping has some defects and is whole can of
worms when mixed with polymorphism (which Nickel has).</p>
<p>Nickel rather relies on another approach called <em>row polymorphism</em>, which is the
ability to abstract over not just a type, as in classical polymorphism, but a
whole piece of an enum type. Row polymorphism is well studied in the literature,
and is for example implemented in PureScript. Nickel already features row
polymorphism for basic enum types and for records types.</p>
<p>Here is how it works:</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 operator">p</span><span class="token operator">r</span><span class="token operator">o</span><span class="token operator">c</span><span class="token operator">e</span><span class="token operator">s</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 keyword">forall</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">O</span><span class="token operator">k</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">E</span><span class="token operator">r</span><span class="token operator">r</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">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 class-name">Number</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><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">O</span><span class="token operator">k</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 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 operator">E</span><span class="token operator">r</span><span class="token operator">r</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 number">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><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">p</span><span class="token operator">r</span><span class="token operator">o</span><span class="token operator">c</span><span class="token operator">e</span><span class="token operator">s</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">'</span><span class="token operator">O</span><span class="token operator">t</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">r</span></code></pre></div>
<p>Because there’s a catch-all case <code class="language-text">_ => -1</code>, the type of <code class="language-text">process</code> is
polymorphic, expressing that it can handle any other variant beside <code class="language-text">'Ok Number</code>
and <code class="language-text">'Error</code> (this isn’t entirely true: <code class="language-text">Ok String</code> is forbidden for example, because it can’t be distinguished from <code class="language-text">Ok Number</code>). Here, <code class="language-text">a</code> can be substituted for a subsequence of an enum type,
such as <code class="language-text">'Foo Bool, 'Bar {x : Number}</code>.</p>
<p>Equipped with row polymorphism, we can infer the type <code class="language-text">forall a. [| 'Ok Number; a |]</code><sup id="fnref-5"><a href="#fn-5" class="footnote-ref">5</a></sup> for <code class="language-text">'Ok 42</code>. When typechecking <code class="language-text">process data</code> in the
original example, <code class="language-text">a</code> will be instantiated to the single row <code class="language-text">'Error</code> and the
example typechecks. You can learn more about structural ADTs and row
polymorphism in the <a href="https://nickel-lang.org/user-manual/typing#enum-row-polymorphism">corresponding section of the Nickel user
manual</a>.</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>While ADTs are part of the basic package of functional languages, Nickel didn’t
have them until relatively recently because of peculiarities of the design of a
configuration language. After exploring the route of union types, which came to
a dead-end, we settled on a structural version of ADTs that turns out to be a
natural extension of the language and didn’t require too much new syntax or
concepts.</p>
<p>ADTs already prove useful to write cleaner and more concise code, and to improve
the interface of libraries, even in a gradually typed configuration language.
Some concrete usages can be found in <a href="https://github.com/tweag/nickel/pull/2000">try_fold_left</a> and <a href="https://github.com/tweag/nickel/pull/1970">validators</a> already.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">Unfortunately, we can’t change the type of
<code class="language-text">std.string.find</code> without breaking existing programs (at least not until a
Nickel 2.0), but this use-case still applies to external libraries or future
stdlib functions<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">In OCaml, Objects, polymorphic variants and modules
are structural while records and ADTs are nominal.<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-3">In Go, interfaces are structural while structs are
nominal.<a href="#fnref-3" class="footnote-backref">↩</a></li>
<li id="fn-4">Repurposing application is theoretically backward
incompatible because <code class="language-text">'Ok 42</code> was already valid Nickel syntax before 1.5,
but it was meaningless (an enum applied to a constant) and would always error out
at runtime, so it’s ok.<a href="#fnref-4" class="footnote-backref">↩</a></li>
<li id="fn-5">In practice, we infer a simpler type <code class="language-text">[| 'Ok Number; ?a |]</code>
where <code class="language-text">?a</code> is a unification variable which can still have limitations.
Interestingly, we decided early on to not perform automatic generalization,
as opposed to the ML tradition, for reasons similar to <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tldi10-vytiniotis.pdf">the ones exposed
here</a>. Doing so, we get (predicative)
higher-rank polymorphism almost for free, while it’s otherwise quite tricky
to combine with automatic generalization. It turned out to pay off in the
case of structural ADTs, because it makes it possible to side-step those
usual enum types inclusion issues (<em>widening</em>) by having the user add more
polymorphic annotations. Or we could even actually infer the polymorphic
type <code class="language-text">forall a. [| 'Ok Number; a |]</code> for literals.<a href="#fnref-5" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2024-09-05-algebraic-data-types-nickel/</link><guid isPermaLink="false">https://tweag.io/blog/2024-09-05-algebraic-data-types-nickel/</guid><pubDate>Thu, 05 Sep 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Nickel modules]]></title><description><![CDATA[<p>One of the key features of <a href="https://nickel-lang.org/">Nickel</a> is the <a href="https://nickel-lang.org/user-manual/merging/">merge system</a>, which is a clever way of combining records, and allows defining complex configurations in a modular way.
This system is inspired by (amongst others) the <a href="https://edolstra.github.io/pubs/nixos-jfp-final.pdf#page=17">NixOS module system</a>, which are the magic bits that tie together NixOS configurations and gives NixOS its insane flexibility.
Nickel merging and NixOS modules work slightly differently under the hood, and target slightly different use-cases:</p>
<ul>
<li>Nickel merge is designed to combine several pieces of configurations which all respect the same contract. This allows an application developer to define the interface of its configuration, and have the user write it in a modular way.</li>
<li>NixOS modules, on the other hand, are designed to combine pieces which not only define a part of the final configuration, but also a part of the contract. This is a must-have for big systems like NixOS, where defining the whole schema in one place isn’t possible.</li>
</ul>
<p>Even in Nickel, it would be sometimes desirable to get the full modularity of NixOS modules. For instance, <a href="https://github.com/nickel-lang/organist">Organist</a> is based on this idea of having individual pieces, each defining both a part of the final schema and a part of the configuration — the <a href="https://github.com/nickel-lang/organist/blob/main/doc/filegen.md">files</a> module will define the interface for declaring custom config files, and hook into the base Nix system to declare a flake app based on it, the <a href="https://github.com/nickel-lang/organist/blob/main/lib/editorconfig.ncl">editorconfig</a> module declares an interface for managing the <code class="language-text">.editorconfig</code> file, and hooks into the <code class="language-text">files</code> interface for the actual generation of the file, etc.</p>
<p>Fortunately, it is actually trivial to implement an equivalent of the NixOS module system in Nickel by leveraging the merge system.
Not only that, but this will only be a very light abstraction over built-in features. This means that it will come with the joy of being understood by the LSP, giving nice auto-completion, quick and relevant error messages, and so on. Also, the lightness of the abstraction makes it very flexible, allowing to easily build variants of the system.</p>
<p>Before explaining how that works, let’s define a bit more precisely what we want with a module system.</p>
<h2 id="module-systems" style="position:relative;"><a href="#module-systems" aria-label="module 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>Module systems</h2>
<p>A “module system”, in the NixOS sense, is a programming paradigm which allows exploding a complex configuration into individual components.
Each component (a “module”) can define both a piece of the schema for the overall configuration, and a piece of the configuration.
The schemas of all the modules are combined to form the final schema, and the same goes for configurations.
The only constraint is that the final configuration matches the final schema.</p>
<p>For instance, here is an instantiation of a (very) simplified version of the NixOS module system, written in Nix:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">mergeModules <span class="token punctuation">{</span>
   module1 <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 punctuation">:</span> <span class="token punctuation">{</span>
     options<span class="token punctuation">.</span>foo <span class="token operator">=</span> mkOption <span class="token punctuation">{</span> type <span class="token operator">=</span> int<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
     options<span class="token punctuation">.</span>bar <span class="token operator">=</span> mkOption <span class="token punctuation">{</span> type <span class="token operator">=</span> string<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
     config<span class="token punctuation">.</span>bar <span class="token operator">=</span> <span class="token string">"world"</span><span class="token punctuation">;</span>
   <span class="token punctuation">}</span><span class="token punctuation">;</span>
   module2 <span class="token operator">=</span> <span class="token punctuation">{</span>config<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 punctuation">{</span>
      options<span class="token punctuation">.</span>baz <span class="token operator">=</span> mkOption <span class="token punctuation">{</span> type <span class="token operator">=</span> string<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      config<span class="token punctuation">.</span>foo <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
      config<span class="token punctuation">.</span>baz <span class="token operator">=</span> <span class="token string">"Hello "</span> <span class="token operator">+</span> config<span class="token punctuation">.</span>bar<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>

<span class="token comment"># Result</span>
<span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">{</span>
  foo <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>              <span class="token comment"># defined by module1, set by module2</span>
  bar <span class="token operator">=</span> <span class="token string">"world"</span><span class="token punctuation">;</span>        <span class="token comment"># defined by module1, set by module1</span>
  baz <span class="token operator">=</span> <span class="token string">"Hello world"</span><span class="token punctuation">;</span>  <span class="token comment"># defined by module2, set by module2 using `bar` from module 1</span>
<span class="token punctuation">}</span></code></pre></div>
<p>We can see that each module defines both a piece of the final schema (the <code class="language-text">options</code> field) and a piece of the final config (the <code class="language-text">config</code> field).
They have the ability to set and refer to options defined in another module.</p>
<p>Assigning a value of the wrong type to an option gives an error. For instance, changing <code class="language-text">config.foo = 3</code> to <code class="language-text">config.foo = "3"</code> yields:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">error<span class="token punctuation">:</span>
       … while evaluating the attribute 'value'
         at <span class="token url">/nix/store/axfhzzkixwwdmxlrma7k8f65214acvml-source/lib/modules.nix:809:9:</span>
          <span class="token number">808</span>|     <span class="token keyword">in</span> warnDeprecation opt <span class="token operator">//</span>
          <span class="token number">809</span>|       <span class="token punctuation">{</span> value <span class="token operator">=</span> <span class="token keyword">builtins</span><span class="token punctuation">.</span>addErrorContext <span class="token string">"while evaluating the option `<span class="token interpolation"><span class="token antiquotation important">$</span><span class="token punctuation">{</span>showOption loc<span class="token punctuation">}</span></span>':"</span> value<span class="token punctuation">;</span>
             |         ^
          <span class="token number">810</span>|         <span class="token keyword">inherit</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>defsFinal'<span class="token punctuation">)</span> highestPrio<span class="token punctuation">;</span>

       … while evaluating the option `foo'<span class="token punctuation">:</span>

       … while evaluating the attribute 'mergedValue'
         at <span class="token url">/nix/store/axfhzzkixwwdmxlrma7k8f65214acvml-source/lib/modules.nix:844:5:</span>
          <span class="token number">843</span>|     <span class="token comment"># Type-check the remaining definitions, and merge them. Or throw if no definitions.</span>
          <span class="token number">844</span>|     mergedValue <span class="token operator">=</span>
             |     ^
          <span class="token number">845</span>|       <span class="token keyword">if</span> isDefined <span class="token keyword">then</span>

       <span class="token punctuation">(</span>stack <span class="token function">trace</span> truncated<span class="token punctuation">;</span> use '<span class="token operator">-</span><span class="token operator">-</span>show<span class="token operator">-</span><span class="token function">trace</span>' to show the full<span class="token punctuation">,</span> detailed <span class="token function">trace</span><span class="token punctuation">)</span>

       error<span class="token punctuation">:</span> A definition for option `foo' is not of type `signed integer'<span class="token punctuation">.</span> Definition values<span class="token punctuation">:</span>
       <span class="token operator">-</span> In `<span class="token operator">&lt;</span>unknown<span class="token operator">-</span>file<span class="token operator">></span>'<span class="token punctuation">:</span> <span class="token string">"foo"</span></code></pre></div>
<h3 id="shortcomings-of-the-nixos-module-system" style="position:relative;"><a href="#shortcomings-of-the-nixos-module-system" aria-label="shortcomings of the nixos module system 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>Shortcomings of the NixOS module system</h3>
<p>This system is incredibly powerful, and rightfully serves not only the whole of NixOS, but also a number of other projects in the Nix world, such as <a href="https://github.com/nix-community/home-manager/">home-manager</a>, <a href="https://github.com/LnL7/nix-darwin/tree/master">nix-darwin</a>, <a href="https://flake.parts/">flake parts</a> and <a href="https://terranix.org/index.html">terranix</a>.</p>
<p>However, it suffers from some limitations, mostly due to it being a pure library-side encoding instead of a Nix built-in.</p>
<ul>
<li>The error messages can get quite daunting (just look at the example above). Great effort has been put to improve the Nix error messages (and it shows), but the situation is still far from ideal.</li>
<li>Because it has nearly zero support from the language side, no LSP server is truly able to understand it, meaning that things like autocompletion or in-editor error messages are very much best-effort, if they exist at all.</li>
<li>Finally, it is often too dynamic for its own good. The fact that it only cares about global consistency makes it nearly impossible to reason about a module individually. The only way, for instance, to know whether a given module is correct is to evaluate the whole module system to know whether all the options it refers to are defined somewhere. Likewise, it is absolutely impossible to know the exact consequences of flipping an option somewhere as it might have an impact on any other module.</li>
</ul>
<h2 id="implementing-a-better-module-system-in-nickel" style="position:relative;"><a href="#implementing-a-better-module-system-in-nickel" aria-label="implementing a better module system in nickel 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>Implementing a (better) module system in Nickel</h2>
<p>Given the usefulness of this module system, and its weaknesses, it would be great if we could have the same thing in Nickel – and even better if we could fix the shortcomings on the way.</p>
<p>It turns out that we can, and in a whopping <em>one</em> line of 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"> </span><span class="token property">Module</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">S</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</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 keyword">not_exported</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</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">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</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></code></pre></div>
<p>This probably needs some explanation, so let’s look at how it works, and how it can be used.</p>
<p>Before anything else, let’s be honest: the simplicity of that line is rather deceptive since it hides the big heavy lifting done by the runtime, in particular the merge and contract systems.</p>
<p>This defines a contract called <code class="language-text">Module</code> that represents values with:</p>
<ul>
<li>A <code class="language-text">Schema</code> field, which is a <a href="https://nickel-lang.org/user-manual/contracts/#records">record contract</a>;</li>
<li>A <code class="language-text">config</code> field, matching the contract defined by <code class="language-text">Schema</code>.</li>
</ul>
<p>We can use it as such:</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">Module</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">S</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</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 keyword">not_exported</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</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">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</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 keyword">in</span>

<span class="token operator">{</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">module1</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">S</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</span><span class="token operator">a</span><span class="token operator">.</span><span class="token operator">f</span><span class="token operator">o</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"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator">S</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</span><span class="token operator">a</span><span class="token operator">.</span><span class="token operator">b</span><span class="token operator">a</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 class-name">String</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">config.bar</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"world"</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 property">module2</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">S</span><span class="token operator">c</span><span class="token operator">h</span><span class="token operator">e</span><span class="token operator">m</span><span class="token operator">a</span><span class="token operator">.</span><span class="token operator">b</span><span class="token operator">a</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 class-name">String</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">b</span><span class="token operator">a</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"> </span><span class="token property">config.foo</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"> </span><span class="token operator"> </span><span class="token property">config.baz</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"Hello "</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">b</span><span class="token operator">a</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">,</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">d</span><span class="token operator">u</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">3</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator"> </span><span class="token property">Module</span><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">d</span><span class="token operator">u</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">1</span><span class="token operator"> </span><span class="token operator">&amp;</span><span class="token operator"> </span><span class="token operator">m</span><span class="token operator">o</span><span class="token operator">d</span><span class="token operator">u</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">2</span><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">d</span><span class="token operator">u</span><span class="token operator">l</span><span class="token operator">e</span><span class="token operator">3</span><span class="token operator">.</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span>

<span class="token operator"></span><span class="token comment"># Result</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">"bar"</span><span class="token operator">:</span><span class="token operator"> </span><span class="token string">"world"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token string">"baz"</span><span class="token operator">:</span><span class="token operator"> </span><span class="token string">"Hello world"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token string">"foo"</span><span class="token operator">:</span><span class="token operator"> </span><span class="token number">3</span>
<span class="token operator">}</span></code></pre></div>
<p>This is very similar to the Nix example above. What happens if we make a mistake on one of the fields, say replace <code class="language-text">config.foo = 1</code> with <code class="language-text">config.foo = "x"</code>?</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nickel export main.ncl
error: contract broken by the value of `foo`
   ┌─ /tmp/tmp.U0m6Ro8Vvo/main.ncl:13:18
   │
 5 │     Schema.foo | Number,
   │                  ------ expected type
   ·
13 │     config.foo = &quot;x&quot;,
   │                  ^^^ applied to this expression
   │
   ┌─ &lt;unknown&gt; (generated by evaluation):1:1
   │
 1 │ &quot;x&quot;
   │ --- evaluated to this value</code></pre></div>
<p>So the contract system is aware of what we want to check, and reports us an error accordingly.
Better, it points to the right place in the code.
Even better, the LSP server is aware of the error, and can point right at it:</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/bc9ed45c512cf5bd35df9646a0e4f0b0/8d68c/lsp-error-invalid-type.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 21.62162162162162%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAjUlEQVQI13XOyw5CIQxFUf7/C41zeWmktEBLi4nhXnXmmu+T45SIZTMzas37kPPdhxBTZpYhKJPNlm62lq0vM3OKKPMTtyOOMd28jymNwRku6XndE9KUOxGVDQpA78PpWsw8D4iIRNQaQEUikdkZ2ngMwTn5jGutpQBA7b07Vf3FzGyH89U+asvs9e/2GxyZ5gqAj9KGAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="lsp error invalid type"
        title="lsp error invalid type"
        src="/static/bc9ed45c512cf5bd35df9646a0e4f0b0/fcda8/lsp-error-invalid-type.png"
        srcset="/static/bc9ed45c512cf5bd35df9646a0e4f0b0/12f09/lsp-error-invalid-type.png 148w,
/static/bc9ed45c512cf5bd35df9646a0e4f0b0/e4a3f/lsp-error-invalid-type.png 295w,
/static/bc9ed45c512cf5bd35df9646a0e4f0b0/fcda8/lsp-error-invalid-type.png 590w,
/static/bc9ed45c512cf5bd35df9646a0e4f0b0/efc66/lsp-error-invalid-type.png 885w,
/static/bc9ed45c512cf5bd35df9646a0e4f0b0/c83ae/lsp-error-invalid-type.png 1180w,
/static/bc9ed45c512cf5bd35df9646a0e4f0b0/8d68c/lsp-error-invalid-type.png 1234w"
        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="global-vs-local-consistency" style="position:relative;"><a href="#global-vs-local-consistency" aria-label="global vs local consistency 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>Global vs local consistency</h3>
<p>This is definitely great, and solves the LSP integration issue, as well as part of the error messages one.
What it doesn’t do is alleviate the lack of local consistency: <code class="language-text">module1</code> and <code class="language-text">module2</code> are still implicitly depending on each other, and the only way to know that is to see that — for instance — <code class="language-text">module2</code> sets <code class="language-text">bar</code>, which is declared in <code class="language-text">module1</code>.</p>
<p>We can, however, enforce something stricter:
In our example, we only require <code class="language-text">module1 &amp; module2</code> to be a valid module.
But we could require each of them to be individually consistent by enforcing that their <code class="language-text">config</code> field matches their <code class="language-text">Schema</code> field:</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token coord">--- a/main.ncl</span>
<span class="token coord">+++ b/main.ncl</span>
@@ -4,11 +4,13 @@ let Module = { Schema | not_exported = {}, config | Schema } in
<span class="token unchanged"><span class="token prefix unchanged"> </span>  module1 = {
<span class="token prefix unchanged"> </span>    Schema.foo | Number,
<span class="token prefix unchanged"> </span>    Schema.bar | String,
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    config | Schema,
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    config.bar = "world",
<span class="token prefix unchanged"> </span>  },
<span class="token prefix unchanged"> </span>  module2 = {
<span class="token prefix unchanged"> </span>    Schema.baz | String,
</span>
<span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    config | Schema,
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    config.bar,
<span class="token prefix unchanged"> </span>    config.foo = 3,
<span class="token prefix unchanged"> </span>    config.baz = "Hello " ++ config.bar,</span></code></pre></div>
<p>Doing so will yield an error:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nickel export main.ncl
error: contract broken by the value of `config`
       extra fields `bar`, `foo`
   ┌─ /tmp/tmp.U0m6Ro8Vvo/main.ncl:13:14
   │
13 │     config | Schema,
   │              ------ expected type
   │
   ┌─ &lt;unknown&gt; (generated by evaluation):1:1
   │
 1 │ { bar, baz = %&lt;closure@0x55fa68e718c8&gt;, foo = 3, }
   │ -------------------------------------------------- evaluated to this value
   │
   = Have you misspelled a field?
   = The record contract might also be too strict. By default, record contracts exclude any field which is not listed.
     Append `, ..` at the end of the record contract, as in `{some_field | SomeContract, ..}`, to make it accept extra fields.</code></pre></div>
<p>Indeed, <code class="language-text">module2</code> isn’t consistent. It is using <code class="language-text">foo</code> and <code class="language-text">bar</code>, but doesn’t know about them as they are declared in <code class="language-text">module1</code>.
But we can fix that by making it explicitly depend on <code class="language-text">module1</code>:</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token coord">--- a/main.ncl</span>
<span class="token coord">+++ b/main.ncl</span>
@@ -7,7 +7,7 @@ let Module = { Schema | not_exported = {}, config | Schema } in
<span class="token unchanged"><span class="token prefix unchanged"> </span>    config | Schema,
<span class="token prefix unchanged"> </span>    config.bar = "world",
<span class="token prefix unchanged"> </span>  },
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>  module2 = {
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>  module2 = module1 &amp; {
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    Schema.baz | String,
</span>
<span class="token unchanged"><span class="token prefix unchanged"> </span>    config | Schema,</span></code></pre></div>
<p>And now everything gets completely consistent, and we can confidently write a fully modular configuration, with the assurance that the language will have our back and give us early warnings in case anything goes wrong.</p>
<p>Besides, since the language really knows about everything that’s going on, the LSP can do its magic and help us with autocompletion.
Let’s add a new option to <code class="language-text">module1</code>:</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token coord">--- a/main.ncl</span>
<span class="token coord">+++ b/main.ncl</span>
@@ -4,6 +4,12 @@ let Module = { Schema | not_exported = {}, config | Schema } in
<span class="token unchanged"><span class="token prefix unchanged"> </span>  module1 = {
<span class="token prefix unchanged"> </span>    Schema.foo | Number,
<span class="token prefix unchanged"> </span>    Schema.bar | String,
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    Schema.my_option
<span class="token prefix inserted">+</span>      | { _ : String }
<span class="token prefix inserted">+</span>      | doc m%"
<span class="token prefix inserted">+</span>          The set of things that will wobble up when
<span class="token prefix inserted">+</span>          stuff wiggles down and bubbles sideways
<span class="token prefix inserted">+</span>        "%,
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    config | Schema,
<span class="token prefix unchanged"> </span>    config.bar = "world",
<span class="token prefix unchanged"> </span>  },</span></code></pre></div>
<p>We can now refer to it from <code class="language-text">module2</code>, and have our editor tell us everything we want to know:</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/3c7b630df1b9d1bd1c5fe48a55a2a550/d5f92/lsp-completion.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 22.972972972972975%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA3UlEQVQY023PSU/DMBQE4Pz//wQVUCFR1AMSICCOE2/PjpeQONtz3ANqxXLh0xxGc5sCEVNKAJpzEUIXx25c3CkjrmuMcYzjltIp/6/AiyHGEDqhtHHWd94653z47GPXD32chnGOf5l+e4EpTdM8zef15a3iYKtGHZ9en9+J1E6ZQJSnymnroQ0/8doG1fpiXXGel2VZnA+7u8PN/fF6/3h1+7DbHz4qRpkkNS9rXlJeNZLUoqSspIw0gtS82C5yztu24fkprvgNjBVgai650o0AoQyTwAQIMJRLAe0XXRQTsPbLc3gAAAAASUVORK5CYII='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="lsp completion"
        title="lsp completion"
        src="/static/3c7b630df1b9d1bd1c5fe48a55a2a550/fcda8/lsp-completion.png"
        srcset="/static/3c7b630df1b9d1bd1c5fe48a55a2a550/12f09/lsp-completion.png 148w,
/static/3c7b630df1b9d1bd1c5fe48a55a2a550/e4a3f/lsp-completion.png 295w,
/static/3c7b630df1b9d1bd1c5fe48a55a2a550/fcda8/lsp-completion.png 590w,
/static/3c7b630df1b9d1bd1c5fe48a55a2a550/efc66/lsp-completion.png 885w,
/static/3c7b630df1b9d1bd1c5fe48a55a2a550/c83ae/lsp-completion.png 1180w,
/static/3c7b630df1b9d1bd1c5fe48a55a2a550/d5f92/lsp-completion.png 2150w"
        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>We’ve seen how we can easily implement an equivalent of the NixOS module system on top of Nickel.
We’ve also seen how this maps nicely to the semantics of the language and gives us access to all the benefits of good error messages and editor integration.</p>
<p>This is obviously just scratching the surface, and there’s a ton of extra features of NixOS modules that haven’t been covered here. Some would be trivial to implement, like <a href="https://nixos.org/manual/nixos/stable/#section-option-types-submodule">submodules</a> (left as an exercise to the reader). Others would require more work, or even <a href="https://github.com/tweag/nickel/blob/master/rfcs/001-overriding.md#custom-merge-functions">changes to the underlying language</a> like <a href="https://nixos.org/manual/nixos/stable/#sec-option-types-custom">custom types with custom merge functions</a>.
However, even this simple formalization is already tremendously powerful. It is used in <a href="https://github.com/nickel-lang/organist/pull/205">an experimental branch of Organist</a> to provide a principled way of combining different, independent, but related pieces of functionality.</p>
<p>A great possible follow-up work would be to hook that up with existing NixOS modules (maybe using a mixture of <a href="https://docs.clan.lol/blog/2024/05/25/jsonschema-converter/">jsonschema-converter</a> and <a href="https://github.com/nickel-lang/json-schema-to-nickel">json-schema-to-nickel</a>) to allow writing one’s NixOS configuration directly in Nickel… maybe soon?</p>]]></description><link>https://tweag.io/blog/2024-06-20-nickel-modules/</link><guid isPermaLink="false">https://tweag.io/blog/2024-06-20-nickel-modules/</guid><pubDate>Thu, 20 Jun 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[The right words in the right place]]></title><description><![CDATA[<p>tl;dr You may not believe it, but Nix documentation is getting better.
Nixpkgs and NixOS still need more time.</p>
<h1 id="table-of-contents" style="position:relative;"><a href="#table-of-contents" aria-label="table of contents 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>Table of contents</h1>
<ul>
<li><a href="#overview">Overview</a></li>
<li><a href="#motivation">Motivation</a></li>
<li><a href="#statistics">Statistics</a></li>
<li><a href="#retrospective">Retrospective</a></li>
<li><a href="#thoughts-on-future-work">Thoughts on future work</a></li>
<li><a href="#acknowledgements">Acknowledgements</a></li>
</ul>
<h1 id="overview" style="position:relative;"><a href="#overview" aria-label="overview 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>Overview</h1>
<p>This is a retrospective of my and many other people’s work on documentation in the Nix ecosystem between October 2022 and March 2024.
It serves as a showcase of what we achieved together, and gives an impression of what’s involved in improving the user experience in a complex software system.
A lot has happened during that time, so this text is quite lengthy.</p>
<p>The details of this report will mainly be interesting to power users and active contributors, or people working on software projects that are in a similar situation as Nix.
For everyone else, I’d summarise the effort so far as “success in slow motion that indicates compounding effects”.
Much of it was made possible through ongoing sponsorship from <a href="https://antithesis.com/">Antithesis</a>, <a href="https://www.tweag.io/">Tweag</a>, and many individuals, along with the incredible commitment of numerous volunteers.</p>
<ul>
<li><a href="https://nix.dev/">nix.dev</a> is now the official documentation hub for the Nix ecosystem, designed to follow the <a href="https://diataxis.fr/">Diátaxis</a> documentation framework.</li>
<li>There are a number of new in-depth tutorials and practical guides, with more in the making.</li>
<li>The contribution process got smoother and is documented much better.</li>
<li>The Nix manual substantially improved in terms of structure, clarity, and detail.
It can now be accessed by release version on nix.dev.</li>
<li>The Nixpkgs and NixOS manuals are now entirely written in Markdown, after two years of migrating away from XML.</li>
<li>More people than ever have contributed improvements to documentation.</li>
<li>The official NixOS Wiki has launched under <a href="https://wiki.nixos.org">wiki.nixos.org</a>.</li>
</ul>
<p>It may not seem like much, but these are big changes that posed enough of a challenge to arrive at.
It’s still not easy or fast to learn all the things needed to wield the full power of Nix.
However, there are reports of people grasping most of it within a couple of weeks on their own, which would be a significant improvement over what previously seemed to take months.
And as more contributors chime in, things are continuously getting better.</p>
<p>The next steps will be expanding reference documentation, developing more suitable technical infrastructure, adding more tutorials, shaping an information architecture that connects the ecosystem… and simply figuring a lot of things out and writing them down.</p>
<h1 id="motivation" style="position:relative;"><a href="#motivation" aria-label="motivation 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>Motivation</h1>
<p>What originally compelled me to start working on improving Nix documentation was a vague feeling that there was something profound about Nix, something that was merely obscured by confusing explanations.
It wasn’t just the fact that the Nix ecosystem permanently solves many problems in computing I believe should never have existed in the first place.
It was the question:
What underlying principle makes it possible for it to do so? Being in the right place at the right time often enough has allowed me to pursue a few hunches as part of my day job at Tweag for the past two years.
And that time was needed.</p>
<p>First, it took me a while to fully grasp how beautifully simple Nix is conceptually:</p>
<ul>
<li>The Nix store has a computation engine that operates on file system trees, disguised as a system for caching sandboxed <code class="language-text">execve</code> calls.</li>
<li>The Nix language’s most distinguishing features are, not coincidentally, first-class facilities for dealing with file system paths and strings.</li>
<li>Nixpkgs is the world’s largest knowledge base for getting open source software to run.</li>
<li>NixOS offers a uniform user interface for configuring that open source software.</li>
</ul>
<p>Second, after staring at it long enough, it turns out that the underlying powerful idea is <em>programming itself</em>! More precisely, disciplined programming:</p>
<ul>
<li>The Nix store enforces <em>referential integrity</em> on the file system and constrains processes to act like <em>pure functions</em>, and it facilitates <em>distributed, incremental computation</em>.
Read more on that in <a href="https://www.tweag.io/blog/2022-07-14-taming-unix-with-nix/">Taming Unix with functional programming</a>.</li>
<li>Nixpkgs is written in the <em>purely functional</em> Nix language, and keeps scaling due to a substantial amount of <em>automation</em>.</li>
<li>The magic behind NixOS (and related tools, such as Home Manager) is the module system, which combines a rich <em>type system</em> with <em>modular composition</em>.
Together with virtual machines, it enables systematic <em>integration testing</em> of a large part of the ecosystem’s artifacts.</li>
</ul>
<p>From the outside, all of this seems terribly messy.
It has grown organically over 20 years, and most of it is still only sparsely documented.
Some parts are hard to understand; but that’s often because programming can be fundamentally hard.
And it doesn’t help that things are just as messy on the inside.
There are places where I’d say “the code doesn’t understand itself”.</p>
<p>The same things that make Nix hard to learn and use also make it hard to document and teach:
There are many ways of doing certain things with Nix, and often none of them is clearly superior.
A large part of that problem must eventually be solved at the implementation level, but that is subject to <a href="http://www.winestockwebdesign.com/Essays/Lisp_Curse.html">the Lisp Curse</a>.
In a volunteer-driven open source software community, many technical issues reduce to social issues.
The <em>real</em> challenge is coordination!</p>
<p>That’s not exactly what I had originally subscribed to.
But in retrospect, maybe it’s not surprising that, apart from finding the right words and the right place for them, the path to better documentation is paved with debates, design documents, fundraisers, UX workshops, governance meetings — that is, listening and talking to lots of people.</p>
<p>Making the ecosystem more approachable for a mass audience may as well end up to be teaching “disciplined software development with Nix”.
But even that will require us, as a community-of-practice, to agree on (rather than just find) answers to many open questions.
Until then, this report is meant to be a panorama of what that process has entailed so far, from my perspective.
I hope that it will encourage you to try for yourself and participate, or otherwise help out.</p>
<h1 id="statistics" style="position:relative;"><a href="#statistics" aria-label="statistics 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>Statistics</h1>
<p>Before getting into a lengthy narrative, let’s look at recorded documentation activities of the past two years.
tl;dr: numbers go up, this is good.</p>
<p>The following three charts show the number of documentation-related pull requests merged in the three repositories the team has worked on: Nix, Nixpkgs, and <a href="http://nix.dev">nix.dev</a>.
The charts have the same vertical scale and each bar stands for a quarter of a year since the beginning of 2022.
Documentation work on Nix is underrepresented prior to Q3, since we only then began systematically labeling those pull requests.
For nix.dev all pull requests are counted.
The most recent period accounts for at most half of the quarter, because this is when I compiled the data.</p>
<p>Notably, Nixpkgs documentation activities have grown roughly with (and possibly even slower than) the overall number of users and contributors.
This sample shows all pull requests automatically labeled as being related to documentation, which often comes with package updates and are often inconsequential in practice.</p>
<p>In contrast, the frequency of changes in <a href="http://nix.dev">nix.dev</a> and the Nix manual greatly increased since the documentation team was founded.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <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,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAB7CAAAewgFu0HU+AAABt0lEQVQ4y6WTy0rDQBiF8yI+hStBXAniWheCK9/Al/ABBC/gZaOooCAqoi6qggt3aqtFCjW0NbZpczPSJE0yMzkyU9OOrYriD4fzM0MO3/wzUZrNJpIkEWKMwTAMmKYJXddRr9dhWRYajYbo071ardbp3cBDTAjiKBbfK57ngVL6KVQW35Nd7hOWQHVNmK8OHNtGFEX9gVy8ZP9pvbcUx3H6CH/rveLrShAEfYT/UYfwL2QyYW+vEEJEYFpfzeo7pzyoZ5aKbdu/JuQBJL11xjohf54hYQwRJX03mrWquLf1NnFK6Lrul++Qfigi3SD1zULe1kU4Jx05XsbY6SrAElBCuw87xZVnJNd+OY+lx2tMnm9i8GAeM1e7mLrYEoGjJyvQNA1quYQwDNtHjuO4OxeWwAo8VJsuLqtP2FNzmMxsYGBnDhOZDQwdLmDsdA3DR4sibPxsHdMX25i9PmwfudVqwfd98b/y/9o0TDxbBu5KRTzoz8gUcnhq1JAtqyjUX3BbKqKga8hWVOSrFeQqKm40FTflIhilUHgqp5QV8ouKYpBWCFCG0A8ASj+cSe4LjzxfnIwTvgNVAi2vSmMVsgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="255f52a1 612d 4224 b3b9 865c089371dc"
        title="255f52a1 612d 4224 b3b9 865c089371dc"
        src="/static/5a30e28e76a839c41f10166ae13b7498/fcda8/255f52a1-612d-4224-b3b9-865c089371dc.png"
        srcset="/static/5a30e28e76a839c41f10166ae13b7498/12f09/255f52a1-612d-4224-b3b9-865c089371dc.png 148w,
/static/5a30e28e76a839c41f10166ae13b7498/e4a3f/255f52a1-612d-4224-b3b9-865c089371dc.png 295w,
/static/5a30e28e76a839c41f10166ae13b7498/fcda8/255f52a1-612d-4224-b3b9-865c089371dc.png 590w,
/static/5a30e28e76a839c41f10166ae13b7498/efc66/255f52a1-612d-4224-b3b9-865c089371dc.png 885w,
/static/5a30e28e76a839c41f10166ae13b7498/c83ae/255f52a1-612d-4224-b3b9-865c089371dc.png 1180w,
/static/5a30e28e76a839c41f10166ae13b7498/11f9c/255f52a1-612d-4224-b3b9-865c089371dc.png 1707w"
        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"
      />
    </span></p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <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,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAB7CAAAewgFu0HU+AAADIUlEQVQ4y22T608UVxiHt/4NTaNtqhJjE1Pb1MRLNFaktrU2fiheEjXGWzT6of1g+sFEP6mUW9W2aOuqoAF12V0FAVlZZMuyiNVWCaBNpQpFUvbK7DYCy+zOZZ/mnB3WSzzJmfecOfP+5nl/847t2dgYSS2NYRjE43EikQjRaJRwOIwyqpCIx+Va3E8kEoSCISLRKLHRGKFgkLiiIEYmk5HTlhh7Rr8SZjSuyARN0zBNU07N0EnrunyZbujZe7ousqXIN7ev8e2d6y8Ljk9M8OIwMxmMjCnXJX1+ll//Jbc/eK+FLX4HmmnI5xY1VrDCY88KWtOmjo0TCA1y6J6XhJqUh5phyLivq563Lh2hvK+D/XeaWNt6gfl1x9ndeYWt/loKPGdY463CNAzSVmU2IXu4+yZvVB2gqOdX1vlqiKlZ6q9/a2COq5RCXw15rhJ5tqTxJKtunGVp0ynyPXY+bznHeEwhOhIkOTmJjaTKsb4OZjtL2BFwMa3yAMqU4O1r5DlL2NpRy0f1P1DYVs3ihgq+bD3PymY7BR47q1sqOdJ9k58e3pI5NnEp7WlnhuMoe25d5c1Lh+mKDPEgHmJvVz15rlI2+x3MrzvBV23VLGyo4AtvFR83n2alx85nLeeYd/UYq72VluCESnmvn7cdRezqdDPLWcz2gIuZtd/J+J67nM3tDj6sO5EjFL7lN5/OES5r+pmNvouWoAllvYKwiJ2dbmY6i2XpU8Jz3WWS8ANLcJFFuMIiFB4ubjwp/c0KqpokfMcinO0sZmfALb3bYRGKVhGE69pqZPIU4SeeM5JQfKj1OUE9Q1mvn+mOo5LwXatUQbot4GKOu4xN7Zd5v+54zkPhl/Aw3/JQ9GOhr/p5yeU97bJE0V+iTXYF3Lk478r3sucW1P/IBl8NSxtPsbb1PAXNdj69cZY13lc8TKdSjPyncHfgEQOxML8P9jMQC8n4JBbij8G/eRwNcn/oMf2REbqHnvBX+F96ng7wZ3CYvuFBHgaf8mD4H9ngsm3SqoqRSqOpKcy0JvcZTSc9mY2p5CQY5mtiUkZ1Iin+WUn4P+RzdWltvk8RAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="3d3dc368 cf07 4873 94a0 fd788ce52b37"
        title="3d3dc368 cf07 4873 94a0 fd788ce52b37"
        src="/static/644e8716d3bad191e54aae7cb44fdd59/fcda8/3d3dc368-cf07-4873-94a0-fd788ce52b37.png"
        srcset="/static/644e8716d3bad191e54aae7cb44fdd59/12f09/3d3dc368-cf07-4873-94a0-fd788ce52b37.png 148w,
/static/644e8716d3bad191e54aae7cb44fdd59/e4a3f/3d3dc368-cf07-4873-94a0-fd788ce52b37.png 295w,
/static/644e8716d3bad191e54aae7cb44fdd59/fcda8/3d3dc368-cf07-4873-94a0-fd788ce52b37.png 590w,
/static/644e8716d3bad191e54aae7cb44fdd59/efc66/3d3dc368-cf07-4873-94a0-fd788ce52b37.png 885w,
/static/644e8716d3bad191e54aae7cb44fdd59/c83ae/3d3dc368-cf07-4873-94a0-fd788ce52b37.png 1180w,
/static/644e8716d3bad191e54aae7cb44fdd59/11f9c/3d3dc368-cf07-4873-94a0-fd788ce52b37.png 1707w"
        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"
      />
    </span></p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <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,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAB7CAAAewgFu0HU+AAACD0lEQVQ4y52Sz2sTURDH86d4Fg96K4IWoSBe6qEiePHg1ZMg6F0QFIWChbbSQ/wBFhVCSxqLSbEKKgaNtmqsSdOYtpufu0mabDa7+3bzkbdpSpomog4M37czs9/3fTPjq9frtFotpNVqNfL5PKVSCUVRPFRVlUKhQC6XQ9M0D6WXy2Uvn1WL2MLGtm1c18Wn6zqO43ik0uVZJnq9U9ObT1VKlCplypqGZVkHCbtdWjcOquk2n5TeTShv/VvsdRn3GYYxUOH/+J7Cf1H2R4VCCI+wY/16191Dt08PO2QeoVyFQQr3Tdl1d2/EOzuuQ2u3dt9QBvVQktA9yRYYwqZmmXs/X34b4Mr7eYzqDhVNw5RrU61WEY5AuG2Vouf5Xsxtxy69ecpwcIpbK6+4EYswNDfBcHCSH9sZvm+lke3z1XX9wC41hIXtONxeXeZs2M+1aIixyENGw36OBsY5EZzkWGCcUwvTnF6c4WRoiguvn7SfjO0Q3lzj6ocgX1SF9I7G9WiII8/vcnF5lkOzNzm/9JjDz+5wLvKI4/MTjL70M7IwzUjoPmcWZxiau8dY+EGbUJgWKTXPi3iMRDFLNPWTREHh3XqcVDHHx40EyUKWWDrJWn6bT+kk8dwWn3+t803JsJJJ8VXJsLq5ges4+CSrME2wBXbTxLFsD+W3ZTRp2QKzYcjR9sGGh029AW57eL8BzjAaORGcB2oAAAAASUVORK5CYII='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="84480102 9633 48d2 9551 d00a2dff3751"
        title="84480102 9633 48d2 9551 d00a2dff3751"
        src="/static/1569c8cf608edd4baed1c8baafaa6972/fcda8/84480102-9633-48d2-9551-d00a2dff3751.png"
        srcset="/static/1569c8cf608edd4baed1c8baafaa6972/12f09/84480102-9633-48d2-9551-d00a2dff3751.png 148w,
/static/1569c8cf608edd4baed1c8baafaa6972/e4a3f/84480102-9633-48d2-9551-d00a2dff3751.png 295w,
/static/1569c8cf608edd4baed1c8baafaa6972/fcda8/84480102-9633-48d2-9551-d00a2dff3751.png 590w,
/static/1569c8cf608edd4baed1c8baafaa6972/efc66/84480102-9633-48d2-9551-d00a2dff3751.png 885w,
/static/1569c8cf608edd4baed1c8baafaa6972/c83ae/84480102-9633-48d2-9551-d00a2dff3751.png 1180w,
/static/1569c8cf608edd4baed1c8baafaa6972/11f9c/84480102-9633-48d2-9551-d00a2dff3751.png 1707w"
        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"
      />
    </span></p>
<p>The next three charts show the number of GitHub issues related to documentation that were opened or closed in a given quarter, again for Nix, Nixpkgs, and nix.dev.
These charts also have the same scale.
Here, one can observe that Nixpkgs documentation activities, while more pronounced overall due to the greater number of people involved, are significantly driven by the team.
There is also some up-and-down due to alternating between more exploratory and more implementation-oriented periods.</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 68.24324324324324%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAB7CAAAewgFu0HU+AAACaElEQVQ4y41SXU8TQRRd/4TPxpgYNNE3f4aPPpj46pMPyhMGRaMPGFRiDAoqkhBfMBFFwSDBD6gBa1uqUBBLWz5Edrfdbrvb7vfO7DEz2wWpxHCTm5O5M3PmnHtHsCwLnueBBSEEiqKgVCqhWCxCkiSoqsprbC3LMiqVCjRNgyiKKJfLvG65Dgil/L5gmiZs2+aEQRDwIm1sRhhlc50hi8TWKhbW8qgoZQiGYYCpjAjZob0wyuY1S9fz4LpuqHA/hDSg8AkJHVDyz350l+W+CJvjf06EWq3GCbfV/K2MkQWAYhvIqCI261W8WJ3npDuKQwfbhGySjuPs+bLfUJdSNtGRmkBGldAaHw1/BCWcqLkFgq7ru6Ycqgr7wdRFhHczMRTqKtqT47zm+v5OC6KDQPhtoh5SRsS+BA2HsGVo+LSVR76u4vRIL4YWv6JrYRo5TcHgSgp2QHDu1WMkcsvwDQuaru8eCmlYXK4WcSczjXytjEuzryG7Jg7ea0XbxHMMFNJYrMq4GHuJ76qIA53nMTI3i82iBK2qQWD9iyxHsWHqODsxiIS0jlPPbqE/HUPLk2vojI3izNhTjObmcainDQ+SH3G07wpuz46j+8dMaNlzXCiGjgvvhzA8H8dwNo3JwhKO919H59QbnBi4wfHkwE10z7zDkd52dH1+i2OPOnA/PomWvqvoSXzA4YeXkShkIRDfR900Ectm8PP3BqYa+G0thzVZRHp1BeuyiFQhi/WihLlCFr9KMpL5ZY6sfwy/rCyhVFEh8ClRCte0+HQ9hjSAbZi87jAkFI5pbSMlBK5lg/o7yOrM8h/L0wwzxO4XfgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="eb0e2611 35aa 42d1 ba77 98c18faca5e2"
        title="eb0e2611 35aa 42d1 ba77 98c18faca5e2"
        src="/static/5d67f05d5611e72a09b4c6937416f726/fcda8/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png"
        srcset="/static/5d67f05d5611e72a09b4c6937416f726/12f09/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png 148w,
/static/5d67f05d5611e72a09b4c6937416f726/e4a3f/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png 295w,
/static/5d67f05d5611e72a09b4c6937416f726/fcda8/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png 590w,
/static/5d67f05d5611e72a09b4c6937416f726/efc66/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png 885w,
/static/5d67f05d5611e72a09b4c6937416f726/c83ae/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png 1180w,
/static/5d67f05d5611e72a09b4c6937416f726/3a1b1/eb0e2611-35aa-42d1-ba77-98c18faca5e2.png 1686w"
        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"
      />
    </span></p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 68.24324324324324%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAB7CAAAewgFu0HU+AAACqklEQVQ4y41STU8TURSdnSv3uHGhYmJMdKN/wKU7/QG6deHCaIgJyocon8YIgTQoYAjRDYEYgqiUEtJStd+hIrS0tJa2dNqZdkjb+Wpnpkffa6cQosab3Jz35t57ct6cy8iyjGq1ChLFYhEcxyGXyyGTyUAQBHonybIseJ6nPaRO+zgO6f00WC4H3TCg6zoYSZKgKAolNBofj6OZ5l3Tddrv59OYCLmwySaxEY9C4PNgRFEEUUmiVqvRIYL/Ss0waL+DjaPTv0zPsqrWFf6N8DjqR9CMdfYnegIr9ExUk/p/EZph1OrnlHgAH5eCm0ui22+t1xo9TKlUooTm8FEiQlAzDKi6hoIiNdU5MnEMBNfwQ8iiixAqVWQzLDWRIe6pqvpHhSbB90IGHd7PTaU+Po179nnY9sLoD66hLEnwJCJQZQUMWQPT5UNlNfpPTMKN/D46A9Ym4XaRx6nhB3jmWMRU1Acvl8Lgpr3+ZLI25MlGw8GqrgG1+qBJuClkcf29BauxLXjzacSkA7QM30evYxGvIx4E+DRuW9+iUqmAKZfLUBsK9cZ+lbQKPLkkFBi4MTuGEZcV58YfoWN1HtdmRzC77cVZSzue2hcwHQsgJgo4MXAHnvgOmGqlgny5iHCRp2RzW17MBL/glu0d3GwCLaNtGFxfwsXJbrz8uowzlnYMOT/i/Phj9K1/QJtzAc5MHCef360TGlUNu4Ucbi5NwZ4Io3WiE10rc7g604/etQVcme7DkGMJl9/04IXzEy5NPsHINysuvOrAqNuGVks7xjw2nB57CNduCIymaZBkGa5oCKFUAsFEDIlsBoF4hKI/tkPRF9vBXo6FdzfcwBCSXBbeaB3dkW3wQgEMdZfsmigRm6GIYvPeRN2AKslNNHQdFVmBoR0inf/t8i+t9PWgMAQ6aAAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="3c0136b6 82a3 441e 9a5a 0a2d7dadd237"
        title="3c0136b6 82a3 441e 9a5a 0a2d7dadd237"
        src="/static/710b3d3a6052b9c4d065703b31c251d4/fcda8/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png"
        srcset="/static/710b3d3a6052b9c4d065703b31c251d4/12f09/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png 148w,
/static/710b3d3a6052b9c4d065703b31c251d4/e4a3f/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png 295w,
/static/710b3d3a6052b9c4d065703b31c251d4/fcda8/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png 590w,
/static/710b3d3a6052b9c4d065703b31c251d4/efc66/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png 885w,
/static/710b3d3a6052b9c4d065703b31c251d4/c83ae/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png 1180w,
/static/710b3d3a6052b9c4d065703b31c251d4/3a1b1/3c0136b6-82a3-441e-9a5a-0a2d7dadd237.png 1686w"
        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"
      />
    </span></p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 68.24324324324324%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAB7CAAAewgFu0HU+AAACZ0lEQVQ4y52TS08TYRSGZ+vCxB9A4gKMmhgX/A5/g67ckbhTg2IMotFgCChyayIa3XiPCsFqWxAQaAHDTVpogZZUewV6m3ZmOjOP+aa01EaN8SQnb85l3nm/c75PyufzaJqGsEwmQywWszwajZJIJCxPJpNEIhHi8XilR9T39vasXGJ3B9000HUdSZZlCoWCRWgYpWQtlv13dUzwhDdZ2gqwm0gi5XI5hEphpmlWXDRXx7X56rqqaSiqUlJYS1j7wb9gtf03oW6Uj2ySzWVJ7ewiZ7NIYsiCsNz8NyLDNCmKeVap0nQdraDgXJjF/30bSWxPUZQ/KlSLxcqPhJqyBRIR9uRsJW6zv8S5voyUTqcrW/71SKUNlk0tlq7W068TbCaiDE45sHlchPJpK9868hzX2nLp2tTO0NhXktIUWieHWA4HrXhTTnG4vYn3K3PccLymZfQN50efWbVO1zscvsWDpYiLKQjFscRchLqgnOLQ3SZsM0765sZ4vDTFsb4rdM58pL6nmW6Pk7r7FxnxLXDu1QCf11eQxPzU/RmWSTNGkTMvurHNj3H6YSt3Joape3CJ2+NDHB+4Rse0nYbeZrrcn2joaabL4+BIxwXcGz4kTVWJZ1LYQ14WY2HO2p/g2vJS33+VW6NvaXx0k/bxYRoH2+j48oFTtut0Tts52d/CPfcnTvS10Dvr4mj3ZWYCXiS9WCQj55hc/0YoGmHav4ovHGIxGCAY/cH8xloFQ7EIcwGfhbMBL9vxKB5/Cd3+VetNS+VlqDkZUzdQ5TwYJgURGwbKfl6R8xU0dB01X8AoHqDoEy/lJ91oAp2d2v0RAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="b618968c c4a3 4ef0 9058 7fec0d662c43"
        title="b618968c c4a3 4ef0 9058 7fec0d662c43"
        src="/static/79b41728b442385815bb4698a7e8684a/fcda8/b618968c-c4a3-4ef0-9058-7fec0d662c43.png"
        srcset="/static/79b41728b442385815bb4698a7e8684a/12f09/b618968c-c4a3-4ef0-9058-7fec0d662c43.png 148w,
/static/79b41728b442385815bb4698a7e8684a/e4a3f/b618968c-c4a3-4ef0-9058-7fec0d662c43.png 295w,
/static/79b41728b442385815bb4698a7e8684a/fcda8/b618968c-c4a3-4ef0-9058-7fec0d662c43.png 590w,
/static/79b41728b442385815bb4698a7e8684a/efc66/b618968c-c4a3-4ef0-9058-7fec0d662c43.png 885w,
/static/79b41728b442385815bb4698a7e8684a/c83ae/b618968c-c4a3-4ef0-9058-7fec0d662c43.png 1180w,
/static/79b41728b442385815bb4698a7e8684a/3a1b1/b618968c-c4a3-4ef0-9058-7fec0d662c43.png 1686w"
        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"
      />
    </span></p>
<p>The final three charts show the number of new contributors and those who made repeated contributions related to documentation, again per quarter and for Nix, Nixpkgs, and nix.dev.
These charts are not to scale with each other because including Nixpkgs would have made the others unreadable.
This is the most notable change, and in my opinion the most important success:
We now have around 10 people working consistently on Nix reference documentation and introductory materials — more than ever!</p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAB4ElEQVQoz22SXU8TQRSG+zexhgsEaiIX/g4vTIhG4i8xChQu0AIWxKiUfsK23RKWQmB3uzsfO/OYGViF6EnevHOSkyfvmZlKURhuk5Q0TUmShDzPkVKilCKOY6Io8rq8uiGKLr0LKZHKIKVCauPnS1W0llxM2oRhSBAMmU6nWGsxxnh3cmVbr0FeY8U1FDM4WcXkU2xrFaNS3JQHZllGoSRllYD75u95Zw4zG0NzhbPzLlfb85CPsV/mQcd+RGvlEmqU0v/AynRert9dxGYhHK6w1Tmms7EE8hy7/xw5u0IokEJQKYrC39cjiD/frUy59u4CWewSvmCn16K/sYidhejdGjeXAdG4TZ5nVMoHeJjwD/Revr4+Y++sx7C+zN7ghGF9iWbQI6gvgQjh4CVaxP9P6Cvpg1HQfsWP8YBwe9kD+ps19h1wq0bjtMtgswb5CNtYQOW3d0B3jzxIM8xhvD7HaPKLi/pT9nrfaX14QjPo0vtYpdE/Jliv8nnQYfCpik0Dip05tAO6dO6lhRDkQmBkTuda0t5/Szfs02m+pz85JTh4R3syZHS4xvHolMm3NX6OAs6P1miHZ0RHbyhESsUlch/YfejSsyRGG5ilMwrv6V2fPPY0SdAWkjj2bqzlN9ax51nxZ9FIAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="contributors nix"
        title="contributors nix"
        src="/static/7b795373cf5ac13b3b0a695d11439b3c/fcda8/contributors-nix.png"
        srcset="/static/7b795373cf5ac13b3b0a695d11439b3c/12f09/contributors-nix.png 148w,
/static/7b795373cf5ac13b3b0a695d11439b3c/e4a3f/contributors-nix.png 295w,
/static/7b795373cf5ac13b3b0a695d11439b3c/fcda8/contributors-nix.png 590w,
/static/7b795373cf5ac13b3b0a695d11439b3c/efc66/contributors-nix.png 885w,
/static/7b795373cf5ac13b3b0a695d11439b3c/00d43/contributors-nix.png 1000w"
        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"
      />
    </span></p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA9hAAAPYQGoP6dpAAACH0lEQVQozz2S2W7TUBCG/ZRIXLRFlEUlBVREVW55BS65ANGCeAIkhAQSS0OapU3SgloTZ4/T1FsS+xzbSXw+ZFvtSKPR6Mx8Z/7RaEIKLMfCcVwsy2I+nxHHcea2bXFlz7Bth6vJJY7jMLHnBEKySCCOJPHgC8tIsFotWcQxmpQBzsRgcnnJ2DTxpjOUApWsyCwwIIm4sck3WMxJrBJJ5KIO1xDTMa6/QgiJJgKfrFV/BUELWq9hdp71qtSLd1C+gbIrqMUcVVyHoAulTRBDqBSQXo/p6VvCYJpPuEi7K9so5wRVfQr2ESpZopRCHT1E+R3U77soMUCldUEPVS6AGKHKj0AOoLTGMvTQpPBz4PFzlNtAneyhvFNovkT5bVTlSRYpF1DXgAz4KMtJY9CH0j0W0kWLQpEDa89y4PFuBlTVHdRMzyZOJSdHWxCOMon5hFs3H+TATZYpMJSCJAXWd8FrQv0FzM6gvgf+P6jtgOhA7TGe1ycoFkD2sxURmlDdziWX77OQHlosA6wY7J8PEJMqfrGAPa4xPdziT7fB388buNYFwY91fhkXND/dZjXvsDzcoDVuM/2+jj7Scb/eQoUumlrGtJyQ85P3DE0do/GR9lBHr3/AGLUx6gfowy6d+jta5oBBcx/D7DE5O6DZ72Ge7tPsdTEbb0giHy1VG0mRnUkYxtmpRGF+d9cxvs5lmL2HMszWFEuZ10t5c6b/AV3J19kyFdIzAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="contributors nixpkgs"
        title="contributors nixpkgs"
        src="/static/e39b99f3b639cf06b54b2b149af3d742/fcda8/contributors-nixpkgs.png"
        srcset="/static/e39b99f3b639cf06b54b2b149af3d742/12f09/contributors-nixpkgs.png 148w,
/static/e39b99f3b639cf06b54b2b149af3d742/e4a3f/contributors-nixpkgs.png 295w,
/static/e39b99f3b639cf06b54b2b149af3d742/fcda8/contributors-nixpkgs.png 590w,
/static/e39b99f3b639cf06b54b2b149af3d742/efc66/contributors-nixpkgs.png 885w,
/static/e39b99f3b639cf06b54b2b149af3d742/00d43/contributors-nixpkgs.png 1000w"
        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"
      />
    </span></p>
<p><span
      class="gatsby-resp-image-wrapper"
      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; "
    >
      <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 50.67567567567568%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA9hAAAPYQGoP6dpAAACCElEQVQoz22SyW4TQRCG/ZRIORAuXAIvgDjxHlyIOCAhOIZgIhGxKVIUJ/YQb/HY8cTjWTKeMfYsPfuHujMBIVFSdVVXV/1V+qtbdV2z3qwJ1gG+77PdbsnzXOlms8F1HRzXxbIdPM/Dtm3CMKQoCkSakWYZeZ6p/CzLaMkH39WxLZPl0lJFVVXdaV0jZRBk/AxS5ctIVcmz5NSJuFoLFZUxIQStTHUo+J/cA154CWe3ifLLqqRs4j8cwShI/uSrCcuyVI7qXtdKZce6KZLSXQk6t8k/d0s/oGP7DLc0tQ1gmqZIvQeUL1UDuggz7LikH6T0vAgnLvC8CadWgH74gHNjwrD3mvSXhe24RFFIS3IlCb0DrKgbnqScrzI67pZRENPfQi+o6R8+pGuMmH5+wtl8zLS9w9C6YRFDlaul5MRCIMp7IgLSLIbU5zLI0b6+YHR9xuWwTd+xGR89pjuXgHsKcH68x4lhMF15UKS05DzTteDYcO4Ae88YDj/QOX7OWP9Cp/2Ugf4d7WAHbXbO4OMjOtd9Jp92OZ0NmR3tcjIdoLd3QHi06jLH3GRcjr4R+AsWg/eYiwvG2jtMs8+k95ab5ZiZ9oa5PcfovkK3DExtn6ulwVLbZ2TOMbsvKcVGTghJFFFkOXEck+aQJCnyJyWJUDaOE4oS4ij+ayuIw4iyhigMqZpN/wYkzerShpFHFgAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="contributors nix dev"
        title="contributors nix dev"
        src="/static/a769e064077f50aca444cb8e0816ae41/fcda8/contributors-nix-dev.png"
        srcset="/static/a769e064077f50aca444cb8e0816ae41/12f09/contributors-nix-dev.png 148w,
/static/a769e064077f50aca444cb8e0816ae41/e4a3f/contributors-nix-dev.png 295w,
/static/a769e064077f50aca444cb8e0816ae41/fcda8/contributors-nix-dev.png 590w,
/static/a769e064077f50aca444cb8e0816ae41/efc66/contributors-nix-dev.png 885w,
/static/a769e064077f50aca444cb8e0816ae41/00d43/contributors-nix-dev.png 1000w"
        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"
      />
    </span></p>
<p>And now for the full story, which is long because we have made and learned so many things.</p>
<h1 id="retrospective" style="position:relative;"><a href="#retrospective" aria-label="retrospective 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>Retrospective</h1>
<p>In 2022, I started out with the hypothesis that there needs to be a comprehensive document that one could read cover to cover to understand the Nix ecosystem.
With Nix inventor Eelco Dolstra’s blessing and Tweag’s funding I started an effort to write what we called The Nix Book.</p>
<p>It turned out not to be that simple at all.
The ecosystem is too large, the spectrum of use cases too wide, and the details too messed up to just sit down and blurt out everything one knows, and then wait for others to fix the typos.
In fact, there have been <a href="https://github.com/NixOS/nix.dev/issues/454">multiple such attempts by different people</a>.</p>
<p>A more systematic approach was needed.
In addition to an initial <a href="https://github.com/NixOS/nix.dev/blob/master/maintainers/documentation-survey.md">literature survey</a>, I also ran a small <a href="https://discourse.nixos.org/t/usability-studies/21404">usability study</a> and evaluated the <a href="https://discourse.nixos.org/t/2022-nix-survey-results/18983">2022 community polls</a>.
During that time, <a href="https://discourse.nixos.org/t/documentation-team-flattening-the-learning-curve/20003">the documentation team was founded</a>, and became the forum for developing and refining goals for documentation in the Nix ecosystem, and working towards their achievement.</p>
<p>As of today, the documentation team meeting was held <a href="https://discourse.nixos.org/search?q=documentation%20team%20meeting%20notes%20%23%20%23dev%3Adocumentation%20in%3Atitle%20order%3Alatest_topic">more than a hundred times</a>.
I’m convinced: we’re delivering.
You can feel the change in pace and atmosphere, and the numbers support it.</p>
<p>Over the period covered by this report, we formulated multiple sets of goals.
They reflect our evolving understanding of the problem space and shifts in priorities.</p>
<p>Because all other attempts to make a comprehensive tractable review turned out to be impractical, I will first list the goals in the order they were published, for reference.
Then, based on the statements reworded and regrouped to avoid overlap, I will present results for each category separately.</p>
<details><summary>From the <a href="https://gist.github.com/fricklerhandwerk/78f8f77ff73aef848649f58a34575d0e#tldr">proposal for The Nix Book</a> April 2022</summary>
<ul>
<li>Write a book <em>actually explaining</em> Nix and its surrounding ecosystem at a high level of abstraction</li>
<li>Overhaul the Nix manual to make it a focused technical reference</li>
<li>Improve discoverability of existing learning material</li>
</ul>
</details>
<details><summary>From the <a href="https://discourse.nixos.org/t/documentation-team-flattening-the-learning-curve/20003">documentation team announcement</a> August 2022</summary>
<ul>
<li>Ease Nix learning, increase onboarding success and user retention</li>
<li>Improve organisation of Nix knowledge</li>
<li>Lead, guide, and support community efforts</li>
</ul>
</details>
<details><summary>From my NixCon talk <a href="https://www.youtube.com/watch?v=0nbwejLyF4Q&t=816s">Flattening the learning curve</a> October 2022</summary>
<ul>
<li>Reference documentation should be complete, correct, and easy to update</li>
<li>Guides should cover all major use cases</li>
<li>Tutorials should teach all key skills necessary to solve problems on one’s own, and require minimal effort from learners</li>
<li>Documentation should be versioned in lockstep with the code being documented</li>
<li>Each area should have a dedicated maintainer; someone who knows the whole thing by heart and knows where to put what</li>
</ul>
</details>
<details><summary>From the <a href="https://discourse.nixos.org/t/documentation-team-call-for-maintainers/25970#near-future-goals-2">call for maintainers</a> March 2023</summary>
<ul>
<li>Ease onboarding for users of Nix tools and contributors to their documentation:
<ul>
<li>Improve discoverability of relevant documentation for major use cases</li>
<li>Find dedicated owners for each part of the documentation</li>
<li>Markdown everywhere</li>
</ul>
</li>
<li>Create a coherent vision for documentation in the Nix ecosystem, and derive an implementation strategy and roadmap, guided by the <a href="https://diataxis.fr/">Diátaxis framework for technical documentation</a>:
<ol>
<li>Reference: Design an <a href="https://en.wikipedia.org/wiki/Information_architecture">information architecture</a> for reference documentation</li>
<li>Tutorials: Draft a complete onboarding and learning journey</li>
<li>Guides: Define a curation model for how-to guides</li>
<li>Explanation: Devise a plan for developing a book on the <a href="https://en.wikipedia.org/wiki/Intellectual_history">intellectual history</a> of the Nix ecosystem</li>
</ol>
</li>
</ul>
</details>
<details><summary>From the <a href="https://github.com/NixOS/nix.dev/blob/e1171bdb11ce2c95eb413555f028f38d9f30e78b/maintainers/documentation-project-2023.md">Learning Journey</a> project April 2023</summary>
<ul>
<li>Develop a curriculum draft</li>
<li>Categorise existing documentation materials</li>
<li>Break down tasks for the writing phase</li>
<li>Prepare a contributor workflow</li>
<li>Link or migrate existing documentation into a central location, as far as possible.</li>
<li>Prepare and publish a call for contributors for the writing phase</li>
</ul>
</details>
<h2 id="documentation-goals-for-the-nix-ecosystem" style="position:relative;"><a href="#documentation-goals-for-the-nix-ecosystem" aria-label="documentation goals for the nix ecosystem 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>Documentation goals for the Nix ecosystem</h2>
<p>I chose the following categories for evaluating documentation activities, because they emerge from the concrete goals recorded in the past.
I ordered them taking into account their relative importance and the sequence of events; but mostly all of this is merely a narrative device to condense a lot of information.</p>
<h3 id="improve-discoverability" style="position:relative;"><a href="#improve-discoverability" aria-label="improve discoverability 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>Improve discoverability</h3>
<p>Many of <a href="https://discourse.nixos.org/t/2022-10-22-documentation-team-meeting-notes-12-nixcon-edition/22689">the most obvious issues with Nix-related documentation</a> revolve around obstacles to finding relevant information.
We made notable progress on improving discoverability, by more clearly separating the roles of different information sources, reworking navigation paths, and adding cross-references.
But we also ran into deeper structural and technical problems, which we started to address from various angles.</p>
<details><summary>Details (17 items)</summary>
<ul>
<li>Over the course of months, the <a href="https://discourse.nixos.org/t/2022-11-10-documentation-team-meeting-notes-15/23617">team refined the role and purpose of nix.dev</a> as a central documentation resource.</li>
<li>Domen Kozar (<a href="https://github.com/domenkozar">@domenkozar</a>, <a href="https://www.cachix.org/">Cachix</a>), generously <a href="https://github.com/NixOS/foundation/issues/42">donated nix.dev to the NixOS Foundation</a>.
This means that <a href="https://github.com/NixOS/nix.dev/pull/575">nix.dev is now the official place for Nix documentation</a>, the single source of truth users can rely on.</li>
<li>Zach Mitchell (<a href="https://github.com/zmitchell">@zmitchell</a>) <a href="https://github.com/NixOS/nix.dev/pull/520">restructured nix.dev to follow Diátaxis categories</a>.</li>
<li>The documentation team <a href="https://discourse.nixos.org/t/2023-05-04-documentation-team-meeting-notes-45/27874#what-to-do-with-all-the-guides-2">tried to find an organisation principle for guides</a>, but without a final decision.
It’s still important to eventually address.</li>
<li>Yuki Langley (<a href="https://github.com/yukiisbored">@yukiisbored</a>) and Lorenzo Manacorda (<a href="https://github.com/asymmetric">@asymmetric</a>) <a href="https://github.com/NixOS/nix.dev/pulls?q=label%3A%22site%22+is%3Aclosed+merged%3A2022-10..2024-03+">took care of the outward appearance of nix.dev</a> and <a href="https://github.com/NixOS/nix.dev/pulls?q=label%3A%22contributor+experience%22+is%3Aclosed+merged%3A2022-10..2024-03+">reduced friction in the contribution process</a>.</li>
<li>The distinction between Nix, Nixpkgs, and NixOS is still often perceived as fuzzy, and we clarified that in the <a href="https://nix.dev/reference/glossary">nix.dev glossary</a>.</li>
<li>We also added to the side bar:
<ul>
<li>an <a href="https://nix.dev/contributing/documentation/">overview of communication platforms</a></li>
<li>links to <a href="https://github.com/nix-community/">community projects</a></li>
<li>a <a href="https://nix.dev/recommended-reading">collection of additional resources</a></li>
</ul>
</li>
<li>Naïm Favier (<a href="https://github.com/ncfavier">@ncfavier</a>) made it such that <a href="http://search.nixos.org">search.nixos.org</a> can now <a href="https://github.com/NixOS/nixos-search/pull/610">find programs in packages</a>.</li>
<li>Johannes Kirschbauer (<a href="https://github.com/hsjobeki">@hsjobeki</a>) started experimenting with a <a href="http://noogle.dev">search engine</a> for Nixpkgs library functions.</li>
<li>The documentation for <a href="https://nixos.org/manual/nix/stable/command-ref/nix-env"><code class="language-text">nix-env</code></a> and <a href="https://nixos.org/manual/nix/stable/command-ref/nix-store"><code class="language-text">nix-store</code></a> is now split into separate pages for sub-commands, each listing all options and environment variables that may apply.</li>
<li>I started an effort to better <a href="https://github.com/NixOS/nix/issues/7769">separate interface documentation from user guides</a> in the Nix manual.</li>
<li>John Ericson (<a href="https://github.com/Ericson2314">@Ericson2314</a>) restructured the manual chapters to roughly match Nix’s architecture: store, language, commands.</li>
<li>We <a href="https://discourse.nixos.org/t/2023-04-27-learning-journey-working-group-meeting-notes-6/27663#learning-journey-design-5">discussed</a> on <a href="https://discourse.nixos.org/t/2023-07-13-learning-journey-working-group-meeting-notes-17/30393">multiple</a> <a href="https://discourse.nixos.org/t/2023-08-31-documentation-team-meeting-notes-77/32497#presenting-third-party-projects-4">occasions</a> whether and when to talk about external rather than “official” tools in documentation.</li>
<li>Elton Vecchietti, Director of UX at Modus Create, ran three <a href="https://discourse.nixos.org/t/zurich-23-11-zhf-hackathon-and-ux-workshop-report/37848">user experience design workshops with Nix developers</a> to help us design an information architecture for the ecosystem and answer the question “Where to put what?”.</li>
<li>The question “What is an official Nix project?” culminated in the <a href="https://discourse.nixos.org/t/nixcon-governance-workshop/32705">NixCon 2023 governance workshop</a>.
My colleague Théophane Hufschmitt (<a href="https://github.com/thufschmitt">@thufschmitt</a>) later organised meetings of community team leads to <a href="https://github.com/NixOS/teams-collaboration/blob/main/meeting-notes/2023-11-10.md#listing-the-main-points-in-the-ecosystem-which-are-lacking-maintenance-and-deciding-on-the-way-to-fix-it-20-mins-all">act on the insights</a> and further <a href="https://github.com/NixOS/teams-collaboration/blob/main/meeting-notes/2024-03-07.md">formalise the organisational structure in the ecosystem</a>.</li>
<li>My colleague Silvan Mosberger (<a href="https://github.com/infinisil">@infinisil</a>) started an <a href="https://github.com/NixOS/nix.dev/issues/869">initiative to go back to having a single official domain</a> in order to avoid confusion.</li>
<li>Jörg Thalheim (<a href="https://github.com/Mic92">@Mic92</a>) and <a href="https://github.com/lassulus">@lassulus</a> worked on setting up an <a href="https://github.com/NixOS/foundation/issues/113">official NixOS user wiki</a>: <a href="https://wiki.nixos.org/">https://wiki.nixos.org</a></li>
</ul>
</details>
<h3 id="increase-coverage-of-reference-documentation" style="position:relative;"><a href="#increase-coverage-of-reference-documentation" aria-label="increase coverage of reference documentation 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>Increase coverage of reference documentation</h3>
<p>The Nix ecosystem is a large piece of software. The goal is to eventually capture all of its interfaces in reference documentation.</p>
<p>We mainly added much more information to the <a href="https://nixos.org/manual/nix/stable/">Nix reference manual</a>, but have not yet started <a href="https://github.com/NixOS/nix/issues/7259">fully documenting the Nix language syntax</a>.
There were many incremental improvements to <a href="https://nixos.org/manual/nix/stable/">Nixpkgs reference manual</a>, and work is ongoing to further increase coverage.
The people involved with the documentation team did not work on the NixOS manual, and the official <a href="https://wiki.nixos.org">NixOS Wiki</a> has only recently launched.</p>
<details><summary>Details (14 items)</summary>
<ul>
<li>John Ericson (<a href="https://github.com/Ericson2314">@Ericson2314</a>) and I finally added important pieces of documentation on the <a href="https://nixos.org/manual/nix/stable/store/">Nix store</a>, as well as an automatically generated listing of all <a href="https://hydra.nixos.org/build/251925005/download/1/manual/store/types/index.html">store types</a> and their parameters.</li>
<li>Robert Hensing (<a href="https://github.com/roberth">@roberth</a>, Hercules CI) described in plain language <a href="https://nixos.org/manual/nix/stable/command-ref/nix-store/realise#description">what happens when a derivation is realised</a>.</li>
<li>The listing of <a href="https://nixos.org/manual/nix/stable/command-ref/conf-file">Nix configuration options</a>, including the section on the various ways to set them, was reworked in detail.
For example, we added information on the <a href="https://hydra.nixos.org/build/251400891/download/1/manual/command-ref/conf-file.html#conf-system"><code class="language-text">system</code></a> and <a href="https://nixos.org/manual/nix/stable/command-ref/conf-file#conf-extra-platforms"><code class="language-text">extra-platforms</code></a> option; linked up everything that relates to <a href="https://hydra.nixos.org/build/251400891/download/1/manual/command-ref/conf-file.html#conf-pure-eval"><code class="language-text">pure-eval</code></a>; updated the format specification for <a href="https://nixos.org/manual/nix/stable/command-ref/conf-file#conf-builders"><code class="language-text">builders</code></a> as well as information on related options; and much more.</li>
<li>My colleague Alexander Bantyev (<a href="https://github.com/balsoft">@balsoft</a>) systematically documented files used by Nix and their formats, such as <a href="https://nixos.org/manual/nix/stable/command-ref/files/profiles">profiles</a>, <a href="https://nixos.org/manual/nix/stable/command-ref/files/channels">channels</a>, or <a href="https://nixos.org/manual/nix/stable/command-ref/files/default-nix-expression">the default Nix expression</a>.
While I would recommend against using these rather historical features, comprehensive documentation helps dealing with their quirks when managing older setups.</li>
<li>Nix maintainers created an automatically generated <a href="https://nixos.org/manual/nix/stable/contributing/experimental-features#currently-available-experimental-features">overview of experimental features</a> and <a href="https://nixos.org/manual/nix/stable/contributing/experimental-features#what-are-experimental-features">documented their development lifecycle</a>.</li>
<li>John Ericson (<a href="https://github.com/Ericson2314">@Ericson2314</a>) added <a href="https://github.com/NixOS/nix/pull/8330">automation for documenting built-in constants</a>.</li>
<li>I added much more information on <a href="https://nixos.org/manual/nix/stable/language/operators">operators in the Nix language</a></li>
<li>Thomas Bereknyei (<a href="https://github.com/tomberek">@tomberek</a>, <a href="https://flox.dev/">Flox</a>) exposed <a href="https://hydra.nixos.org/job/nix/master/internal-api-docs/latest/download-by-type/doc/internal-api-docs">doxygen API documentation</a> for the C++ interfaces.</li>
<li>I added dedicated pages to document <a href="https://nixos.org/manual/nix/stable/language/string-interpolation">string interpolation</a>, <a href="https://nixos.org/manual/nix/stable/language/import-from-derivation">Import From Derivation</a>, and the syntax that we decided to call <a href="https://nixos.org/manual/nix/stable/language/constructs/lookup-path">lookup paths</a></li>
<li>Alejandro Sánchez Medina (<a href="https://github.com/alejandrosame">@alejandrosame</a>) spent serious time on untangling the enormous <a href="https://nixos.org/manual/nixpkgs/stable/#python">section on Python infrastructure in Nixpkgs</a>.
Among other things, one result is a massive <a href="https://github.com/NixOS/nixpkgs/issues/240118">tracking issue</a> and a <a href="https://github.com/NixOS/nixpkgs/issues/250376">coverage overview</a>.</li>
<li>Robert (<a href="https://github.com/roberth">@roberth</a>) and Silvan (<a href="https://github.com/infinisil">@infinisil</a>) improved introductory documentation on <a href="https://github.com/NixOS/nixpkgs/pull/242318">library functions</a> for <a href="https://github.com/NixOS/nixpkgs/pull/248220">fixed-point computation</a>.</li>
<li>There were <a href="https://github.com/NixOS/nixpkgs/pull/240531">multiple</a> <a href="https://github.com/NixOS/nix.dev/pull/526">attempts</a> <a href="https://discourse.nixos.org/t/dawn-contributing-to-nix-modules-documentation/29685">made</a> to rework and expand module system documentation, but all of them stalled.
This is an important issue to eventually resolve.</li>
<li>Johannes Kirschbauer (<a href="https://github.com/hsjobeki">@hsjobeki</a>) has work in progress to improve automatic rendering of in-code documentation for Nix expressions, and especially Nixpkgs library functions.</li>
<li>A large part of Nix-related knowledge is not written down anywhere, and thus has many gaps.
Sponsored by Tweag, my colleague Silvan Mosberger (<a href="https://github.com/infinisil">@infinisil</a>) started systematically addressing that with his weekly video series, <a href="https://www.youtube.com/playlist?list=PLyzwHTVJlRc8yjlx4VR4LU5A5O44og9in">The Nix Hour</a>, where he explores a broad variety of topics around the ecosystem.
And it’s still ongoing after more than 65 episodes!</li>
</ul>
</details>
<h3 id="ensure-correctness-of-reference-documentation" style="position:relative;"><a href="#ensure-correctness-of-reference-documentation" aria-label="ensure correctness of reference documentation 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>Ensure correctness of reference documentation</h3>
<p>Merely having interface documentation is not enough, the information also has to be correct and up to date.</p>
<p>Contributors corrected countless errors and omissions in all documentation resources.
Work is ongoing to render all of the Nixpkgs library documentation from the source code.
There are technical obstacles to increase automatic presentation of interface documentation in the Nix reference manual.</p>
<p>The biggest impact on correctness would be made by automatically testing examples as part of continuous integration, but there was no progress on that apart from exchanging ideas.
We’ve been updating examples while working on other things, to make them more self-contained so they could be tested eventually.</p>
<details><summary>Details (8 items)</summary>
<ul>
<li>The Nix manual’s <a href="https://nixos.org/manual/nix/stable/glossary">glossary</a> was cleaned up, and many terms clarified.</li>
<li>Nix maintainers discussed <a href="https://github.com/NixOS/nix/issues/10373">automatic rendering of interfaces for built-in functions</a>, but an implementation requires more time than we can currently afford.</li>
<li>Bernardo Vecchia Stein (<a href="https://github.com/danielsidhion">@danielsidhion</a>) started <a href="https://github.com/NixOS/nixpkgs/issues/278769">a proof-reading pass of the Nixpkgs manual</a>, and an effort to make the style and formatting consistent.
He also reviewed <a href="https://discourse.nixos.org/t/2024-01-18-documentation-team-meeting-notes-105/38475#notes-1">numerous</a> <a href="https://discourse.nixos.org/t/2024-01-25-documentation-team-meeting-notes-106/38792#notes-1">pull</a> <a href="https://discourse.nixos.org/t/2024-02-08-documentation-team-meeting-notes-108/39492#notes-1">requests</a> with corrections and updates, and presented his own <a href="https://discourse.nixos.org/t/a-vision-for-the-manuals-and-other-documentation/42330">vision</a> and <a href="https://discourse.nixos.org/t/a-roadmap-for-the-documentation-ecosystem/42328">roadmap</a> for the Nix documentation ecosystem.</li>
<li><a href="http://nix.dev">nix.dev</a> now offers stable URLs to different <a href="https://nix.dev/reference/nix-manual">releases of the Nix reference manual</a>, and articles always link to a particular Nix version.
It still needs more automation to keep the manuals up to date, before we can fully switch over and redirect old links to the new setup.
Handling the Nixpkgs and NixOS manuals in the same way is planned for the future.</li>
<li>Henrik Karlsson (<a href="https://github.com/henrik-ch">@henrik-ch</a>) made <a href="https://github.com/NixOS/nix-pills/pulls?q=is%3Apr+is%3Aclosed+author%3Ahenrik-ch">various small updates to the Nix Pills</a>.</li>
<li>The team started <a href="https://github.com/NixOS/nixpkgs/issues/265058">systematically removing mentions of <code class="language-text">nix-env</code> from the Nixpkgs manual</a>, which revealed a few <a href="https://discourse.nixos.org/t/2023-09-21-documentation-team-meeting-notes-81/33490#hacking-2">related issues that were fixed on the way</a>.</li>
<li>Johannes Kirschbauer (<a href="https://github.com/hsjobeki">@hsjobeki</a>) drove to conclusion <a href="https://github.com/NixOS/rfcs/pull/145">RFC 145</a> that specifies the format of documentation comments in the Nix language.
He is working on <a href="https://github.com/NixOS/nixpkgs/pull/262987">migrating in-code documentation in Nixpkgs</a> to the new format.</li>
<li><a href="https://github.com/pennae">@pennae</a>, Lorenzo Manacorda (<a href="https://github.com/asymmetric">@asymmetric</a>), and Silvan Mosberger (<a href="https://github.com/infinisil">@infinisil</a>) kept improving <a href="https://github.com/nix-community/nixdoc"><code class="language-text">nixdoc</code></a>, the tool that renders Nix function documentation in the Nixpkgs manual</li>
</ul>
</details>
<h3 id="improve-the-contributor-experience" style="position:relative;"><a href="#improve-the-contributor-experience" aria-label="improve the contributor experience 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>Improve the contributor experience</h3>
<p>I claim that one of the most important use cases of any community-driven open source project is <em>contributing to that project</em>.
Much of our efforts revolved around easing contributions, in particular to documentation.
We added and improved contributor guides for each component of the Nix ecosystem, and added automation to further reduce friction.
Substantial parts of official documentation are now actively maintained, and the team holds regular meetings to review and merge pull requests.
As a consequence, in the second half of 2023 we got much more work done than ever, and more people than ever joined and stayed to help.</p>
<details><summary>Details (14 items)</summary>
<ul>
<li>The documentation team explored <a href="https://discourse.nixos.org/t/2022-10-06-documentation-team-meeting-notes-11/22286">strategies to improve the contribution process</a>.</li>
<li>We <a href="https://github.com/NixOS/nix.dev/issues/359">established an emoji convention</a> to gauge interest in issues and pull requests, to help with prioritisation.</li>
<li>Shahar “Dawn” Or (<a href="https://github.com/mightyiam">@mightyiam</a>) introduced a <a href="https://github.com/NixOS/nixpkgs/pull/243062">development mode</a> to the Nixpkgs and NixOS manuals, which allows live updates within seconds.</li>
<li>Thanks to a heroic effort by <a href="https://github.com/pennae">@pennae</a>, the <a href="https://github.com/NixOS/nixpkgs/pull/239636">Nixpkgs</a> and <a href="https://github.com/NixOS/nixpkgs/pull/237557">NixOS</a> finally got rid of XML as a documentation format, and thus completed the migration to Markdown that was going on since 2020.</li>
<li>Robert (<a href="https://github.com/roberth">@roberth</a>) enabled <a href="https://github.com/NixOS/nix/pull/7541">link checking for the Nix manual and added convenience for handling internal links</a>.</li>
<li>The chapter on contributing to Nix was updated and expanded:
<ul>
<li>My former colleague Yorick van Pelt (<a href="https://github.com/YorickvP">@YorickvP</a>) rewrote the page about <a href="https://nixos.org/manual/nix/stable/contributing/hacking">compiling Nix</a>.</li>
<li>John Ericson rewrote the page on <a href="https://nixos.org/manual/nix/stable/contributing/testing">running tests</a>.</li>
<li>I added a guide on <a href="https://nixos.org/manual/nix/stable/contributing/documentation">contributing documentation</a>.</li>
</ul>
</li>
<li>Luc Perkins (<a href="https://github.com/lucperkins">@lucperkins</a>, <a href="https://determinate.systems">Determinate Systems</a>) led the documentation team to sharpening its <a href="https://discourse.nixos.org/t/2022-11-03-documentation-team-meeting-notes-14/23050">role</a> and <a href="https://discourse.nixos.org/t/2022-12-01-documentation-team-meeting-notes-18/23623">structure</a>.
Zach Mitchell (<a href="https://github.com/zmitchell">@zmitchell</a>) helped <a href="https://discourse.nixos.org/t/2023-03-21-documentation-team-meeting-notes-34/26619#split-permissions-board-structure-licenses-6">further refine the team’s processes.</a> <a href="https://discourse.nixos.org/t/2023-03-30-documentation-team-meeting-notes-37/26966#notes-2">Every team member got triage access</a> to the main repositories, and we <a href="https://discourse.nixos.org/t/2023-03-30-documentation-team-meeting-notes-37/26966#backlog-grooming-4">set up a project board to keep an overview</a> of the situation.</li>
<li>We published a <a href="https://discourse.nixos.org/t/documentation-team-call-for-maintainers/25970">call for maintainers and near-future goals for documentation</a>, which brought in many new team members.
The group has since stabilised at 5-8 regular contributors.</li>
<li>We tried to clarify the <a href="https://discourse.nixos.org/t/2023-03-28-documentation-team-meeting-notes-36/26953#notes-2">roles</a> and <a href="https://discourse.nixos.org/t/2023-04-04-documentation-team-meeting-notes-38/27000#finding-maintainers-for-the-nixpkgs-and-nixos-manuals-6">responsibilities</a> of documentation owners to make it easier for new people to join, and currently exercise an smooth but informal process.</li>
<li>Lorenzo Manacorda (<a href="https://github.com/asymmetric">@asymmetric</a>) added a <a href="https://nix.dev/contributing/how-to-get-help">guide on how to get help</a> with one’s contributions.
Bob van der Linden (<a href="https://github.com/bobvanderlinden">@bobvanderlinden</a>) added a welcoming <a href="https://github.com/NixOS/nix/pull/7968">contributor guide in the Nix repository</a>.
My colleague Silvan Mosberger (<a href="https://github.com/infinisil">@infinisil</a>) <a href="https://github.com/NixOS/nixpkgs/pull/245243">restructured the Nixpkgs contribution guide</a>, and I wrote a <a href="https://nix.dev/contributing/documentation/writing-a-tutorial">guide on designing new tutorials</a>.
The contributor guides turned out to be beneficial on repeated occasions.</li>
<li>Lorenzo (<a href="https://github.com/asymmetric">@asymmetric</a>) also <a href="https://github.com/NixOS/nix.dev/pull/798">added a spell-checker</a> to nix.dev.</li>
<li>Although not immediately related to documentation, it’s worth noting that (<a href="https://github.com/infinisil">@infinisil</a>) spent a significant amount of time on designing and implementing <a href="https://github.com/NixOS/rfcs/pull/140">RFC140</a>, which prescribes and automatically enforces a simplified directory structure that greatly reduces the friction when adding packages to Nixpkgs.</li>
<li>Regular team meetings seem to have reduced the turnaround time for contributions for selected topics.
It would be interesting to collect and visualise data to validate that impression.</li>
<li>The documentation team <a href="https://discourse.nixos.org/t/2023-06-22-documentation-team-meeting-notes-57/29500#funding-opportunities-3">developed</a> a <a href="https://github.com/nix-community/projects/blob/main/proposals/stdenv.md#how-will-you-accomplish-the-work-please-provide-a-list-of-deliverables-with-associated-effort-and-cost-of-each-deliverable">project proposal for a Sovereign Tech Fund challenge</a>, as an attempt to accelerate development of infrastructure and contents for the Nixpkgs manual.
We did not win the grant to implement it, but the document keeps serving as a reference to guide ongoing efforts.</li>
<li>Over time, we learned how to make the review cycle more pleasant for everyone involved:
<a href="https://discourse.nixos.org/t/2023-09-14-documentation-team-meeting-notes-80/33033">Team meetings became hacking and editing sessions</a> in which we reviewed open pull requests.
This helped creating a common culture, built up momentum, and made for better opportunities to accomplish something worthwhile together.</li>
<li>The Nix manual and <a href="http://nix.dev">nix.dev</a> are actively maintained since October 2022.
Since 2024 we also have two people actively working on Nixpkgs documentation.
NixOS reference documentation is still effectively unmaintained.</li>
</ul>
</details>
<h3 id="teach-important-skills" style="position:relative;"><a href="#teach-important-skills" aria-label="teach important skills 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>Teach important skills</h3>
<p>Part of the documentation team’s mission is to provide more and better learning materials.
Thanks to many volunteers’ patience and endurance, the group produced roughly one tutorial every two months between end of 2022 and end of 2023.
This reportedly improved the learning experience — especially where we took the time to build a solid foundation in reference documentation.
That way, we have addressed a good chunk of fundamental use cases.</p>
<p>But there is still much ground to cover, even for what we consider essential skills and workflows in the Nix ecosystem.
Some articles could be a lot shorter.
Nonetheless, there are now more and much better tutorials than ever, all of which are actively maintained to incorporate feedback and keep up with best practices.</p>
<details><summary>Details (9 items)</summary>
<ul>
<li>I overhauled the <a href="https://nix.dev/tutorials/first-steps/ad-hoc-shell-environments">introductory <code class="language-text">nix-shell</code> tutorial</a>.</li>
<li>Solène Rapenne (<a href="https://github.com/rapenne-s">@rapenne-s</a>) completely reworked the one on <a href="https://nix.dev/tutorials/first-steps/reproducible-scripts">reproducible shebang scripts</a>.</li>
<li>Zach Mitchell (<a href="https://github.com/zmitchell">@zmitchell</a>) updated the <a href="https://nix.dev/tutorials/first-steps/declarative-shell">tutorial on declarative shell environments</a> and added a guide on <a href="https://nix.dev/guides/recipes/sharing-dependencies">sharing dependencies between packages and development shells</a>.</li>
<li>Olaf Hochherz (<a href="https://github.com/olafklingt">@olafklingt</a>) added a tutorial on <a href="https://nix.dev/tutorials/nixos/nixos-configuration-on-vm">running NixOS in virtual machines</a>, and greatly simplified the introduction to <a href="https://nix.dev/tutorials/nixos/integration-testing-using-virtual-machines">testing distributed systems with NixOS</a>.</li>
<li>Alexander Groleau (<a href="https://github.com/proofconstruction">@proofconstruction</a>) wrote a gentle primer to <a href="https://nix.dev/tutorials/packaging-existing-software">packaging existing software</a>.</li>
<li>Lorenzo Manacorda (<a href="https://github.com/asymmetric">@asymmetric</a>), Alexander (<a href="https://github.com/proofconstruction">@proofconstruction</a>), and I converted Silvan’s interactive <a href="https://nix.dev/tutorials/module-system/module-system">module system tutorial</a>, which was originally created for Summer of Nix 2021, to a written article.</li>
<li>Silvan Mosberger (<a href="https://github.com/infinisil">@infinisil</a>) wrote a tutorial for the <a href="https://nix.dev/tutorials/working-with-local-files">new file set library</a>.</li>
<li>Jeff Huffman (<a href="https://github.com/tejing1">@tejing1</a>) added a <a href="https://nix.dev/permalink/stub-ld">guide on how to use regular pre-built executables on NixOS</a>.</li>
<li>I ported over the <a href="https://nix.dev/tutorials/callpackage"><code class="language-text">callPackage</code> tutorial</a> originally written by Norbert Melzer (<a href="https://github.com/NobbZ">@NobbZ</a>) for Summer of Nix 2022.</li>
</ul>
</details>
<h3 id="explain-concepts" style="position:relative;"><a href="#explain-concepts" aria-label="explain concepts 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>Explain concepts</h3>
<p>The Nix ecosystem approaches many problems in a unique way, which requires introducing new terms and explaining why certain things are the way they are.
While that was my original motivation to start working on documentation, explanations have shown to be less of an important tool for serving immediate needs.
As a result, only a few things were worked on.</p>
<details><summary>Details (3 items)</summary>
<ul>
<li>The documentation team wrote an <a href="https://nix.dev/concepts/flakes">overview article on flakes</a>, which followed <a href="https://discourse.nixos.org/t/2023-06-26-documentation-team-meeting-notes-58/29666#httpsgithubcomnixosnixdevpull546-9">repeated</a> <a href="https://discourse.nixos.org/t/2023-07-17-documentation-team-meeting-notes-64/30653#notes-2">questions</a> <a href="https://discourse.nixos.org/t/2023-08-03-documentation-team-meeting-notes-69/31263">about</a> how we should deal with the subject.</li>
<li>Daniel Ramirez (<a href="https://github.com/wamirez">@wamirez</a>), together with NixOS 23.11 release manager Ryan Lahfa (<a href="https://github.com/raitobezarius">@raitobezarius</a>), added <a href="https://nix.dev/concepts/faq#which-channel-branch-should-i-use">recommendations for choosing releases</a>, answering a question that was indeed frequently asked.</li>
<li>I added a note on the <a href="https://nix.dev/concepts/faq#what-is-the-origin-of-the-name-nix">origins of the name Nix and the snowflake logo</a>.</li>
</ul>
</details>
<h3 id="reduce-onboarding-time" style="position:relative;"><a href="#reduce-onboarding-time" aria-label="reduce onboarding time 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>Reduce onboarding time</h3>
<p>The primary mission of the documentation team is to reduce onboarding time for beginners.
That plays into addressing the other challenges I have talked about, such as attracting more contributors.
We figured that a sequence of lessons focused on widely applicable skills would be an effective resource for users to become self-sufficient in the ecosystem.</p>
<p>To that end, there was a coordinated project to design what we called the Learning Journey, which was supported by many people through a fundraiser on Open Collective.
Despite some setbacks, we achieved the main goals and will continue with implementation by writing more tutorials for the curriculum.</p>
<details><summary>Details (7 items)</summary>
<ul>
<li>With support of the NixOS Foundation, the documentation team <a href="https://discourse.nixos.org/t/documentation-project-proposal-for-google-season-of-docs-2023/26648">applied for Google Season of Docs</a> with the goal to develop a Nix curriculum called the <a href="https://github.com/NixOS/nix.dev/blob/master/maintainers/documentation-project-2023.md">Learning Journey</a>.</li>
<li>We did not win the grant, but Ron Efroni (<a href="https://github.com/ronef">@ronef</a>, <a href="https://flox.dev">Flox</a>), in his capacity as NixOS Foundation board member, swiftly helped running a <a href="https://opencollective.com/nixos/projects/documentation-project">successful fundraiser</a> to get the project started regardless.
Within a few months, we collected more than 18 000 Euro from 57 supporters.</li>
<li>The team <a href="https://discourse.nixos.org/t/2023-04-27-documentation-team-meeting-notes-44/27694#documentation-project-3">organised participation</a>, <a href="https://discourse.nixos.org/t/2023-05-11-documentation-team-meeting-notes-47/28082">processed applications</a>, and were excited to have found the ideal candidates for the editorial lead and technical expert positions.</li>
<li>Unfortunately, after helping to kickstart the project, our editorial lead had to permanently leave for health reasons.
Instead of searching for a replacement, <a href="https://discourse.nixos.org/t/2023-06-15-documentation-team-meeting-notes-55/29161#project-priorities-2">we decided to shift focus</a>, and reallocated part of the budget to fund work on technical infrastructure.</li>
<li>A dedicated <a href="https://discourse.nixos.org/t/2023-03-14-documentation-team-meeting-notes-32/26351#learning-journey-working-group-6">Learning Journey volunteer working group</a> formed around the project, led by Zach Mitchell (<a href="https://github.com/zmitchell">@zmitchell</a>, <a href="https://flox.dev">Flox</a>).
Zach <a href="https://discourse.nixos.org/search?q=This%20Month%20in%20Nix%20Docs%20in%3Atitle%20order%3Alatest_topic">published monthly updates</a> during that time.
The remaining budget was used for expert reviews and support work, as originally intended, which helped keep up the momentum.</li>
<li>By the end of 2023, the Learning Journey group produced the key deliverable: a <a href="https://github.com/NixOS/nix.dev/issues/572">curriculum for learning Nix</a>.
This was accompanied by new and improved tutorials and guides, updated navigation structure on nix.dev, a well-tried contribution workflow, and a better overview of existing documentation resources.</li>
<li>We didn’t do another usability study as originally planned.
That will be more sensible after filling up more of the curriculum scaffold with more tutorials.</li>
</ul>
</details>
<h1 id="summary" style="position:relative;"><a href="#summary" aria-label="summary 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>Summary</h1>
<p>All in all, we can claim success:
The number of new and regular contributors to Nix documentation and <a href="http://nix.dev">nix.dev</a> has increased significantly every quarter.
The average turnaround time for documentation contributions seems to have decreased.
We’ve come quite far with smoothing out the first couple of days and weeks of using vanilla Nix.
Based on feedback and heuristics, such as <a href="https://discourse.nixos.org/c/learn/9">questions asked on Discourse</a>, I conclude that we have substantially reduced the time required for onboarding with Nix.
I’m convinced that discoverability of learning materials and reference documentation has also improved a lot.
Some of these results have yet to be validated with more evidence.</p>
<h1 id="thoughts-on-future-work" style="position:relative;"><a href="#thoughts-on-future-work" aria-label="thoughts on future work 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>Thoughts on future work</h1>
<p>I think that Nix documentation is on the right trajectory to eventually shake off its negative reputation and thus help unleash the power of Nix onto the world.
I suggest to continue following the goals discussed in this report, and in order to reach them more quickly:</p>
<ul>
<li>Finish things that have been started.</li>
<li>Put more emphasis on technical infrastructure and automation.</li>
<li>Focus on fewer topics, prioritise reference documentation.</li>
<li>Cultivate code ownership and empower more people to become maintainers.</li>
<li>Write more tutorials outlined in the Learning Journey.</li>
<li>Help establish a consistent information architecture across the Nix ecosystem.</li>
<li>Secure more funding to support volunteer efforts.</li>
<li>Systematically measure effectiveness of documentation efforts.</li>
</ul>
<h1 id="acknowledgements" style="position:relative;"><a href="#acknowledgements" aria-label="acknowledgements 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>Acknowledgements</h1>
<p>This whole endeavor has only been possible to sustain thanks to ongoing financial support.
I’d like to thank the leadership of <a href="https://antithesis.com/">Antithesis</a>, <a href="https://www.tweag.io/">Tweag</a>, <a href="https://flox.dev/">Flox</a>, and <a href="https://determinate.systems/">Determinate Systems</a> for funding me, Silvan, Zach, and Luc to lead and assist the documentation team’s efforts over the past 18 months.
Many thanks also to all our supporters on Open Collective for your confidence in our mission and your crucial role in speeding up progress.</p>
<p>An enormous amount of work has also been done by numerous volunteers, who did not just contribute to multiple articles and many small improvements, but were also involved in arriving at technical decisions and implementing them.
Some of them stay around to incorporate corrections to their texts and code.
Thank you so much for your indispensable contributions to our collective endeavor:</p>
<ul>
<li>Alejandro Sánchez Medina (<a href="https://github.com/alejandrosame">@alejandrosame</a>)</li>
<li>Lorenzo Manacorda (<a href="https://github.com/asymmetric">@asymmetric</a>)</li>
<li>Alexander Bantyev (<a href="https://github.com/balsoft">@balsoft</a>)</li>
<li>Bob van der Linden (<a href="https://github.com/bobvanderlinden">@bobvanderlinden</a>)</li>
<li>Bernardo Vecchia Stein (<a href="https://github.com/danielsidhion">@danielsidhion</a>)</li>
<li>Domen Kozar (<a href="https://github.com/domenkozar">@domenkozar</a>)</li>
<li>John Ericson (<a href="https://github.com/Ericson2314">@Ericson2314</a>)</li>
<li>Henrik Karlsson (<a href="https://github.com/henrik-ch">@henrik-ch</a>)</li>
<li>Johannes Kirschbauer (<a href="https://github.com/hsjobeki">@hsjobeki</a>)</li>
<li><a href="https://github.com/lassulus">@lassulus</a></li>
<li>Jörg Thalheim (<a href="https://github.com/Mic92">@Mic92</a>)</li>
<li>Shahar “Dawn” Or (<a href="https://github.com/mightyiam">@mightyiam</a>)</li>
<li>Naïm Favier (<a href="https://github.com/ncfavier">@ncfavier</a>)</li>
<li>Norbert Melzer (<a href="https://github.com/NobbZ">@NobbZ</a>)</li>
<li>Olaf Hochherz (<a href="https://github.com/olafklingt">@olafklingt</a>)</li>
<li><a href="https://github.com/pennae">@pennae</a></li>
<li>Alexander Groleau (<a href="https://github.com/proofconstruction">@proofconstruction</a>)</li>
<li>Ryan Lahfa (<a href="https://github.com/raitobezarius">@raitobezarius</a>)</li>
<li>Solène Rapenne (<a href="https://github.com/rapenne-s">@rapenne-s</a>)</li>
<li>Robert Hensing (<a href="https://github.com/roberth">@roberth</a>)</li>
<li>Ron Efroni (<a href="https://github.com/ronef">@ronef</a>)</li>
<li>Jeff Huffman (<a href="https://github.com/tejing1">@tejing1</a>)</li>
<li>Théophane Hufschmitt (<a href="https://github.com/thufschmitt">@thufschmitt</a>)</li>
<li>Thomas Bereknyei (<a href="https://github.com/tomberek">@tomberek</a>)</li>
<li>Daniel Ramirez (<a href="https://github.com/wamirez">@wamirez</a>)</li>
<li>Yorick van Pelt (<a href="https://github.com/YorickvP">@YorickvP</a>)</li>
<li>Yuki Langley (<a href="https://github.com/yukiisbored">@yukiisbored</a>)</li>
<li>Zach Mitchell (<a href="https://github.com/zmitchell">@zmitchell</a>)</li>
</ul>]]></description><link>https://tweag.io/blog/2024-05-02-right-words-right-place/</link><guid isPermaLink="false">https://tweag.io/blog/2024-05-02-right-words-right-place/</guid><pubDate>Thu, 02 May 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Re-implementing the Nix protocol in Rust]]></title><description><![CDATA[<p>The Nix daemon uses a custom binary protocol — the <em>nix daemon protocol</em> — to
communicate with just about everything. When you run <code class="language-text">nix build</code> on your
machine, the Nix binary opens up a Unix socket to the Nix daemon and talks
to it using the Nix protocol<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>. When you administer a Nix server remotely using
<code class="language-text">nix build --store ssh-ng://example.com [...]</code>, the Nix binary opens up an SSH
connection to a remote machine and tunnels the Nix protocol over SSH. When you
use remote builders to speed up your Nix builds, the local and remote Nix daemons speak
the Nix protocol to one another.</p>
<p>Despite its importance in the Nix world, the Nix protocol has no specification
or reference documentation. Besides the original implementation in the Nix
project itself, the <a href="https://github.com/haskell-nix/hnix-store">hnix-store</a> project contains a re-implementation of the
client end of the protocol. The <a href="https://codeberg.org/gorgon/gorgon">gorgon</a> project contains a partial re-implementation
of the protocol in Rust, but we didn’t know about it when we started. We do not know of any other implementations. (The
<a href="https://tvix.dev">Tvix</a> project created its own <a href="https://cs.tvl.fyi/depot@842d5ed3ee68f819742cbd3a81dac8f3410161dc/-/blob/tvix/store/docs/api.md">gRPC-based</a>
protocol instead of re-implementing a Nix-compatible one.)</p>
<p>So we re-implemented the Nix protocol, in Rust.
We started it mainly as a learning exercise, but we’re hoping to do some useful things along the way:</p>
<ul>
<li>Document and demystify the protocol. (That’s why we wrote this blog post! 👋)</li>
<li>Enable new kinds of debugging and observability. (We tested our implementation with a little Nix proxy
that transparently forwards the Nix protocol while also writing a log.)</li>
<li>Empower other third-party Nix clients and servers. (We wrote an
<a href="https://github.com/tweag/remote-execution-nix">experimental tool</a> that acts as a Nix remote builder, but proxies the
actual build over the Bazel Remote Execution protocol.)</li>
</ul>
<p>Unlike the <code class="language-text">hnix-store</code> re-implementation, we’ve implemented both ends of the protocol.
This was really helpful for testing, because it allowed our debugging proxy to verify
that a serialization/deserialization round-trip gave us something
byte-for-byte identical to the original. And thanks
to Rust’s procedural macros and the <a href="https://serde.rs"><code class="language-text">serde</code></a> crate, our implementation is
declarative, meaning that it also serves as concise documentation of the
protocol.</p>
<h2 id="structure-of-the-nix-protocol" style="position:relative;"><a href="#structure-of-the-nix-protocol" aria-label="structure of the nix protocol 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 the Nix protocol</h2>
<p>A Nix communication starts with the exchange of a few magic bytes, followed by
some version negotiation. Both the client and server maintain compatibility with
older versions of the protocol, and they always agree to speak the newest version
supported by both.</p>
<p>The main protocol loop is initiated by the client, which sends a “worker op” consisting
of an opcode and some data. The server gets to work on carrying out the requested operation.
While it does so, it enters a “stderr streaming” mode in which it sends a stream of
logging or tracing messages back to the client (which is how Nix’s progress messages
make their way to your terminal when you run a <code class="language-text">nix build</code>). The stream of stderr messages
is terminated by a special <code class="language-text">STDERR_LAST</code> message. After that, the server sends the operation’s
result back to the client (if there is one), and waits for the next worker op to come along.</p>
<h2 id="the-nix-wire-format" style="position:relative;"><a href="#the-nix-wire-format" aria-label="the nix wire format 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 Nix wire format</h2>
<p>Nix’s wire format starts out simple. It has two basic types:</p>
<ul>
<li>unsigned 64-bit integers, encoded in little-endian order; and</li>
<li>byte buffers, written as a length (a 64-bit integer) followed by the bytes in the buffer.
If the length of the buffer is not a multiple of 8, it is zero-padded to a multiple of 8 bytes.
Strings on the wire are just byte buffers, with no specific encoding.</li>
</ul>
<p>Compound types are built up in terms of these two pieces:</p>
<ul>
<li>Variable-length collections like lists, sets, or maps are represented by the number of elements they contain (as a 64-bit integer) followed by their contents.</li>
<li>Product types (i.e. structs) are represented by listing out their fields one-by-one.</li>
<li>Sum types (i.e. unions) are serialized with a tag followed by the contents.</li>
</ul>
<p>For example, a “valid path info” consists of a deriver (a byte buffer), a hash (a byte buffer),
a set of references (a sequence of byte buffers), a registration time (an integer), a nar size (an integer),
a boolean (represented as an integer in the protocol), a set of signatures (a sequence of byte buffers), and
finally a content address (a byte buffer). On the wire, it looks like:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">3c 00 00 00 00 00 00 00 2f 6e 69 78 2f 73 74 6f 72 65 ... 2e 64 72 76 00 00 00 00  &lt;- deriver
╰──── length (60) ────╯ ╰─── /nix/store/c3fh...-hello-2.12.1.drv ───╯ ╰ padding ╯

40 00 00 00 00 00 00 00 66 39 39 31 35 63 38 37 36 32 ... 30 33 38 32 39 30 38 66  &lt;- hash
╰──── length (64) ────╯ ╰───────────────────── sha256 hash ─────────────────────╯

02 00 00 00 00 00 00 00                                                            ╮
╰── # elements (2) ───╯                                                            │
                                                                                   │
   39 00 00 00 00 00 00 00 2f 6e 69 78 ... 2d 32 2e 33 38 2d 32 37 00 00 .. 00 00  │
   ╰──── length (57) ────╯ ╰── /nix/store/9y8p...glibc-2.38-27 ──╯ ╰─ padding ──╯  │ references
                                                                                   │
   38 00 00 00 00 00 00 00 2f 6e 69 78 ... 2d 68 65 6c 6c 6f 2d 32 2e 31 32 2e 31  │
   ╰──── length (56) ────╯ ╰───────── /nix/store/zhl0...hello-2.12.1 ───────────╯  ╯

1c db e8 65 00 00 00 00 f8 74 03 00 00 00 00 00 00 00 00 00 00 00 00 00            &lt;- numbers
╰ 2024-03-06 21:07:40 ╯ ╰─ 226552 (nar size) ─╯ ╰─────── false ───────╯

01 00 00 00 00 00 00 00                                                            ╮
╰── # elements (1) ───╯                                                            │
                                                                                   │ signatures
   6a 00 00 00 00 00 00 00 63 61 63 68 65 2e 6e 69 ... 51 3d 3d 00 00 00 00 00 00  │
   ╰──── length (106) ───╯ ╰─── cache.nixos.org-1:a7...oBQ== ────╯ ╰─ padding ──╯  ╯

00 00 00 00 00 00 00 00                                                            &lt;- content address
╰──── length (0) ─────╯</code></pre></div>
<p>This wire format is not self-describing: in order to read it, you need
to know in advance which data-type you’re expecting. If you get confused or misaligned somehow,
you’ll end up reading complete garbage. In my experience, this usually leads to
reading a “length” field that isn’t actually a length, followed by an attempt to allocate
exabytes of memory. For example, suppose we were trying to read the “valid path info” written
above, but we were expecting it to be a “valid path info with path,” which is the same as a
valid path info except that it has an extra path at the beginning. We’d misinterpret
<code class="language-text">/nix/store/c3f-...-hello-2.12.1.drv</code> as the path, we’d misinterpret the hash as the
deriver, we’d misinterpret the number of references (2) as the number of bytes in
the hash, and we’d misinterpret the length of the first reference as the hash’s data.
Finally, we’d interpret <code class="language-text">/nix/sto</code> as a 64-bit integer and promptly crash as we
allocate space for more than <span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>8</mn><mo>×</mo><msup><mn>10</mn><mn>18</mn></msup></mrow><annotation encoding="application/x-tex">8 \times 10^{18}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;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 mtight">18</span></span></span></span></span></span></span></span></span></span></span></span></span> references.</p>
<p>There’s one important exception to the main wire format: “framed data”.
Some worker ops need to transfer source trees or build artifacts that are too
large to comfortably fit in memory; these large chunks of data need to be
handled differently than the rest of the protocol. Specifically, they’re transmitted
as a sequence of length-delimited byte buffers, the idea being that you can read one
buffer at a time, and stream it back out or write it to disk before reading the next
one.
Two features make this framed data unusual:
the sequence of buffers are terminated by an empty buffer instead of being length-delimited
like most of the protocol, and the individual buffers are not padded out to a multiple of 8 bytes.</p>
<h2 id="serde" style="position:relative;"><a href="#serde" aria-label="serde 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>Serde</h2>
<p>Serde is the de-facto standard for serialization and deserialization in Rust. It
defines an interface between serialization formats (like JSON, or the Nix wire
protocol) on the one hand and serializable data types on the other. This divides our
work into two parts: first, we implement the serialization format, by specifying the
correspondence between Serde’s data model and the Nix wire format we described above.
Then we describe how the Nix protocol’s messages map to the Serde data model.</p>
<p>The best part about using Serde for this task is that the second step becomes
straightforward and completely declarative. For example, the <code class="language-text">AddToStore</code> worker op
is implemented like</p>
<div class="gatsby-highlight" data-language="rust"><pre class="language-rust"><code class="language-rust"><span class="token attribute attr-name">#[derive(serde::Deserialize, serde::Serialize)]</span>
<span class="token keyword">pub</span> <span class="token keyword">struct</span> <span class="token type-definition class-name">AddToStore</span> <span class="token punctuation">{</span>
    <span class="token keyword">pub</span> name<span class="token punctuation">:</span> <span class="token class-name">StorePath</span><span class="token punctuation">,</span>
    <span class="token keyword">pub</span> cam_str<span class="token punctuation">:</span> <span class="token class-name">StorePath</span><span class="token punctuation">,</span>
    <span class="token keyword">pub</span> refs<span class="token punctuation">:</span> <span class="token class-name">StorePathSet</span><span class="token punctuation">,</span>
    <span class="token keyword">pub</span> repair<span class="token punctuation">:</span> <span class="token keyword">bool</span><span class="token punctuation">,</span>
    <span class="token keyword">pub</span> data<span class="token punctuation">:</span> <span class="token class-name">FramedData</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span></code></pre></div>
<p>These few lines handle both serialization and deserialization of the <code class="language-text">AddToStore</code> worker op,
while ensuring that they remain in-sync.</p>
<h2 id="mismatches-with-the-serde-data-model" style="position:relative;"><a href="#mismatches-with-the-serde-data-model" aria-label="mismatches with the serde data model 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>Mismatches with the Serde data model</h2>
<p>While Serde gives us some useful tools and shortcuts, it isn’t a perfect fit for our case.
For a start, we don’t benefit much from one of Serde’s most important benefits: the decoupling
between serialization formats and serializable data types. We’re interested in a specific
serialization format (the Nix wire format) <em>and</em> a specific collection of data types (the ones
used in the Nix protocol); we don’t gain much by being able to, say,
serialize the Nix protocol to JSON.</p>
<p>The main disadvantage of using Serde is that we need to match the Nix protocol to Serde’s data
model. Most things match fairly well; Serde has native support for integers, byte buffers,
sequences, and structs. But there were a few mismatches that we had to work around:</p>
<ul>
<li>Different kinds of sequences: Serde has native support for sequences, and it can support
sequences that are either length-delimited or not. However, Serde does not make it easy to support
length-delimited and non-length-delimited sequences in the same serialization format. And
although most sequences in the Nix format are length-delimited, the sequence of chunks in a
<em>framed source</em> are not. We hacked around this restriction by treating a framed source not
as a sequence but as a tuple with <span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mn>64</mn></msup></mrow><annotation encoding="application/x-tex">2^{64}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;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 mtight">64</span></span></span></span></span></span></span></span></span></span></span></span></span> elements, relying on the fact that Serde doesn’t care
if you terminate a tuple early.</li>
<li>The Serde data model is larger than the Nix protocol needs; for example, it supports floating
point numbers, and integers of different sizes and signedness. Our Serde de/serializer raises
an error at runtime if it encounters any of these data types. Our Nix protocol implementation
avoids these forbidden data types, but the Serde abstraction between the serializer and the
data types means that any mistakes will not be caught at compile time.</li>
<li>Sum types tagged with integers: Serde has native support for tagged unions, but it assumes
that they’re tagged with either the variant name (i.e. a string) or the variant’s index within
a list of all possible variants. The Nix protocol uses numeric tags, but we can’t just
use the variant’s index: we need to specify specific tags for specific variants, to match the
ones used by Nix. We solved this by using our own derive macro for tagged unions. Instead of
using Serde’s native unions, we map a union to a Serde tuple consisting of a tag followed by
its payload.</li>
</ul>
<p>But with these mismatches resolved, our final definition of the Nix protocol is fully declarative
and pretty straightforward:</p>
<div class="gatsby-highlight" data-language="rust"><pre class="language-rust"><code class="language-rust"><span class="token attribute attr-name">#[derive(TaggedSerde)]</span>
<span class="token comment">//       ^^ our custom procedural macro for unions tagged with integers</span>
<span class="token keyword">pub</span> <span class="token keyword">enum</span> <span class="token type-definition class-name">WorkerOp</span> <span class="token punctuation">{</span>
    <span class="token attribute attr-name">#[tagged_serde = 1]</span>
    <span class="token comment">//              ^^ this op has opcode 1</span>
    <span class="token class-name">IsValidPath</span><span class="token punctuation">(</span><span class="token class-name">StorePath</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token keyword">bool</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token comment">//             ^^            ^^ the op's response type</span>
    <span class="token comment">//             || the op's payload</span>
    <span class="token attribute attr-name">#[tagged_serde = 6]</span>
    <span class="token class-name">QueryReferrers</span><span class="token punctuation">(</span><span class="token class-name">StorePath</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token class-name">StorePathSet</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token attribute attr-name">#[tagged_serde = 7]</span>
    <span class="token class-name">AddToStore</span><span class="token punctuation">(</span><span class="token class-name">AddToStore</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token class-name">ValidPathInfoWithPath</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token attribute attr-name">#[tagged_serde = 9]</span>
    <span class="token class-name">BuildPaths</span><span class="token punctuation">(</span><span class="token class-name">BuildPaths</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token keyword">u64</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token attribute attr-name">#[tagged_serde = 10]</span>
    <span class="token class-name">EnsurePath</span><span class="token punctuation">(</span><span class="token class-name">StorePath</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token keyword">u64</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token attribute attr-name">#[tagged_serde = 11]</span>
    <span class="token class-name">AddTempRoot</span><span class="token punctuation">(</span><span class="token class-name">StorePath</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token keyword">u64</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token attribute attr-name">#[tagged_serde = 14]</span>
    <span class="token class-name">FindRoots</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Resp</span><span class="token operator">&lt;</span><span class="token class-name">FindRootsResponse</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token comment">// ... another dozen or so ops</span>
<span class="token punctuation">}</span></code></pre></div>
<h2 id="next-steps" style="position:relative;"><a href="#next-steps" aria-label="next steps 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>Next steps</h2>
<p>Our implementation is still a work in progress; most notably the API needs a lot
of polish. It also only supports protocol version 34, meaning it cannot interact
with old Nix implementations (before 2.8.0, which was released in 2022) and will lack support for features
introduced in newer versions of the protocol.</p>
<p>Since in its current state our Nix protocol implementation can
already do some useful things, we’ve made the crate available on <a href="https://crates.io/crates/nix-remote">crates.io</a>.
If you have a use-case that isn’t supported yet, let us know! We’re still trying to figure
out what can be done with this.</p>
<p>In the meantime, now that we can handle the Nix remote protocol itself we’ve shifted our
experimental hacking over to <a href="https://github.com/tweag/remote-execution-nix">integrating</a> with Bazel remote execution.
We’re writing a program that presents itself as a Nix remote builder, but instead of executing
the builds itself it sends them via the <a href="https://github.com/bazelbuild/remote-apis">Bazel Remote Execution API</a> to some other build
infrastructure. And then when the build is done, our program sends it back to the requester as though
it were just a normal Nix remote builder.</p>
<p>But that’s just our plan, and we think there must be more applications of this. If you could speak
the Nix remote protocol, what would you do with it?</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">Unless you’re running as a user that has read/write access to the nix
store, in which case <code class="language-text">nix build</code> will just modify the store directly instead
of talking to the Nix daemon.<a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2024-04-25-nix-protocol-in-rust/</link><guid isPermaLink="false">https://tweag.io/blog/2024-04-25-nix-protocol-in-rust/</guid><pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Cloud Native Computing in 2024—feeling the pulse at Kubecon]]></title><description><![CDATA[<p>Last year, at the end of winter, we wrote our <a href="https://www.tweag.io/blog/2023-02-23-infrastructure-pulse-2023/">impressions of the trends and
evolution of infrastructure and configuration management</a>
after attending <a href="https://fosdem.org/2023/">FOSDEM</a> and <a href="https://cfgmgmtcamp.eu/ghent2023/">CfgMgmtCamp</a>. We’re
at it again, but with <a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/">Kubecon</a> this year, the biggest cloud native
computing conference.</p>
<p>If you’ve never heard of cloud native computing before, it has <a href="https://www.redhat.com/en/topics/cloud-native-apps">a</a> <a href="https://en.wikipedia.org/wiki/Cloud-native_computing">number</a> <a href="https://youtu.be/p-88GN1WVs8?feature=shared">of</a> <a href="https://github.com/cncf/foundation/blob/main/charter.md">definitions</a> online, but the simplest one is that it’s mostly about Kubernetes.</p>
<p>Kubecon is a huge event with thousands of attendees. The conference spanned
several levels of the main convention center in Paris, with a myriad of
conference rooms and a whole floor for sponsor booths. FOSDEM already felt huge
compared to academic conferences, but Kubecon is even bigger.</p>
<p>Although the program was filled with appealing talks, we ended up spending most
of our time chatting with people and visiting booths, something you can’t do as easily online.</p>
<h2 id="nix-for-the-win" style="position:relative;"><a href="#nix-for-the-win" aria-label="nix for the win 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 for the win</h2>
<p>The very first morning, as we were walking around and waiting for the caffeine to kick in, we immediately spotted a Nix logo on someone’s sweatshirt. No better way to start the day than to meet with fellow Nix users!</p>
<p>And Nix was in general a great entry point for conversations at this year’s Kubecon. We expected it to still be an outsider at an event about container-driven technology, but the problems that “containers as a default packaging unit” can’t solve were so present that Nix’s value proposition is attuned to what everyone had on their minds anyway. In other words, Nix is now known enough as to serve as a conversation starter: the company might not use it, but many people have heard about it and were very interested to hear insights from big contributors like Tweag, the Modus Create OSPO.</p>
<h2 id="cybersecurity" style="position:relative;"><a href="#cybersecurity" aria-label="cybersecurity 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>Cybersecurity</h2>
<p>Many security products were represented. Securing cloud-native applications is indeed a difficult matter, as their many layers and components expose a large attack surface.</p>
<p>For one, the schedule included a security track, with a fair share of talks
being about <a href="https://en.wikipedia.org/wiki/Software_supply_chain">SBOMs</a> tying back into the problem of containers that ship an opaque
content without inventory. Nix, as a solution to this problem, was a great
conversation starter here as well, especially for fellow Nixer Matthias, who
can talk for hours about how Nix is the best (and maybe only) technology
for automatically deriving complete SBOMs of a piece of software, in a
trustworthy manner. Our own NLNet-funded project <a href="https://github.com/tweag/genealogos/">genealogos</a>, which does
exactly that, is recently getting a lot of interest.</p>
<p>Besides the application code and what goes in it, another focus was avoiding misconfiguration of the cloud infrastructure layer, of the Kubernetes cluster, and anything else going into container images. Many companies propose SaaS combinations of static linters scanning the configuration files directly with
various policy rules, heuristics and dynamic monitoring of secure cloud native
applications. Our configuration language <a href="https://github.com/tweag/nickel/">Nickel</a> was very relevant here: one of its <em>raisons d’être</em> is to provide efficient tools (types, contracts and a powerful LSP) to detect and fix misconfigurations as early as possible. We had cool conversations around writing custom security policies as Nickel contracts with the new LSP background contract checking (introduced in 1.5) reporting non-compliance live in the editor — in that light, contracts are basically a lightweight way to program an LSP.</p>
<h2 id="internal-developer-platforms-idps" style="position:relative;"><a href="#internal-developer-platforms-idps" aria-label="internal developer platforms idps 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>Internal Developer Platforms (IDPs)</h2>
<p>IDPs were a hot topic at Kubecon. Tweag’s mission, as the OSPO of a leading software development consultancy, is to improve developer experience across the software lifecycle, which makes IDPs a natural topic for us.</p>
<p>An IDP is a platform - usually a web interface in practice - which glues
several developer tools and services together and acts as the central entry
point for most developer workflows. It’s centered around the idea of
<em>self-service</em>, not unlike the console of cloud providers, but configurable for
your exact use case and open to integrate tools across ecosystem boundaries.
We already emphasized this emerging new abstraction in <a href="https://www.tweag.io/blog/2023-02-23-infrastructure-pulse-2023/">last year’s
post</a>. Example use cases are routinely deploying new
infrastructure with just a few clicks, rather than requiring to sync and
to send messages back-and-forth to the DevOps team. IDPs don’t replace other
tools but offer a unified interface, usually with customized presets, to
interact with repositories, internal data and infrastructure.</p>
<p>Backstage, an open-source IDP developed by Spotify, had its own sub-conference
at Kubecon. Several products are built on top of it as well: it’s not really a
ready-to-use solution but rather the engine to build a custom IDP for your
company, which leaves room for turnkey offers. We feel that such integrated,
centralized and simple-to-use services may become a standard in the future:
think of how much GitHub (or an equivalent) is a central part of our modern
workflow, but also of how many things it frustratingly <em>can’t</em> do (in
particular infrastructure).</p>
<h2 id="cloud--ai" style="position:relative;"><a href="#cloud--ai" aria-label="cloud  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>Cloud &#x26; AI</h2>
<p>Many Kubernetes-based MLOps companies propose services to make it easy to
deploy and manage scalable AI models in the cloud.</p>
<p>In the other direction, with the advent of generative AI, there was no doubt
that we would see AI-based products to ease the automation of infrastructure-related tasks. We attended a demo of a multi-agent system which integrates with e.g. Slack, where you can ask a bot to perform end-to-end tasks (which includes interacting with several systems, like deploying something to the cloud, editing a Jira ticket and pushing something to a GitHub repo) or ask high-level questions, such as “which AWS users don’t have MFA enabled”.</p>
<p>It’s hard to tell from a demo how solid this would be in a real production system. I
also don’t know if I would trust an AI agent to perform tasks without proper
validation from a human (though there is a mode where confirmation is required
before applying changes). There are also security concerns around having those
agents run somewhere with write access to your infrastructure.</p>
<p>Putting those important questions aside, the demo was still quite impressive. It
makes sense to automate those small boring tasks which usually require you to
manually interact with several different platforms and are often quite
mechanical indeed.</p>
<h2 id="ospo" style="position:relative;"><a href="#ospo" aria-label="ospo 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>OSPO</h2>
<p>We attended a Birds-of-a-Feather session on Open Source Program Offices (OSPO).
While the small number of participants was a bit disappointing (between 10
and 15, compared to the size of the conference), the small group discussions
were still engrossing, and we were pleased to meet people from other OSPOs as
well as engineers wanting to push for an OSPO in their own company.</p>
<p>The generally small size OSPOs (including from very large and influential tech
companies) and their low maturity from a strategic point of view was surprising
to us. Many OSPOs seem to be stuck in tactical concerns, managing license and
IP issues that can occur when developers open up company-owned repos. In such a
situation, all OSPO members are fully occupied by the large number of requests
they get. But the most interesting questions: how to share benefits and costs
by working efficiently with open source communities, how to provide strategic
guidance and support, and how to gain visibility in communities of interest were
only addressed by few. A general concern seemed to be generally a lack of
understanding by upper management about the real strategic power that an OSPO
can provide. From that perspective, Tweag, although a pink unicorn as a
consulting OSPO, is quite far on the maturity curve with concrete strategical
and technical firepower through <a href="https://tweag.io/groups/">technical groups</a>,
and our <a href="https://github.com/tweag/">open-source portfolio</a> (plus the projects
that we contribute to but aren’t ours).</p>
<h2 id="concluding-words" style="position:relative;"><a href="#concluding-words" aria-label="concluding words 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>Concluding words</h2>
<p>Kubecon was a great experience, and we’re looking forward to the next one. We are excited about the advent of Internal Developer Platforms and the concept of self-serving infrastructure, which are important aspects of developer experience.</p>
<p>On the technological side, the cloud-native world seems to be dominated by
Kubernetes with Helm charts and YAML, and Docker, while the technologies we
believe in and are actively developing are still outsiders in the space
(of course they aren’t a full replacement for what currently exists, but
they could fill many gaps). I’m thinking in particular about Nix (and more
generally about declarative, hermetic and reproducible builds and deployments) and
Nickel (better configuration languages and management tools). But, conversation
after conversation, conference after conference, we’re seeing more and more
interests in new paradigms, sometimes because those technologies are best
equipped - by far - to solve problems that are on everyone’s radar (e.g. software traceability through SBOMs with Nix) thanks to their different approach.</p>]]></description><link>https://tweag.io/blog/2024-04-18-cloud-native-pulse-2024/</link><guid isPermaLink="false">https://tweag.io/blog/2024-04-18-cloud-native-pulse-2024/</guid><pubDate>Thu, 18 Apr 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Software Identifiers through the eyes of Nix]]></title><description><![CDATA[<p><em>This is an answer to a recent request for comments issued by CISA, the United
States “Cybersecurity and Infrastructure Security Agency”, about software
identifiers. Unfortunately I wasn’t aware of this request for comments early
enough and thus too late to comment officially. But CISA encouraged me to
publish the answer as a separate blog post. The Guix team similarly published
<a href="https://guix.gnu.org/en/blog/2024/identifying-software/">their own answer</a></em></p>
<hr>
<p>Dear CISA team,</p>
<p>I appreciate your effort to <a href="https://www.regulations.gov/document/CISA-2023-0026-0001">gather comments</a> about your recently released <a href="https://www.cisa.gov/resources-tools/resources/software-identification-ecosystem-option-analysis">“Software Identification Ecosystem Option Analysis” white paper</a>. As you say in the Executive Summary, “Organizations of all sizes must track what software they own and operate to perform user support, inventory administration, and vulnerability management”. I would go further and claim that for <em>any</em> software system that will be modified and reassembled — which is basically always — precise knowledge and control over the components and how they should be put together is crucial. Precise naming, identifiers, are the basis of that. In that light, I would like to bring to your attention another noteworthy technology that hasn’t been mentioned in this study, <a href="https://www.nixos.org">Nix</a> and its sister project <a href="https://guix.gnu.org/">Guix</a>, that achieves exactly this.</p>
<p>Nix is a powerful package manager, offering a very distinctive approach to deploying software. It achieves very high levels of reproducibility and provenance tracking of software artifacts by design, utilizing a functional and declarative language to describe software builds and their dependencies.</p>
<p>In fact, the levels of reproducibility it achieves are so high that Nix can robustly rely on an “input-addressed” storage, an identification model that names software artifacts by hashing everything required to build them, as opposed to hashing their content once assembled. This unique input-addressed approach is very powerful because it allows computing the identifier of a software asset without assembling it.</p>
<p>“Software artifacts” in Nix can be anything from sources and data assets to executable binary packages. And, importantly, “everything required to build” is not limited to source code and data assets, as in most intrinsic identification models, but comprises all commands, configuration, and recursively identified dependencies that are required to assemble the asset in a very strict sandbox. This controlled environment ensures that the description and the identifier of a software asset (called “package closure” in Nix) are complete, capturing all ingredients that went into the final output.</p>
<p>The reproducibility of core packages of the Nix distribution NixOS is <a href="https://r13y.com/">very high, automatically tested</a>, and can in principle be used for the independent, and decentralized verification of the content of software artifacts, as demonstrated by <a href="https://github.com/nix-community/trustix">this implementation</a> developed within the European Commission’s <a href="https://nlnet.nl/PET/">Next Generation Internet program</a>.</p>
<p>In addition, these advantageous properties allowed the Nix community to construct <a href="https://github.com/NixOS/nixpkgs">Nixpkgs</a>, the <a href="https://repology.org/repositories/graphs">largest, and most up to date</a>, open software library available. As required by the Nix model, this enormous repository of software assets comprises not only a description of the components that have been used to assemble them but also everything else necessary to produce the final packages, including, besides build instructions and configuration, <a href="https://www.tweag.io/blog/2019-02-06-mapping-open-source/">a global dependency graph</a> of all these assets. And, besides the guarantees that Nix furnishes by design for completeness and reproducibility, the packages go <a href="https://github.com/NixOS/nixpkgs/tree/master/nixos/tests">through automatic tests</a> executed by <a href="https://status.nixos.org/">an associated CI</a>, and the hands of tens of thousands of regular users.</p>
<p>The screenshot below shows the <a href="https://hydra.nixos.org/build/245968603#tabs-details">CI build example</a> of the open source computer game <a href="https://daid.github.io/EmptyEpsilon/">EmptyEpsilon</a>. Omitting some details in the screenshot below, <code class="language-text">rja769qkxhiha7mbhq5bjmkjd0d5l1v0-empty-epsilon-2023.06.17</code> in the <em>Derivation store path</em> field, is the unique identifier of the particular version of this software package realized in the context of all the dependencies and configuration that are defined in a specific version (commit) of the Nixpkgs library that this CI build is attached to. “release.nix” is the entrypoint into the Nixpkgs library of software assets where EmptyEpsilon is defined using the Nix language; the mentioned .drv file contains an intermediate, raw build recipe that was generated from the Nix expressions. Software assets can come with attached metadata such as license information or short descriptions, and some data such as the closure size comprising the software and all its dependencies (build- and run-time) can easily be computed. More details on this can be found <a href="https://nixos.org/guides/nix-pills/nix-store-paths#id1458">here</a> or <a href="https://github.com/NixOS/nix/blob/master/doc/manual/src/protocols/store-path.md">here</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/f32db8e8ace0b766b2af1262de6d9827/3a737/hydra-emptyepsilon.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 52.02702702702703%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABp0lEQVQoz3WS266iQBRE/QwBpQVa7oIINBe5ZdQXnScTTXwy8f//oSZ7n3HinOQ8VBoasrqqds+KokDf9xjHCV3XQSkFM+6gh3to0QA9aKB7BRZeDj2eoEcdjHAPI2hghA3MlQ1hmjCFgBACM9u2IaXEer2G67oIwxCWI1m2dGE7azjyS8KyYQrrmwRMAv7VzHEchlmWxWuWZXAZ/nWAlA4/07fVagUhTHbyk2bTNOHxeHBUTdM4NlVA+9vtFoZhYLlcssyPaD8CT6cTns8nDocDFosFg2jveDxiGAakaYo4jnndbDagigj8BnzG5cjk6HK5sEOKVdc1ur7HLs9RVRWy3Y5BpCiK2CkdTPoOZofk4n6/I4piaNocqRqQ1L8g0xYyqSHTPbyshef73Knv+zw4Wqn3TzA7pGi32w1t23Jnqiw5alMr1JVCWZZo6prd5nnOoj26blSD53k8rH8Oqa/X64Xz+YwkSRj8myqYLijbEUWRQ6mKAXQg3QIS/UvvBPwvMnV2vV4RBAHm8zk7GccBCUXPSkRhyEMhEYSividPcd/Tf0f+AymbLW4qr6TOAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="CI build details of EmptyEpsilon"
        title="CI build details of EmptyEpsilon"
        src="/static/f32db8e8ace0b766b2af1262de6d9827/fcda8/hydra-emptyepsilon.png"
        srcset="/static/f32db8e8ace0b766b2af1262de6d9827/12f09/hydra-emptyepsilon.png 148w,
/static/f32db8e8ace0b766b2af1262de6d9827/e4a3f/hydra-emptyepsilon.png 295w,
/static/f32db8e8ace0b766b2af1262de6d9827/fcda8/hydra-emptyepsilon.png 590w,
/static/f32db8e8ace0b766b2af1262de6d9827/efc66/hydra-emptyepsilon.png 885w,
/static/f32db8e8ace0b766b2af1262de6d9827/3a737/hydra-emptyepsilon.png 897w"
        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 exceptional completeness, robustness and precision of this approach uniquely positions Nix (and Guix) as highly valuable tool for automatically creating and managing accurate, reliable and reproducible software bills of materials (SBOMs) that can be employed to address the challenges outlined in the CISA notice. In fact, several projects exist (e.g. <a href="https://github.com/mlieberman85/nixbom">1</a>,<a href="https://github.com/tiiuae/sbomnix">2</a>,<a href="https://github.com/tweag/genealogos">3</a>) that aim to automatically generate SBOMs from Nix expressions, or <a href="https://github.com/nix-community/vulnix">connect Nix packages</a> to NIST’s <a href="https://nvd.nist.gov/vuln">national vulnerability database</a>. The Nix model for identifiers works very well together with SoftWare Hash IDentifiers (SWHID) for the full state of version control system repositories that <a href="https://www.regulations.gov/document/CISA-2023-0026-0006">are developed by Software Heritage</a>.</p>
<p>Finally, and certainly most importantly, I would like to emphasize that tens of thousands of users are demonstrating every day that applying this model comes without overhead. In fact, the precision and robustness of these software identifiers comes with a multitude of additional benefits. All this is not magic but enabled by a tool that follows a rigorous deployment model, the <a href="https://edolstra.github.io/pubs/phd-thesis.pdf">output of decades of academic experimentation and research</a>. This is why the user bases of Nix and Guix, still emerging technologies, are <a href="https://star-history.com/#nixos/nixpkgs&#x26;Date">rapidly growing</a>.</p>]]></description><link>https://tweag.io/blog/2024-03-12-nix-as-software-identifier/</link><guid isPermaLink="false">https://tweag.io/blog/2024-03-12-nix-as-software-identifier/</guid><pubDate>Tue, 12 Mar 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Bazel remote execution with rules_nixpkgs]]></title><description><![CDATA[<p>Tweag developed <a href="https://github.com/tweag/rules_nixpkgs"><code class="language-text">rules_nixpkgs</code></a> to empower Bazel users with the ability to leverage
Nix’s reproducible builds and its extensive package registry. That ruleset has proven to be
especially advantageous in endeavors demanding intricate dependency administration and the
maintenance of uniform build environments.</p>
<p>However, <code class="language-text">rules_nixpkgs</code> is incompatible with remote execution. This is a major limitation given that remote
execution is possibly the main reason why people switch to Bazel. And that <code class="language-text">rules_nixpkgs</code> provides a great way to configure hermetic toolchains, which are an important ingredient for reliable remote execution. There is no trivial fix as
can be seen in the related, longstanding <a href="https://github.com/tweag/rules_nixpkgs/issues/180">open issue</a>. At Tweag we
investigated a promising solution presented at Bazel eXchange 2022 (<a href="https://drive.google.com/file/d/1PuU_SuIQjzkVZPRMYnYN71Dd7nIljpWF/view?usp=drive_link">recording</a>), but these ideas
were never implemented in a public proof of concept.</p>
<p>In this post, we will present our new <a href="https://github.com/tweag/nix-bazel-remote-execution-infra">remote execution infrastructure repo</a> and walk you
through the required steps to comprehend and replicate how it achieves remote execution with
<code class="language-text">rules_nixpkgs</code>.</p>
<h3 id="the-remote-execution-limitation" style="position:relative;"><a href="#the-remote-execution-limitation" aria-label="the remote execution limitation 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 remote execution limitation</h3>
<p>When we make use of <code class="language-text">rules_nixpkgs</code>, we instruct Bazel to use packages from <a href="https://github.com/NixOS/nixpkgs">nixpkgs</a>
rather than those from the host system. This means that when we try to build a C++ project, Bazel won’t use the
<code class="language-text">gcc</code> compiler, which is typically found under <code class="language-text">/usr/bin</code>, but instead will use the compiler specified
by <code class="language-text">rules_nixpkgs</code> and provided by Nix, typically stored under some <code class="language-text">/nix/store/&lt;unique_hash>-gcc/bin</code> directory.</p>
<p>Bazel distinguishes actions to import external dependencies from regular build actions. The former are always executed locally<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>, while the latter can be distributed using remote execution. <code class="language-text">rules_nixpkgs</code> falls into the former category and invokes Nix to download and install the required <code class="language-text">/nix/store/&lt;unique_hash>-gcc</code> path locally on your machine.</p>
<p>This scenario works fine when we’re building locally. However, when we enable remote execution, <code class="language-text">rules_nixpkgs</code> still installs dependencies locally, while the build happens on another machine, which
will not have those paths available, so it will inevitably fail.</p>
<h3 id="initial-setup-with-remote-execution" style="position:relative;"><a href="#initial-setup-with-remote-execution" aria-label="initial setup with remote execution 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>Initial setup with remote execution</h3>
<p>For our proof of concept, we decided to use <a href="https://github.com/buildbarn">Buildbarn</a> to provide the remote execution
endpoint and infrastructure. Buildbarn provides Kubernetes manifests that we can use to deploy all
the necessary Buildbarn components for remote execution to work. We’ll be using the examples from
the <a href="https://github.com/buildbarn/bb-deployments/tree/1f221b54c99b57e3953865a75069a84245d96b56">bb-deployments</a> repository to test our setup, but also modifying it to make
use of <code class="language-text">rules_nixpkgs</code>.</p>
<p>To replicate our implementation you’ll need a working Buildbarn infrastructure, which in this case
would be a Kubernetes cluster. You can use <a href="https://github.com/tweag/nix-bazel-remote-execution-infra/blob/c0c698a040027ef2462892de9417718c19bcb03a/buildbarn/README.md">our guide</a> to set up a cluster on AWS.</p>
<h4 id="test-remote-execution-without-rules_nixpkgs" style="position:relative;"><a href="#test-remote-execution-without-rules_nixpkgs" aria-label="test remote execution without rules_nixpkgs 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>Test remote execution without <code class="language-text">rules_nixpkgs</code></h4>
<p>To make sure that everything is working as expected, we’ll use the <code class="language-text">@abseil-hello</code> Bazel target
which is available in the <a href="https://github.com/buildbarn/bb-deployments/tree/1f221b54c99b57e3953865a75069a84245d96b56">Buildbarn deployments repo</a>. This example does not use
<code class="language-text">rules_nixpkgs</code>, yet. You can clone the <a href="https://github.com/buildbarn/bb-deployments/tree/1f221b54c99b57e3953865a75069a84245d96b56">bb-deployments</a> repository, if you want to follow
along.</p>
<ul>
<li>Get the service endpoint of the Buildbarn executor service (<code class="language-text">frontend</code>). If you’re deploying on a
cloud provider this would be a load-balancer.</li>
</ul>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ kubectl get services <span class="token parameter variable">-n</span> buildbarn
NAME        TYPE           CLUSTER-IP      EXTERNAL-IP                         PORT<span class="token punctuation">(</span>S<span class="token punctuation">)</span>                      AGE
browser     ClusterIP      <span class="token number">172.20</span>.22.171   <span class="token operator">&lt;</span>none<span class="token operator">></span>                              <span class="token number">7984</span>/TCP                     8d
frontend    LoadBalancer   <span class="token number">172.20</span>.126.97   xxxxx.us-east-1.elb.amazonaws.com   <span class="token number">8980</span>:31657/TCP               8d
scheduler   ClusterIP      <span class="token number">172.20</span>.83.110   <span class="token operator">&lt;</span>none<span class="token operator">></span>                              <span class="token number">8982</span>/TCP,8983/TCP,7982/TCP   8d
storage     ClusterIP      None            <span class="token operator">&lt;</span>none<span class="token operator">></span>                              <span class="token number">8981</span>/TCP                     8d</code></pre></div>
<ul>
<li>Update <code class="language-text">.bazelrc</code> to use the remote executor endpoint of our environment</li>
</ul>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token punctuation">..</span>.
build:remote-exec <span class="token parameter variable">--remote_executor</span><span class="token operator">=</span>grpc://<span class="token punctuation">[</span>endpoint-from-previous-step<span class="token punctuation">]</span>
<span class="token punctuation">..</span>.</code></pre></div>
<p>Now we can try building the <code class="language-text">@abseil-hello</code> target using the remote execution infrastructure. Note that we’ll
be using a custom toolchain specific to the default executors created by Buildbarn.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">bazel build <span class="token parameter variable">--config</span><span class="token operator">=</span>remote-ubuntu-22-04 @abseil-hello//:hello_main</code></pre></div>
<h4 id="test-remote-execution-with-rules_nixpkgs" style="position:relative;"><a href="#test-remote-execution-with-rules_nixpkgs" aria-label="test remote execution with rules_nixpkgs 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>Test remote execution with <code class="language-text">rules_nixpkgs</code></h4>
<p>Once we have validated that our setup works we can create a new target that uses <code class="language-text">rules_nixpkgs</code>.</p>
<p>Update <code class="language-text">.bazelversion</code> to use <code class="language-text">6.4</code> which is a version supported by <code class="language-text">rules_nixpkgs</code> (any other
version on the <code class="language-text">6.x</code> should work as well).</p>
<p>Update the <code class="language-text">WORKSPACE</code> file with the following:</p>
<div class="gatsby-highlight" data-language="bazel"><pre class="language-bazel"><code class="language-bazel">http_archive(
    name = &quot;io_tweag_rules_nixpkgs&quot;,
    strip_prefix = &quot;rules_nixpkgs-244ae504d3f25534f6d3877ede4ee50e744a5234&quot;,
    urls = [&quot;https://github.com/tweag/rules_nixpkgs/archive/244ae504d3f25534f6d3877ede4ee50e744a5234.tar.gz&quot;],
)

load(&quot;@io_tweag_rules_nixpkgs//nixpkgs:repositories.bzl&quot;, &quot;rules_nixpkgs_dependencies&quot;)
rules_nixpkgs_dependencies()

load(&quot;@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl&quot;, &quot;nixpkgs_git_repository&quot;, &quot;nixpkgs_package&quot;, &quot;nixpkgs_cc_configure&quot;)

load(&quot;@io_tweag_rules_nixpkgs//nixpkgs:toolchains/go.bzl&quot;, &quot;nixpkgs_go_configure&quot;) # optional

nixpkgs_git_repository(
    name = &quot;nixpkgs&quot;,
    revision = &quot;23.11&quot;,
)

nixpkgs_cc_configure(
  repository = &quot;@nixpkgs&quot;,
  name = &quot;nixpkgs_config_cc&quot;,
  attribute_path = &quot;clang&quot;,
)</code></pre></div>
<p>This is the standard boilerplate to install <code class="language-text">rules_nixpkgs</code> on our Bazel workspace. We’re also
creating a reference to the nixpkgs repository, and a C++ toolchain using <code class="language-text">clang</code>.</p>
<p>Next, we create a new <code class="language-text">cc_binary</code> target in <code class="language-text">BUILD.bazel</code> with a simple hello-world program.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">cat</span> BUILD.bazel
<span class="token punctuation">..</span>.
cc_binary<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"hello-world"</span>,
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"hello-world.cc"</span><span class="token punctuation">]</span>,
<span class="token punctuation">)</span>

$ <span class="token function">cat</span> hello-world.cc
<span class="token comment">#include &lt;iostream></span>

int main<span class="token punctuation">(</span>int argc, char** argv<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  std::cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Hello world!"</span> <span class="token operator">&lt;&lt;</span> std::endl<span class="token punctuation">;</span>
  <span class="token builtin class-name">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Now we need to update the custom Buildbarn toolchain used by the executors to reference
<code class="language-text">@nixpkgs_config_cc</code>. Update the file <code class="language-text">tools/remote-toolchains/BUILD.bazel</code> and replace the instances
of <code class="language-text">@remote_config_cc</code> with <code class="language-text">@nixpkgs_config_cc</code>.</p>
<p>We can try building the application using the C++ toolchain we defined with <code class="language-text">rules_nixpkgs</code>. We expect
this to fail because the executors are not Nix-aware yet.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ bazel build <span class="token parameter variable">--config</span><span class="token operator">=</span>remote-ubuntu-22-04 @abseil-hello//:hello_main

<span class="token punctuation">..</span>.
ERROR: /home/user/.cache/bazel/_bazel_user/5ce2ca33a49034ed7557e24d70204ce5/external/com_google_absl/absl/base/BUILD.bazel:324:11: Compiling absl/base/internal/throw_delegate.cc failed: <span class="token punctuation">(</span>Exit <span class="token number">34</span><span class="token punctuation">)</span>: Remote Execution Failure:
Invalid Argument: Failed to run command: Failed to start process: fork/exec /nix/store/n37gxbg343hxin3wdryx092mz2dkafy8-clang-wrapper-16.0.6/bin/cc: no such <span class="token function">file</span> or directory
<span class="token punctuation">..</span>.</code></pre></div>
<p>Because the executors don’t have the <code class="language-text">/nix/store</code> available, they cannot resolve the compiler path
which is generated locally on our machine when we invoke <code class="language-text">bazel build</code>.</p>
<p>Now let’s see how we can solve this problem by configuring the executors to access a shared
<code class="language-text">/nix/store</code> via NFS.</p>
<h2 id="nfs-based-solution" style="position:relative;"><a href="#nfs-based-solution" aria-label="nfs based solution 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>NFS-based solution</h2>
<p>Our solution involves a Nix server that bridges this gap. This server manages and
synchronizes the Nix dependencies across the Bazel build environment.</p>
<p>Here’s how it works:</p>
<ol>
<li>
<p>During <code class="language-text">bazel build</code> the <code class="language-text">rules_nixpkgs</code> repository rules will build and copy any Nix derivation
to the remote Nix server.</p>
</li>
<li>
<p>The Nix server will export the <code class="language-text">/nix/store</code> directory tree via a read-only NFS mount share to the executors.</p>
</li>
<li>
<p>When a build is triggered, all necessary dependencies are already available on the executors,
allowing for the build process to continue.</p>
</li>
</ol>
<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/1ed1b4cf9c0c375c30aa4fe39ef147d8/b98f1/diagram%40x3.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/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA2UlEQVQoz4WR2U7DQBAEHRuDj4QjCYRDVTzx/7+IGo2RIxnxMLa0u13b3dsAzdaomZN6BibgSb0Bfib7W7rmj40+IFXgFbhXAzwAj+o7cAKOwAB0ZeAK2AERvAEvQKu25apdBIv7Asb9rXpU9zm3jhfhpdw1a8C6hvrfqRd1qDq+olfnpvoYcoM6ligR+sCrs77OdRUxkE/gA5jLyC7rEY9lP5CpDszpLH0BifJQPaaS55q+3J6r57AO+bTl4iri1mOt1sbVSy+d/wI3If8Am0qzrweb1CTYfQOTrTtyLkNQYAAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Workflow overview"
        title="Workflow overview"
        src="/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/fcda8/diagram%40x3.png"
        srcset="/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/12f09/diagram%40x3.png 148w,
/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/e4a3f/diagram%40x3.png 295w,
/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/fcda8/diagram%40x3.png 590w,
/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/efc66/diagram%40x3.png 885w,
/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/c83ae/diagram%40x3.png 1180w,
/static/1ed1b4cf9c0c375c30aa4fe39ef147d8/b98f1/diagram%40x3.png 3421w"
        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>Implementation-wise, we’ll need to make the following changes to the Buildbarn infrastructure:</p>
<ul>
<li>
<p>A Nix server. This could be a VM with Nix installed that is exporting the <code class="language-text">/nix/store</code>
directory as a read-only NFS share over the private network. We’ll need SSH access on that server
from the machine that invokes <code class="language-text">bazel build</code>.</p>
</li>
<li>
<p>Kubernetes executors with the exported NFS share mounted.</p>
</li>
</ul>
<p>For a detailed setup guide and implementation specifics, refer to our <a href="https://github.com/tweag/nix-bazel-remote-execution-infra">infrastructure
repository</a>.</p>
<p>To instruct <code class="language-text">rules_nixpkgs</code> to copy the nix derivations to the server we’ll need to create
an entry in our SSH config (typically found under <code class="language-text">~/.ssh/config</code>) with the remote server and then
set the environment variable <code class="language-text">BAZEL_NIX_REMOTE</code> with the name of that entry.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># SSH Configuration</span>
$ <span class="token function">cat</span> ~/.ssh/config
Host nix-server
  Hostname <span class="token punctuation">[</span>public-ip<span class="token punctuation">]</span>
  IdentityFile <span class="token punctuation">[</span>ssh-private-key<span class="token punctuation">]</span>
  Port <span class="token punctuation">[</span>ssh-port<span class="token punctuation">]</span>
  User <span class="token punctuation">[</span>ssh-user<span class="token punctuation">]</span></code></pre></div>
<h2 id="testing-out-remote-execution-again" style="position:relative;"><a href="#testing-out-remote-execution-again" aria-label="testing out remote execution again 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>Testing out remote execution again</h2>
<p>With the new setup, we can try building the project again.</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token builtin class-name">export</span> <span class="token assign-left variable">BAZEL_NIX_REMOTE</span><span class="token operator">=</span>nix-server
$ bazel clean <span class="token parameter variable">--expunge</span> <span class="token comment"># To refetch the Nix derivations</span>
$ bazel build <span class="token parameter variable">--config</span><span class="token operator">=</span>remote-ubuntu-22-04 @abseil-hello//:hello_main</code></pre></div>
<p>You should now see lines like the following, confirming communication with the Nix server</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">...
Analyzing: target @abseil-hello//:hello_main (0 packages loaded, 0 targets configured)
    Fetching repository @nixpkgs_config_cc_info; Remote-building Nix derivation 9s
...</code></pre></div>
<p>And the build should be successful.</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, we explored the challenges and our solution for integrating <code class="language-text">rules_nixpkgs</code> with remote
execution in Bazel. Of course this solution is not perfect and it comes with some shortcomings that end
user should be aware of.</p>
<ul>
<li>
<p>The first issue is about cache eviction. Caching all the Nix paths over the long term is not
practical from a storage standpoint. That’s why we need a way to mark the required paths, and
garbage collect the others. A Nix path should be available as long as a client may trigger a remote build
that uses it. However, there’s no way to determine when a client no longer needs a specific path. A
simple solution will be to invalidate the least used paths. That will require a tighter integration
with the Bazel APIs in order to track the Nix path usage.</p>
</li>
<li>
<p>The second issue relates to NFS performance. This depends on the infrastructure and
workloads in operation. At least we want to tune the NFS synchronization to the point that the
paths are available before any build begins. Slow synchronization between the NFS server and client
can lead to failed builds.</p>
</li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">Bazel has an <a href="https://github.com/bazelbuild/bazel/commit/06a26c37126e3eefabd28c24a4e35059b5b136bc">experimental feature</a> that enables remotable repository rule actions. However, their capabilities are too limited to support the <code class="language-text">rules_nixpkgs</code> use-case.<a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2024-02-29-remote-execution-rules-nixpkgs/</link><guid isPermaLink="false">https://tweag.io/blog/2024-02-29-remote-execution-rules-nixpkgs/</guid><pubDate>Thu, 29 Feb 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Source filtering with file sets]]></title><description><![CDATA[<p>Sponsored by <a href="https://antithesis.com/">Antithesis</a> (distributed systems reliability testing experts),
I’ve developed a new library to filter local files in Nix
which I’d like to introduce!</p>
<p>This post requires some familiarity with Nix and its language.
So if you don’t know what Nix is yet,
<a href="https://nix.dev/">take a look</a> first,
it’s pretty neat.</p>
<p>In this post we’re going to look at what source filtering is,
why it’s useful,
why a new library was needed for it,
and the basics of the new library.</p>
<p>This post notably won’t really <em>teach</em> you a lot about the new library,
that’s what the <a href="https://nix.dev/tutorials/file-sets">official tutorial</a>
and the <a href="https://nixos.org/manual/nixpkgs/stable/#sec-functions-library-fileset">reference documentation</a> is for.
But if you’d like to know some background and motivation, please read on!</p>
<h2 id="why-filter-sources" style="position:relative;"><a href="#why-filter-sources" aria-label="why filter 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>Why filter sources</h2>
<p>You most likely have come across this pattern:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">stdenv<span class="token punctuation">.</span>mkDerivation <span class="token punctuation">{</span>
  src <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  <span class="token comment"># stuff..</span>
<span class="token punctuation">}</span></code></pre></div>
<p>This is the basis for a Nix expression to build a project in a local directory.
There’s a <em>lot</em> of magic to make this work,
but we’ll focus on just a few aspects:</p>
<ul>
<li>Bar some exceptions, attributes passed to <a href="https://nixos.org/manual/nixpkgs/stable/#part-stdenv"><code class="language-text">stdenv.mkDerivation</code></a>
are automatically turned into environment variables that are available to the derivation builder.</li>
<li>The relative <a href="https://nixos.org/manual/nix/stable/language/values#type-path">path expression</a> <code class="language-text">./.</code>
is turned into a string of the form <code class="language-text">"/nix/store/&lt;hash>-&lt;name>"</code> by hashing the contents of the directory that Nix file is in, and adding it to the Nix store.</li>
</ul>
<p>We then end up with a <a href="https://nixos.org/manual/nix/stable/glossary#gloss-store-derivation">store derivation</a>
whose environment variables include <code class="language-text">src = "/nix/store/&lt;hash>-&lt;name>"</code>.
To get more info on how all of this works, see the <a href="https://nixos.org/manual/nix/stable/language/derivations.html">documentation on derivations</a>.</p>
<p>This generally <em>does</em> work,
with the big caveat that <em>all</em> files in the local directory are copied into the Nix store
and become a dependency of the derivation.
This means that:</p>
<ul>
<li>
<p>Changing <em>any</em> file will cause the resulting derivation to change,
making Nix unable to reuse previous build results.</p>
<p>For example, if you just <a href="https://github.com/NixOS/rfcs/pull/166">format your Nix files</a>,
Nix will have to build the project’s derivation again!</p>
</li>
<li>
<p>If you have secret files in the local directory,
they get added to the Nix store too,
making them readable by any user on the system!</p>
<blockquote>
<p><strong>Note</strong>
If you use the experimental new <code class="language-text">nix</code> CLI with Flakes and Git in a current (2.18.1) version of Nix,
only files tracked by Git will be available to Nix, so this generally won’t be a problem.</p>
<p>But be careful: If you don’t use Git, the entire directory is always copied to the Nix store!
Furthermore, experimental features may change over time.</p>
</blockquote>
</li>
</ul>
<h2 id="the-hardcore-way-to-filter-sources" style="position:relative;"><a href="#the-hardcore-way-to-filter-sources" aria-label="the hardcore way to filter 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>The hardcore way to filter sources</h2>
<p>To address this, Nix comes with <a href="https://nixos.org/manual/nix/stable/language/builtins.html#builtins-path"><code class="language-text">builtins.path</code></a>,
which allows controlling how paths get added to the Nix store:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">builtins</span><span class="token punctuation">.</span>path <span class="token punctuation">{</span>
  <span class="token comment"># The path to add to the store</span>
  path <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  <span class="token comment"># A function determining whether to include specific paths under ./.</span>
  <span class="token function">filter</span> <span class="token operator">=</span>
    <span class="token comment"># A path under ./.</span>
    path<span class="token punctuation">:</span>
    <span class="token comment"># The path type, either "directory", "normal", "symlink" or "unknown"</span>
    type<span class="token punctuation">:</span>
    <span class="token comment"># The return value of this function indicates</span>
    <span class="token comment"># whether the path should be included or not.</span>
    <span class="token comment"># In this case we always return true, meaning everything is included</span>
    <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>While this interface looks straightforward,
it’s notoriously tricky to get it to do what you want.</p>
<p>Let’s give it a try and start with something trivial,
say only including a single file in the current directory:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># default.nix</span>
<span class="token keyword">builtins</span><span class="token punctuation">.</span>path <span class="token punctuation">{</span>
  path <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  <span class="token function">filter</span> <span class="token operator">=</span>
    path<span class="token punctuation">:</span> type<span class="token punctuation">:</span>
    path <span class="token operator">==</span> <span class="token url">./file</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">touch</span> <span class="token function">file</span>

$ tree <span class="token string">"<span class="token variable"><span class="token variable">$(</span>nix <span class="token builtin class-name">eval</span> <span class="token parameter variable">--raw</span> -f.<span class="token variable">)</span></span>"</span>
/nix/store/dg5zq00kxabc3lfg03bnrfwax1ndgn6s-filter

<span class="token number">0</span> directories, <span class="token number">0</span> files</code></pre></div>
<p>It doesn’t work, we just get an empty directory!
The problem here is that the <code class="language-text">filter</code> function is not called with <em>path</em> values but rather <em>strings</em>,
and the <code class="language-text">==</code> operator always returns <code class="language-text">false</code> when given two values of different types.</p>
<p>To fix this, we can use the builtin <a href="https://nixos.org/manual/nix/stable/language/builtins.html#builtins-toString"><code class="language-text">toString</code></a> function,
which converts path values to strings:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># default.nix</span>
<span class="token keyword">builtins</span><span class="token punctuation">.</span>path <span class="token punctuation">{</span>
  path <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  <span class="token function">filter</span> <span class="token operator">=</span>
    path<span class="token punctuation">:</span> type<span class="token punctuation">:</span>
    path <span class="token operator">==</span> <span class="token function">toString</span> <span class="token url">./file</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ tree <span class="token string">"<span class="token variable"><span class="token variable">$(</span>nix <span class="token builtin class-name">eval</span> <span class="token parameter variable">--raw</span> -f.<span class="token variable">)</span></span>"</span>
/nix/store/2myzf03ca2ch3lc40p7frvqqbvm5nm2m-filter
└── <span class="token function">file</span>

<span class="token number">1</span> directory, <span class="token number">1</span> <span class="token function">file</span></code></pre></div>
<p>Great, this works! Now let’s try the same for <code class="language-text">dir/file</code>:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># default.nix</span>
<span class="token keyword">builtins</span><span class="token punctuation">.</span>path <span class="token punctuation">{</span>
  path <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  <span class="token function">filter</span> <span class="token operator">=</span>
    path<span class="token punctuation">:</span> type<span class="token punctuation">:</span>
    path <span class="token operator">==</span> <span class="token function">toString</span> <span class="token url">./dir/file</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">mkdir</span> <span class="token function">dir</span> <span class="token operator">&amp;&amp;</span> <span class="token function">touch</span> dir/file

$ tree <span class="token string">"<span class="token variable"><span class="token variable">$(</span>nix <span class="token builtin class-name">eval</span> <span class="token parameter variable">--raw</span> -f.<span class="token variable">)</span></span>"</span>
/nix/store/dg5zq00kxabc3lfg03bnrfwax1ndgn6s-filter

<span class="token number">0</span> directories, <span class="token number">0</span> files</code></pre></div>
<p>Apparently this doesn’t work for nested directories.</p>
<p>The problem now is that the <code class="language-text">filter</code> function <em>first</em> gets called on <code class="language-text">dir</code> itself.
And because <code class="language-text">./dir != ./dir/file</code>, it returns <code class="language-text">false</code>, therefore excluding <code class="language-text">dir</code> entirely.</p>
<p>To fix this we need to make sure the function recurses into the directories,
which we can do by checking for <code class="language-text">type == "directory"</code>:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># default.nix</span>
<span class="token keyword">builtins</span><span class="token punctuation">.</span>path <span class="token punctuation">{</span>
  path <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  <span class="token function">filter</span> <span class="token operator">=</span>
    path<span class="token punctuation">:</span> type<span class="token punctuation">:</span>
    <span class="token comment"># Return true for all directories</span>
    type <span class="token operator">==</span> <span class="token string">"directory"</span>
    <span class="token comment"># But also for the file we want to include</span>
    <span class="token operator">||</span> path <span class="token operator">==</span> <span class="token function">toString</span> <span class="token url">./dir/file</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>But what if there is another directory we don’t care about?</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">mkdir</span> another-dir

$ tree <span class="token variable"><span class="token variable">$(</span>nix <span class="token builtin class-name">eval</span> <span class="token parameter variable">--raw</span> -f.<span class="token variable">)</span></span>
/nix/store/wj0y4f4x5llzz8kj48jd0gvszddp3jr0-filter
├── another-dir
└── <span class="token function">dir</span>
    └── <span class="token function">file</span>

<span class="token number">3</span> directories, <span class="token number">1</span> <span class="token function">file</span></code></pre></div>
<p>This worked, <code class="language-text">dir/file</code> is there.
But so is the <code class="language-text">another-dir</code>, although it doesn’t even contain any files!</p>
<p>We could go on like that,
but I think you get the gist:
This function is tricky to use!</p>
<h2 id="introducing-the-file-set-library" style="position:relative;"><a href="#introducing-the-file-set-library" aria-label="introducing the file set library 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>Introducing the file set library</h2>
<p>Let’s compare this to the new file set library:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> lib <span class="token operator">?</span> <span class="token function">import</span> <span class="token punctuation">(</span><span class="token function">fetchTarball</span> <span class="token string">"channel:nixos-23.11"</span> <span class="token operator">+</span> <span class="token string">"/lib"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">:</span>
lib<span class="token punctuation">.</span>fileset<span class="token punctuation">.</span>toSource <span class="token punctuation">{</span>
  root <span class="token operator">=</span> <span class="token url">./.</span><span class="token punctuation">;</span>
  fileset <span class="token operator">=</span> <span class="token url">./dir/file</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ tree <span class="token variable"><span class="token variable">$(</span>nix <span class="token builtin class-name">eval</span> <span class="token parameter variable">--raw</span> -f. outPath<span class="token variable">)</span></span>
/nix/store/csgp388b3zqxp2av01gjncy9sadxib9q-source
└── <span class="token function">dir</span>
    └── <span class="token function">file</span>

<span class="token number">2</span> directories, <span class="token number">1</span> <span class="token function">file</span></code></pre></div>
<p>This is much more straightforward and does what you’d expect.
But where is the file set here?</p>
<p>The key here is that when the file set library expects to get a file set,
but gets a path, it implicitly turns the path into a file set.
So to the library, <code class="language-text">./dir/file</code> is a file set containing just the single file <code class="language-text">./dir/file</code>,
while <code class="language-text">./dir</code> would be a file set containing all files in the directory <code class="language-text">./dir</code>.</p>
<p>The real power of this library however comes from the fact that
file sets behave just like mathematical sets,
and it comes with some core functions to support that:</p>
<ul>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fileset.union"><code class="language-text">lib.fileset.union :: FileSet -> FileSet -> FileSet</code></a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fileset.intersection"><code class="language-text">lib.fileset.intersection :: FileSet -> FileSet -> FileSet</code></a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fileset.difference"><code class="language-text">lib.fileset.difference :: FileSet -> FileSet -> FileSet</code></a></li>
</ul>
<p>Some other notable features are:</p>
<ul>
<li>Files are <em>never</em> added to the store
unless explicitly requested with <a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fileset.toSource"><code class="language-text">lib.fileset.toSource</code></a>.</li>
<li>Maximum laziness:
Directories are never recursed into more than necessary.</li>
<li>Actionable error messages in case something doesn’t look right.</li>
<li>Minimal assumptions:
The library only relies on stable Nix features.
It even works correctly with <a href="https://github.com/NixOS/nix/pull/6530">possible future changes to the behavior of Nix paths</a>.</li>
</ul>
<p>But this is not the best place to teach you about the library.
For that, head over to the <a href="https://nix.dev/tutorials/file-sets">official tutorial</a> instead,
or check out the <a href="https://nixos.org/manual/nixpkgs/stable/#sec-functions-library-fileset">reference documentation</a>!</p>
<p>The file set library is going to be included in the
<a href="https://github.com/NixOS/nixpkgs/issues/258640">upcoming</a> NixOS 23.11 release.
If you encounter any problems using it or are missing some feature,
let me know in <a href="https://github.com/NixOS/nixpkgs/issues/266356">this tracking issue</a>.</p>
<h2 id="comparison" style="position:relative;"><a href="#comparison" aria-label="comparison 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>Comparison</h2>
<p>For completeness, we also need to look at previous related efforts and see how they compare to this library:</p>
<ul>
<li>
<p><a href="https://nixos.org/manual/nix/stable/language/builtins.html#builtins-fetchGit"><code class="language-text">builtins.fetchGit ./.</code></a>
allows creating a store path from all Git-tracked files,
so it’s very similar to <a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fileset.gitTracked"><code class="language-text">lib.fileset.gitTracked</code></a>.
However, it’s tricky to further restrict or extend the set selected files,
since the above <code class="language-text">filter</code>-based approach wouldn’t work on store paths without some changes.</p>
</li>
<li>
<p><a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.sources.cleanSourceWith"><code class="language-text">lib.sources.cleanSourceWith</code></a>
is a simple wrapper around <code class="language-text">builtins.path</code>.
While it has the same <code class="language-text">filter</code>-based interface,
it improves over <code class="language-text">builtins.path</code> by being chainable,
allowing the set of included files to be further restricted.</p>
</li>
<li>
<p><a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.sources.cleanSource"><code class="language-text">lib.sources.cleanSource</code></a>
uses <code class="language-text">cleanSourceWith</code> underneath to set <code class="language-text">filter</code> to a reasonable default,
filtering out some of the most common unwanted files automatically.
The file set library doesn’t <em>yet</em> have a good replacement for this,
but there is <a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fileset.fromSource"><code class="language-text">lib.fileset.fromSource</code></a>,
which you can use to convert any <code class="language-text">lib.sources</code>-based value to a file set.</p>
</li>
<li>
<p><a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.sources.sourceByRegex"><code class="language-text">lib.sources.sourceByRegex</code></a>
and <a href="https://nixos.org/manual/nixpkgs/stable/#function-library-lib.sources.sourceFilesBySuffices"><code class="language-text">lib.sources.sourceFilesBySuffices</code></a>
are also functions built on top of <code class="language-text">cleanSourceWith</code>,
and as such can be chained with each other.
While <code class="language-text">sourceFilesBySuffices</code> is not bad,
the interface of <code class="language-text">sourceByRegex</code> is rather clunky and error-prone.
Furthermore, it’s hard to add more files to the result.</p>
</li>
<li>
<p><a href="https://github.com/hercules-ci/gitignore.nix"><code class="language-text">gitignore.nix</code></a>
and <a href="https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-nix-gitignore"><code class="language-text">pkgs.nix-gitignore</code></a>
allow you to filter files based on Git’s <code class="language-text">.gitignore</code> files,
which is very related to Git-tracked files.
The file set library doesn’t replace these functions,
but it can be used as a more composable foundation.</p>
</li>
<li>
<p><a href="https://github.com/numtide/nix-filter">nix-filter</a>
is a third-party <code class="language-text">lib.sources</code> wrapper.
It wraps it with a nicer interface,
but suffers from some unclear semantics and composability issues.
The file set library should serve as an improved replacement.</p>
</li>
<li>
<p><a href="https://discourse.nixos.org/t/source-combinators/13331">Source combinators</a>
was a previous attempt to create a composable interface for handling source files.
It was a bit tricky to use and never merged,
but this is in fact the work that inspired the new file set library!</p>
</li>
</ul>
<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’ve seen that filtering sources can improve your Nix experience by avoiding unnecessary derivation rebuilds.
While it was possible to filter sources before using the <code class="language-text">builtins.path</code> function and other approaches, there are many pitfalls.
The <code class="language-text">lib.fileset</code> library in comparison makes source filtering a breeze.</p>
<p>In addition to a huge thanks to <a href="https://antithesis.com/">Antithesis</a> as the main sponsor of this work,
I’d also like to thank <a href="https://github.com/roberth/">Robert Hensing</a> from <a href="https://hercules-ci.com/">Hercules CI</a>
and <a href="https://github.com/fricklerhandwerk">Valentin Gagarin</a> from Tweag
for all the help they’ve given me during reviews!</p>]]></description><link>https://tweag.io/blog/2023-11-28-file-sets/</link><guid isPermaLink="false">https://tweag.io/blog/2023-11-28-file-sets/</guid><pubDate>Tue, 28 Nov 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Organist: stay sane managing your development environments]]></title><description><![CDATA[<p>tl;dr: We’re pleased to announce the beta release of <a href="https://github.com/nickel-lang/organist">Organist</a>, a tool designed to ease the definition of reliable and low-friction development environments and workflows, building on the combined strengths of <a href="https://github.com/nixos/nix">Nix</a> and <a href="https://github.com/tweag/nickel">Nickel</a>.</p>
<h2 id="a-mess-of-cables-and-knobs" style="position:relative;"><a href="#a-mess-of-cables-and-knobs" aria-label="a mess of cables and knobs 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 mess of cables and knobs</h2>
<p>I used to play piano as a kid.
As a teenager, I became frustrated by the limitations of the instrument and started getting into synthesizers.
That was a thrilling experience. Being able to turn a few knobs and get a totally different — and brand new — sound was an amazing feeling.
But it was also frustrating. There are so many ways in which you can tune such an instrument (including so many ways of getting an awful sound out of it) that I ended up spending all of my time trying to come up with the right sound, and I didn’t have any left to actually practice the instrument.
Besides, the sheer complexity and intricacy of the configuration meant that it was incredibly hard to remember how to get what I wanted out of it.</p>
<p>Then I started playing the organ.
Organs might be impressive and daunting, but compared to a synthesizer, they are pretty boring.
Modulo a few exceptions, the only controls you have in front of you are the stop knobs which allow you to turn a stop (sound) on or off.
That still leaves quite a few possible combinations, but very far from the virtually infinite possibilities of an analog synthesizer.
Now I had an expressive enough instrument, but one that knew how to get out of my way to let me play <em>it</em> rather than play <em>with it</em>.</p>
<p>Modern development projects are synthesizer-like these days. We combine a plethora of tools, each one coming with its own set of knobs.
Like the old school modular synthesizers, they generally all come from different places and plugging them together requires a huge mess of ad-hoc cabling, like in the good old time!</p>
<figure>
  <img src="https://upload.wikimedia.org/wikipedia/commons/3/3d/Steveporcaro_toto.jpg" alt="Modular synthesizer">
  <figcaption>Fig 1: Programmer tweaking their development environment</figcaption>
</figure>
<h2 id="organist" style="position:relative;"><a href="#organist" aria-label="organist 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>Organist</h2>
<p>It would be tempting to just ditch all of that complexity and only provide a hard-coded set of tools, with hard-coded settings and try to shoehorn everything into that setup.
But we all know that such a thing is a fool’s errand.
The variety of tools and knobs merely reflects the complexity of the real world, and we have to deal with it one way or another.</p>
<p>Instead, we can <em>limit the scope</em> of that complexity: provide the best possible defaults, and a framework to make it easy to override them in a tractable manner.</p>
<p><a href="https://github.com/nickel-lang/organist">Organist</a> is a tool meant to do just that: Provide you with an ergonomic way of managing the complexity of your development environment.</p>
<p>Let’s start with a few assumptions on the required qualities of a good development environment. As much as possible, it should:</p>
<ol>
<li>Remain local. Asking contributors to <code class="language-text">apt get</code> something just to contribute to your project is both a hindrance, and a source of failure and friction in the setup process.</li>
<li>Be seamless to instantiate. Every manual step that people need to perform before working on your project is a chance to mess up, give up, or waste endless amounts of time.</li>
<li>Be defined in the repository and kept in sync with the code. It must always be possible to go back to an old version and have everything ready to work on it.</li>
<li>Be tailored to your project. Every project has different needs and there’s no “one-size fits them all” <a href="https://www.thestar.com/news/insight/when-u-s-air-force-discovered-the-flaw-of-averages/article_e3231734-e5da-5bf5-9496-a34e52d60bd9.html">as the US air force discovered more than half a century ago</a>.</li>
</ol>
<p>Organist tries to make it easy for you to address these points by centralizing all the necessary configuration in a single file (or set of files) that can be checked-in to your repository, with both sane defaults and a powerful configuration mechanism.</p>
<h2 id="more-concretely" style="position:relative;"><a href="#more-concretely" aria-label="more concretely 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 concretely</h2>
<p>Given a single configuration describing your project’s whole environment (project dependencies and their configuration, the services that are required to run, etc.), Organist will drop you into an environment in which all of these are available and properly configured.</p>
<p>Centralizing all the configuration into a single place might look like a dangerous idea, as centralizing the mess could make it even messier if done wrong.
However, Organist provides the means to avoid this trap by leveraging <a href="https://github.com/tweag/nickel">Nickel</a> as its configuration language.
Nickel is a language first started by Tweag, with two fundamental features that help tremendously for managing the complexity:</p>
<ul>
<li><a href="https://nickel-lang.org/user-manual/correctness/">Contracts</a> provide ahead-of-time sanity checking, as well as most of the niceties of a good programming language (completion, documentation, etc.)</li>
<li>The <a href="https://www.tweag.io/blog/2023-11-02-nickel-merge-system/">merge system</a> makes it possible to define a (possibly complex) default configuration on the library side, and only have the client override the relevant fields.</li>
</ul>
<p>This design draws inspiration from the <a href="https://nixos.org/manual/nixos/stable/#sec-writing-modules">NixOS module system</a> which has shown its value in projects like <a href="https://nixos.org/manual/nixos/stable/">NixOS</a> and <a href="https://github.com/nix-community/home-manager/">home-manager</a>.</p>
<p><a href="https://github.com/nixos/nix">Nix</a> does the heavy lifting under the hood, provisioning the dependencies (thanks to the incredible <a href="https://github.com/nixos/nixpkgs">Nixpkgs</a> software distribution), and doing the grunt work of generating and combining all the configuration files. Indeed, Nix combines a few properties that make it an awesome fit for Organist’s needs:</p>
<ol>
<li>It makes it easy to build fully local and reproducible environments. This means that it’s easy to ask Nix something like “make me this very specific version of <code class="language-text">gcc</code> available only in my current shell session”, which is incredibly valuable.</li>
<li>It is totally agnostic to what it packages. This means that we can use it indifferently for building and pulling in a 2M lines toolchain or generating a simple text file.</li>
</ol>
<p>Organist also currently reuses the Nix command-line interface as its main entry point to avoid reimplementing the wheel (and keep it compatible with plain Nix users).</p>
<p>Note that Organist is not the only tool in that space.
<a href="https://devenv.sh">devenv</a> in particular follows some similar goals, although with a slightly different approach.
The <a href="https://github.com/nickel-lang/organist/tree/master#how-does-this-differ-from-insert-your-favorite-tool-">README</a> highlights the difference with the most prominent alternatives.</p>
<h2 id="demo-time-sort-of" style="position:relative;"><a href="#demo-time-sort-of" aria-label="demo time sort of 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>Demo time (sort-of)</h2>
<figure>
  <a href="https://asciinema.org/a/621037" target="_blank"><img src="/db7445577be4fbc69332bb25e4f135b4/organist_demo.gif" style="width:100%"></a>
  Screencast of Organist in action (click for higher-res)
</figure>
<p>The repository at <a href="https://github.com/tweag/organist-example">https://github.com/tweag/organist-example</a> shows an example of using Organist for a real-life(-ish) project. Let’s dissect the history a bit. All the steps link to the commit that implements them for completeness (although following them isn’t necessary to get the big picture).</p>
<p>It all starts with a simple Python script to upload files to S3 (<a href="https://github.com/tweag/organist-example/commit/0838d76a5a61557f21eeadd55a784b04a1e0cb35">step 1</a>).
The script works fine, but we now want to publish it as a standalone project.</p>
<p>This script requires the <a href="https://pypi.org/project/boto3/">boto3</a> Python library.
We can pull it in with <a href="https://python-poetry.org/">Poetry</a> (<a href="https://github.com/tweag/organist-example/commit/bd17c7474395e52cea16d697ba7375145dcb2e75">step 2</a>). Nothing to do with Organist so far, just plain Python development.</p>
<p>However, <code class="language-text">poetry</code> itself, as well as <code class="language-text">python</code> need to be available.
We can leverage <a href="https://nixos.org/nix">Nix</a> for that.
We’ll do it through Organist to abstract things away.</p>
<p>Before anything, we need a slight bit of initial boilerplate. Nix’s template mechanism can handle that for us with <code class="language-text">nix flake init -t github:nickel-lang/organist</code> (<a href="https://github.com/tweag/organist-example/commit/d22f1ce0dffa2bec473e71c533f7f6fb28295280">step 3</a>).
This being done, we can edit the Organist configuration to provide us with the right environment (<a href="https://github.com/tweag/organist-example/commit/e053238943251da46478ab0c2022bcd4e7a5f8f1">step 4</a>). This means:</p>
<ul>
<li>
<p>Changing the base <code class="language-text">shell</code> type from <code class="language-text">Bash</code> to <code class="language-text">Python310</code>. This ensures that we have a python interpreter (3.10) available, as well as some development tools to make our life easier (<a href="https://pypi.org/project/python-lsp-server/"><code class="language-text">python-lsp-server</code></a> in particular by default);</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span> shells = organist.shells.Bash,
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> shells = organist.shells.Python310,</span></code></pre></div>
</li>
<li>
<p>Adding <code class="language-text">poetry</code> to the available packages in the shell (and removing <code class="language-text">hello</code> as it’s not very useful beyond serving as example).</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span> packages = {},
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span> packages.poetry = organist.import_nix "nixpkgs#poetry",</span></code></pre></div>
</li>
</ul>
<p>We can now enter our new development shell <code class="language-text">nix develop</code>, and run our program (🎉). However, it requires directly accessing the Amazon S3 servers, which isn’t practical for testing.
Fortunately, great third-party implementations of the S3 API exist, and we can for instance leverage <a href="https://min.io/">minio</a> for our tests.</p>
<p>This requires:</p>
<ul>
<li>Having the <code class="language-text">minio</code> executable available</li>
<li>Being able to run it with the right parameters</li>
<li>Passing the correct parameters and credentials to our program</li>
</ul>
<p>This is a fair amount of work to put together, especially if this has to be replicated for every contributor to our project.
Leveraging a bit of Nix, a command-runner like <a href="http://ddollar.github.io/foreman/">foreman</a> or <a href="https://honcho.readthedocs.io/en/latest/index.html">honcho</a>, and some manual work, we could get this all running in a reasonably automated fashion.
However, Organist provides a <code class="language-text">services</code> extension to make that easier for us and allow defining everything in a single place (<a href="https://github.com/tweag/organist-example/commit/439cf92f30cdf0fff387f59725c765692d7b4a9e">step 5</a>).</p>
<p>What we have to do is set <code class="language-text">services.minio</code> to the right command (using the Nix integration to transparently fetch the <code class="language-text">minio</code> server), and run it:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token property">services.minio</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">x</span><span class="token operator">-</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">o</span><span class="token operator">r</span><span class="token operator">g</span><span class="token operator">a</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">i</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator">t</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">x</span><span class="token operator"> </span><span class="token string">"nixpkgs#minio"</span><span class="token operator">}</span><span class="token operator">/</span><span class="token operator">b</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">/</span><span class="token operator">m</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">o</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">e</span><span class="token operator">r</span><span class="token operator">v</span><span class="token operator">e</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">a</span><span class="token operator">d</span><span class="token operator">d</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">s</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">:</span><span class="token number">9000</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">m</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">o</span><span class="token operator">-</span><span class="token operator">d</span><span class="token operator">a</span><span class="token operator">t</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></code></pre></div>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix run <span class="token builtin class-name">.</span><span class="token comment">#start-services start</span>
<span class="token number">17</span>:18:57 system  <span class="token operator">|</span> minio.1 started <span class="token punctuation">(</span>pid<span class="token operator">=</span><span class="token number">461162</span><span class="token punctuation">)</span>
<span class="token punctuation">..</span>.
<span class="token number">17</span>:18:58 minio.1 <span class="token operator">|</span> S3-API: http://127.0.0.1:9000
<span class="token punctuation">..</span>.</code></pre></div>
<p>That’s not enough, because our program doesn’t know about this local minio instance and will still try to connect to S3.
But we can just set the right environment (still from our Nickel configuration) to make it look there:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token property">env.AWS_ENDPOINT_URL</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"http://localhost:9000"</span></code></pre></div>
<p>And we can now run our shiny service:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix develop <span class="token parameter variable">--command</span> poetry run python3 src/main.py</code></pre></div>
<p>Obviously, this is <em>just</em> set within the scope of our development environment.
Doing so doesn’t alter production in any way, so we can still use Amazon S3 (or whatever else we prefer) there.</p>
<p>This still isn’t ideal because the <code class="language-text">AWS_ENDPOINT_URL</code> has to hardcode the minio port number.
But this is all code! So we can just factor it out:</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">minio_port</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">t</span><span class="token operator">o</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 number">9031</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">env.AWS_ENDPOINT_URL</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"http://localhost:%{minio_port}"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">services.minio</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">x</span><span class="token operator">-</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">o</span><span class="token operator">r</span><span class="token operator">g</span><span class="token operator">a</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">s</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">i</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator">t</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">x</span><span class="token operator"> </span><span class="token string">"nixpkgs#minio"</span><span class="token operator">}</span><span class="token operator">/</span><span class="token operator">b</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">/</span><span class="token operator">m</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">o</span><span class="token operator"> </span><span class="token operator">s</span><span class="token operator">e</span><span class="token operator">r</span><span class="token operator">v</span><span class="token operator">e</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">a</span><span class="token operator">d</span><span class="token operator">d</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">s</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">m</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">o</span><span class="token operator">_</span><span class="token operator">p</span><span class="token operator">o</span><span class="token operator">r</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 operator">/</span><span class="token operator">.</span><span class="token operator">m</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">i</span><span class="token operator">o</span><span class="token operator">-</span><span class="token operator">d</span><span class="token operator">a</span><span class="token operator">t</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></code></pre></div>
<h2 id="going-further" style="position:relative;"><a href="#going-further" aria-label="going further 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>Going further</h2>
<p>This got us to a <em>working</em> environment.
But we could go further into making it a <em>nicer</em> environment to work with.</p>
<p>For instance, we can leverage Organist to generate us a good <a href="https://editorconfig.org/">EditorConfig</a> configuration (<a href="https://github.com/tweag/organist-example/commit/84bf2884e63204ebe1016dd5927b03e48c3f2af6">84bf28</a>) or even integrate with the awesome <a href="https://direnv.net/">direnv</a> to provide automatic reloading of the environment (<a href="https://github.com/tweag/organist-example/commit/84696f32f273549f73708ee01c6a80e8cd4b1b6e">84696f</a>).</p>
<h2 id="going-even-further" style="position:relative;"><a href="#going-even-further" aria-label="going even further 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>Going even further</h2>
<p>The world of development tools is an exciting one, full of marvels.
Organist interfaces with a number of them, but there’s so much more that could be done!</p>
<p>I’m planning for instance use to let Organist generate <a href="https://github.com/nickel-lang/organist/issues/147">generate GitHub actions</a>, or integrate with <a href="https://github.com/tweag/tf-ncl">tf-ncl</a> to make it easier to deploy our project.</p>
<p>Don’t hesitate to contribute you own Organist module to cover any need you have that’s not already handled.</p>
<p>Although Organist is still in its infancy, we hope that it will make your project setup easier.
We’re eager to get some feedback on it, so don’t hesitate to try it out!</p>
<p>It’s all happening <a href="https://github.com/nickel-lang/organist">in the Organist repository on GitHub</a>.</p>]]></description><link>https://tweag.io/blog/2023-11-16-announcing-organist/</link><guid isPermaLink="false">https://tweag.io/blog/2023-11-16-announcing-organist/</guid><pubDate>Thu, 16 Nov 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[nixtract 0.1.0]]></title><description><![CDATA[<p>Tweag is excited to announce the first release of <a href="https://github.com/tweag/nixtract"><code class="language-text">nixtract</code> 0.1.0</a>!
This is our first step towards a broader effort to make Nix the best tool to tackle tomorrow’s challenges of the <a href="https://en.wikipedia.org/wiki/Software_supply_chain">Software Supply Chain</a>.</p>
<p>In order to understand why we need <code class="language-text">nixtract</code>, let me tell you about the “secret” value of Nixpkgs.</p>
<h2 id="is-it-a-bird-a-plane-its-a-graph" style="position:relative;"><a href="#is-it-a-bird-a-plane-its-a-graph" aria-label="is it a bird a plane its a 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>Is it a bird? A plane? It’s a graph!</h2>
<p>The Nix language allows you to define the “recipe” to build anything into a package, like the sources and the steps to make the package, but also the dependencies it needs to build.
For instance we can define the recipe for a package that depends on <code class="language-text">zlib</code>.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> stdenv<span class="token punctuation">,</span> fetchFromGitHub<span class="token punctuation">,</span> zlib <span class="token punctuation">}</span><span class="token punctuation">:</span>

stdenv<span class="token punctuation">.</span>mkDerivation <span class="token punctuation">{</span>
  pname <span class="token operator">=</span> <span class="token string">"mypackage"</span><span class="token punctuation">;</span>
  version <span class="token operator">=</span> <span class="token string">"1.0.0"</span><span class="token punctuation">;</span>

  src <span class="token operator">=</span> fetchFromGitHub <span class="token punctuation">{</span>
    owner <span class="token operator">=</span> <span class="token string">"someowner"</span><span class="token punctuation">;</span>
    repo <span class="token operator">=</span> <span class="token string">"somerepo"</span><span class="token punctuation">;</span>
    rev <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>
    hash <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 punctuation">;</span>

  buildInputs <span class="token operator">=</span> <span class="token punctuation">[</span> zlib <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Such a recipe is called a “derivation”.
Behind the <code class="language-text">zlib</code> variable we use here also lies a derivation,
another recipe to build said library.</p>
<p>Looking at this expression, we can think of it as a graph.
Derivations are the nodes,
and they are connected when one depends on another.</p>
<!-- https://mermaid.live/edit#pako:eNoVyzsOwjAQRdGtWK9ONuCCipIK2mkGe5xY-BOZsVCIsndMd4tzD7jqBRYh1Y9buam53akYk_eN3YsXMfN8Md8Un1QwIUvLHP0Yjr8i6CpZCHakl8A9KYHKOSh3rY-9OFhtXSb0zbPKNfLSOMMGTm85f786K30 -->
<p><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="mermaid-svg" width="100%" style="max-width: 201.453125px;" viewBox="-8 -8 201.453125 50" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#mermaid-svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg .error-icon{fill:#552222;}#mermaid-svg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg .edge-thickness-normal{stroke-width:2px;}#mermaid-svg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg .marker{fill:#333333;stroke:#333333;}#mermaid-svg .marker.cross{stroke:#333333;}#mermaid-svg svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg .label{font-family:“trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg .cluster-label text{fill:#333;}#mermaid-svg .cluster-label span,#mermaid-svg p{color:#333;}#mermaid-svg .label text,#mermaid-svg span,#mermaid-svg p{fill:#333;color:#333;}#mermaid-svg .node rect,#mermaid-svg .node circle,#mermaid-svg .node ellipse,#mermaid-svg .node polygon,#mermaid-svg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg .flowchart-label text{text-anchor:middle;}#mermaid-svg .node .label{text-align:center;}#mermaid-svg .node.clickable{cursor:pointer;}#mermaid-svg .arrowheadPath{fill:#333333;}#mermaid-svg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg .cluster text{fill:#333;}#mermaid-svg .cluster span,#mermaid-svg p{color:#333;}#mermaid-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg :root{—mermaid-font-family:“trebuchet ms”,verdana,arial,sans-serif;}</style><g><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="10" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"/><g class="edgePaths"><path d="M94.65625,17L98.82291666666667,17C102.98958333333333,17,111.32291666666667,17,119.65625,17C127.98958333333333,17,136.32291666666666,17,140.48958333333334,17L144.65625,17" id="L-mypackage-zlib-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-mypackage LE-zlib" style="fill:none;" marker-end="url(#flowchart-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default default flowchart-label" id="flowchart-mypackage-0" transform="translate(47.328125, 17)"><rect class="basic label-container" style="" rx="0" ry="0" x="-47.328125" y="-17" width="94.65625" height="34"/><g class="label" style="" transform="translate(-39.828125, -9.5)"><rect/><foreignObject width="79.65625" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="nodeLabel">mypackage</span></div></foreignObject></g></g><g class="node default default flowchart-label" id="flowchart-zlib-1" transform="translate(165.0546875, 17)"><rect class="basic label-container" style="" rx="0" ry="0" x="-20.3984375" y="-17" width="40.796875" height="34"/><g class="label" style="" transform="translate(-12.8984375, -9.5)"><rect/><foreignObject width="25.796875" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="nodeLabel">zlib</span></div></foreignObject></g></g></g></g></g><style>@import url(”<a href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css%22);">https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css”);</a></style></svg></p>
<p>This graph reads: <code class="language-text">mypackage</code> depends on <code class="language-text">zlib</code>.</p>
<p>This is what we call a <a href="https://en.wikipedia.org/wiki/Dependency_graph">“dependency graph”</a>.</p>
<h2 id="from-one-to-many" style="position:relative;"><a href="#from-one-to-many" aria-label="from one to many 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 one to many</h2>
<p>Once we’ve written the recipe of a package,
it would be a waste not to share it, wouldn’t it?
That’s what people thought, hence creating <a href="https://github.com/NixOS/nixpkgs">Nixpkgs</a>,
a repository of Nix expressions that define packages maintained by the Nix community.</p>
<p>Nixpkgs is a great endeavour, maintaining one of the largest collection of packages ever made, which we could use to draw a dependency graph!</p>
<p>Maintainers also add all sorts of metadata such as the name,
the version, the license, the homepage, the description, and so on,
which could be used to make an even richer graph.</p>
<p>This data is extremely valuable, and can be used for many purposes such as:</p>
<ul>
<li>listing all the packages that a package depends on, both direct and transitive (what we call the <em>closure</em> of the package)</li>
<li>listing all licenses that are in the closure of a package</li>
<li>if plugged into a binary cache, it would help analyze the closure size and find the dependencies that have the most impact on it</li>
<li>if plugged into a CVE database, it would help flag all compromised packages</li>
</ul>
<p>One could imagine a dashboard with all the packages used in a system, or even throughout a company’s systems, to quickly monitor, audit and act on the entire Software Supply Chain.</p>
<p>The information is here in Nixpkgs, but hard to use because it’s encoded in Nix expressions.
<code class="language-text">nixtract</code>’s goal is to make it accessible in a way that’s easier to consume.</p>
<h2 id="extracting-data-from-nix-expressions" style="position:relative;"><a href="#extracting-data-from-nix-expressions" aria-label="extracting data from nix expressions 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>extracting data from Nix expressions</h2>
<p>Let’s consider a small Nix expression.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
  b <span class="token operator">=</span> <span class="token punctuation">{</span> c <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
  d <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> e <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> f <span class="token operator">=</span> <span class="token number">4</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>We can explore this nested structure like so:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix eval -f example.nix --json &#39;a&#39;
1
$ nix eval -f example.nix --json &#39;b.c&#39;
2</code></pre></div>
<p><code class="language-text">'a'</code> and <code class="language-text">'b.c'</code> are called <em>attribute paths</em>, and describe how to reach a value.
This is the mechanism that is used to access packages in Nixpkgs. For example, we can install packages through their attribute path:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix-env -iA pythonPackages.requests</code></pre></div>
<p>This first accesses the <code class="language-text">pythonPackages</code> attribute of the usual <code class="language-text">pkgs</code> attribute set.
<code class="language-text">pythonPackages</code> is an attribute set, and we can then access its <code class="language-text">requests</code> attribute.</p>
<p>One could be tempted to parse Nix expressions with a custom parser to extract the graph.
Unfortunately, it gets a bit more complicated when a node has many children, represented as a list:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix eval -f example.nix d --apply &#39;d: (builtins.elemAt d 0).e&#39;
3</code></pre></div>
<p>And it would quickly get too complex with Nix functions:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">let</span>
  names <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"a"</span> <span class="token string">"b"</span> <span class="token string">"c"</span><span class="token punctuation">]</span>
  items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">in</span>
  <span class="token keyword">builtins</span><span class="token punctuation">.</span><span class="token function">listToAttrs</span> <span class="token punctuation">(</span><span class="token keyword">builtins</span><span class="token punctuation">.</span><span class="token function">map</span> <span class="token punctuation">(</span>x<span class="token punctuation">:</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token keyword">builtins</span><span class="token punctuation">.</span><span class="token function">elemAt</span> names x<span class="token punctuation">;</span> value <span class="token operator">=</span> x<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span> items<span class="token punctuation">)</span></code></pre></div>
<p>This last expression builds a simple attribute set <code class="language-text">{a = 0; b = 1; c = 2}</code>, but it does require to evaluate it to make any sense.</p>
<p>All in all, the only strategy available to extract the graph data written in Nix code is to evaluate said Nix code.</p>
<h2 id="evaluating-the-entire-nixpkgs" style="position:relative;"><a href="#evaluating-the-entire-nixpkgs" aria-label="evaluating the entire nixpkgs 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>Evaluating the entire Nixpkgs</h2>
<p>Nixpkgs being Nix code, it can be imported and evaluated, but also manipulated in Nix code!</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">let</span>
  pkgs <span class="token operator">=</span> <span class="token function">import</span> <span class="token operator">&lt;</span>nixpkgs<span class="token operator">></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">in</span>
  <span class="token keyword">builtins</span><span class="token punctuation">.</span><span class="token function">attrNames</span> pkgs</code></pre></div>
<p>Here <code class="language-text">pkgs</code> is an attribute set like any in Nix, so we can manipulate it to our heart’s content.</p>
<p>A first approach to extract the information from Nixpkgs was thus to use a Nix expression that returns the whole graph as JSON.</p>
<p>Our more regular readers probably have heard about this on this very same blog <a href="https://www.tweag.io/blog/2022-09-13-nixpkgs-graph/">about a year ago</a>.
However, at this time, we built the graph of packages that are top-level, and only them.
As it turns out, this is not sufficient for a complete analysis;
what we need is the entire graph of derivations,
even the ones not accessible at the top-level.</p>
<p>So now, the idea is to recursively go down <code class="language-text">pkgs</code>, find derivations,
transform them into a JSON-like attribute set,
but also keep recursing into its dependencies.</p>
<p>For the curious, below is a small snippet demonstrating that idea.
Don’t worry if it looks intimidating,
you don’t need to understand it to read this blog post.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> nixpkgs<span class="token punctuation">,</span> system <span class="token punctuation">}</span><span class="token punctuation">:</span>
<span class="token keyword">let</span>
  <span class="token comment"># recurse :: string -> any -> attrs | null</span>
  recurse <span class="token operator">=</span>
    name<span class="token punctuation">:</span> value<span class="token punctuation">:</span>
      <span class="token keyword">if</span> <span class="token operator">!</span><span class="token punctuation">(</span>nixpkgs<span class="token punctuation">.</span>lib<span class="token punctuation">.</span>isDerivation value<span class="token punctuation">)</span> <span class="token keyword">then</span>
        <span class="token comment"># recurse when possible</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span>recurseForDerivations <span class="token keyword">or</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
        <span class="token keyword">then</span>
          <span class="token keyword">builtins</span><span class="token punctuation">.</span>mapAttrs recurse value
        <span class="token keyword">else</span>
          <span class="token keyword">null</span>
      <span class="token comment"># process derivation and recurse into inputs</span>
      <span class="token keyword">else</span>
        <span class="token punctuation">{</span>
          <span class="token comment"># path to the evaluated derivation file</span>
          derivationPath <span class="token operator">=</span> value<span class="token punctuation">.</span>drvPath<span class="token punctuation">;</span>
          <span class="token comment"># one could add any information</span>

          <span class="token comment"># recurse into inputs</span>
          buildInputs <span class="token operator">=</span> <span class="token function">map</span> <span class="token punctuation">(</span>recurse <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span>buildInputs <span class="token keyword">or</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"># do the same for other inputs...</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">;</span>
<span class="token keyword">in</span>
  <span class="token comment"># build the value that will be yielded</span>
  <span class="token function">map</span>
    <span class="token comment"># go through a bit of processing to not have the children nodes written in the node</span>
    <span class="token punctuation">(</span>drv<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">inherit</span> <span class="token punctuation">(</span>drv<span class="token punctuation">)</span> derivationPath<span class="token punctuation">;</span>
      buildInputs <span class="token operator">=</span> <span class="token function">map</span> <span class="token punctuation">(</span>inputDrv<span class="token punctuation">:</span> inputDrv<span class="token punctuation">.</span>derivationPath<span class="token punctuation">)</span> <span class="token punctuation">(</span>drv<span class="token punctuation">.</span>buildInputs <span class="token keyword">or</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 comment"># process recursively</span>
    <span class="token punctuation">(</span>nixpkgs<span class="token punctuation">.</span>lib<span class="token punctuation">.</span>collect
      <span class="token punctuation">(</span>x<span class="token punctuation">:</span> <span class="token punctuation">(</span>x<span class="token punctuation">.</span>derivationPath <span class="token keyword">or</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
      <span class="token punctuation">(</span><span class="token keyword">builtins</span><span class="token punctuation">.</span>mapAttrs recurse nixpkgs<span class="token punctuation">.</span>legacyPackages<span class="token punctuation">.</span><span class="token antiquotation important">$</span><span class="token punctuation">{</span>system<span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span></code></pre></div>
<p>If you would like to learn more about it,
you can check out the complete expression we used:
<a href="https://github.com/tweag/nixpkgs-graph-explorer/blob/ad8413013b6b30307d23a87041487a3252602357/core/explorer/extract/nixpkgs-graph.nix"><code class="language-text">nixpkgs-graph-explorer/core/extract/nixpkgs-graph.nix</code></a><sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>.
It handles the many tricks of evaluating Nixpkgs.</p>
<p>The idea would then be to evaluate it and get the output data.</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nix eval -f nixpkgs-to-graph.nix --json</code></pre></div>
<p>Unfortunately, that dream quickly came to an end when recursing over the entire Nixpkgs.
As much as we tried to tailor an expression to recursively describe, Nix kept filling the RAM and made our laptops crash.</p>
<p>But if one Nix can’t do it all, maybe many Nix-s can!</p>
<h2 id="towards-nixtract" style="position:relative;"><a href="#towards-nixtract" aria-label="towards nixtract 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>Towards nixtract</h2>
<p><code class="language-text">nixtract</code> uses not one, but two Nix expression.</p>
<p>The first one focuses on finding all the top-level derivations that can be directly accessed from <code class="language-text">pkgs</code>.
It differs from the snippet shown earlier in that</p>
<ul>
<li>It does not recurse on the inputs of the derivation,</li>
<li>It outputs nothing more than the attribute path and the output path,</li>
<li>The output is streamed rather than batched.</li>
</ul>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ TARGET_FLAKE_REF=&quot;nixpkgs&quot; TARGET_SYSTEM=&quot;x86_64-linux&quot; nix eval --json --file ./nixtract/find-attribute-paths.nix
trace: {&quot;foundDrvs&quot;:[{&quot;attributePath&quot;:&quot;AMB-plugins.out&quot;,&quot;derivationPath&quot;:&quot;/nix/store/p060rxjqna1pnmg63yz6j7ya1as1wk4k-AMB-plugins-0.8.1.drv&quot;,&quot;outputPath&quot;:&quot;/nix/store/z15irhv9lbr3f86fgifzbhrnga2m42ji-AMB-plugins-0.8.1&quot;}]}
trace: {&quot;foundDrvs&quot;:[{&quot;attributePath&quot;:&quot;ArchiSteamFarm.out&quot;,&quot;derivationPath&quot;:&quot;/nix/store/s57c44cqwn7sw7vc7b7zx0na4snvkh9c-archisteamfarm-5.4.4.5.drv&quot;,&quot;outputPath&quot;:&quot;/nix/store/ka2wsngqkblcbl3awbcmhlpxllrddif4-archisteamfarm-5.4.4.5&quot;}]}
...</code></pre></div>
<p>The second one focuses on following an attribute path and “describing” the derivation.</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ TARGET_FLAKE_REF="nixpkgs" TARGET_SYSTEM="x86_64-linux" TARGET_ATTRIBUTE_PATH="hello" nix eval --json --file ./nixtract/describe-derivation.nix
{"attributePath":"hello","buildInputs":[],"derivationPath":"/nix/store/r91xxmyayj90xlr0378r817ly09khpxg-hello-2.12.1.drv","name":"hello-2.12.1","nixpkgsMetadata":{"broken":false,"license":"GNU General Public License v3.0 or later","pname":"hello","version":"2.12.1"},"outputPath":"/nix/store/7syg3lif5ik17dsrwgk3s00s116q87by-hello-2.12.1","outputs":[{"name":"out","outputPath":"/nix/store/7syg3lif5ik17dsrwgk3s00s116q87by-hello-2.12.1"}],"parsedName":{"name":"hello","version":"2.12.1"}</code></pre></div>
<p>In Python, we make a queue of attribute paths that we need to “describe”,
fed by the first expression ran in a process, and we process each of them using the second expression ran in other processes.</p>
<p>But the first expression is not the only one contributing to the queue.
When the second expression, the one that “describes”, visits a derivation that has inputs,
these input derivations will be added by the Python script to the queue if they haven’t already been added.
This ensures that not only top level derivations are described,
but all the derivations in the closure of each visited derivation are as well.</p>
<p>Instead of recursing everything at once in one Nix process that ends up gobbling up all our RAM,
we spawn many small Nix processes that don’t explode.
An extra benefit is that we can now parallelize the extraction and get the data faster.</p>
<h2 id="and-voilà" style="position:relative;"><a href="#and-voil%C3%A0" aria-label="and voilà 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>and <em>voilà!</em></h2>
<p>All in all, you only use a single command line to start the magic:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nixtract -
{&quot;attribute_path&quot;: &quot;AMB-plugins.out&quot;, &quot;derivation_path&quot;: &quot;/nix/store/90bclfvqnc0mzn6vsd99bzl383vl2j04-AMB-plugins-0.8.1.drv&quot;, &quot;output_path&quot;: &quot;/nix/store/7n05ir19xx0086dk1wq1zm1gq8g1ymyr-AMB-plugins-0.8.1&quot;, &quot;outputs&quot;: [{&quot;name&quot;: &quot;out&quot;, &quot;output_path&quot;: &quot;/nix/store/7n05ir19xx0086dk1wq1zm1gq8g1ymyr-AMB-plugins-0.8.1&quot;}], &quot;name&quot;: &quot;AMB-plugins-0.8.1&quot;, &quot;parsed_name&quot;: {&quot;name&quot;: &quot;AMB-plugins&quot;, &quot;version&quot;: &quot;0.8.1&quot;}, &quot;nixpkgs_metadata&quot;: {&quot;pname&quot;: &quot;AMB-plugins&quot;, &quot;version&quot;: &quot;0.8.1&quot;, &quot;broken&quot;: false, &quot;license&quot;: &quot;GNU General Public License v2.0 or later&quot;}, &quot;build_inputs&quot;: [{&quot;attribute_path&quot;: &quot;AMB-plugins.out.buildInputs.0&quot;, &quot;build_input_type&quot;: &quot;build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/ib5bh4b9fl40zamlx0zpfgw8ygaj01l5-ladspa.h-1.15&quot;}]}
...

{&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2&quot;, &quot;derivation_path&quot;: &quot;/nix/store/pvdzfr30s1xc8qcvavmmkj1j51lpnifp-curl-8.3.0.drv&quot;, &quot;output_path&quot;: &quot;/nix/store/r4638iv5a646jz14rl02r6r9cqhggaky-curl-8.3.0-dev&quot;, &quot;outputs&quot;: [{&quot;name&quot;: &quot;bin&quot;, &quot;output_path&quot;: &quot;/nix/store/yiip8b2ks99ixn5jf9jlx0bsxl5r2h2m-curl-8.3.0-bin&quot;}, {&quot;name&quot;: &quot;dev&quot;, &quot;output_path&quot;: &quot;/nix/store/r4638iv5a646jz14rl02r6r9cqhggaky-curl-8.3.0-dev&quot;}, {&quot;name&quot;: &quot;out&quot;, &quot;output_path&quot;: &quot;/nix/store/bhmynyjwzc2r6iqf7fhc3yzjcv3paiwa-curl-8.3.0&quot;}, {&quot;name&quot;: &quot;man&quot;, &quot;output_path&quot;: &quot;/nix/store/c19b760rmwv0j57bgscxmic9qkylihzg-curl-8.3.0-man&quot;}, {&quot;name&quot;: &quot;devdoc&quot;, &quot;output_path&quot;: &quot;/nix/store/ad15cgxydfspm6sqda4plrv2vf2kfib9-curl-8.3.0-devdoc&quot;}, {&quot;name&quot;: &quot;debug&quot;, &quot;output_path&quot;: &quot;/nix/store/bv42bixmsj38cdfdrf08760pxc5z03pq-curl-8.3.0-debug&quot;}], &quot;name&quot;: &quot;curl-8.3.0&quot;, &quot;parsed_name&quot;: {&quot;name&quot;: &quot;curl&quot;, &quot;version&quot;: &quot;8.3.0&quot;}, &quot;nixpkgs_metadata&quot;: {&quot;pname&quot;: &quot;curl&quot;, &quot;version&quot;: &quot;8.3.0&quot;, &quot;broken&quot;: false, &quot;license&quot;: &quot;curl License&quot;}, &quot;build_inputs&quot;: [{&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.nativeBuildInputs.0&quot;, &quot;build_input_type&quot;: &quot;native_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/5daca24rn22c65ff25lc6z0g0imfphvr-pkg-config-wrapper-0.29.2&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.nativeBuildInputs.1&quot;, &quot;build_input_type&quot;: &quot;native_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/2j7b1ngdvqd0bidb6bn9icskwm6sq63v-perl-5.38.0&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.0&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/xz08admjq3viprgxnb6gc9xvbbhdxq7h-brotli-1.1.0-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.1&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/s64na02z7fd3q6d04br9ivbdrqcmx5vj-libkrb5-1.20.1-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.2&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/46dnqv0sjm5g1sgd4r0rqp50sv7wr1ma-nghttp2-1.54.0-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.3&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/84d89lbdfdzy1wb363k3avl8qb8i6lcb-libidn2-2.3.4-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.4&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/lqsfdc4p9vax2h55csza9iw46zjpghl6-openssl-3.0.10-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.5&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/ifvv1iq1fq7j3z7ykz997gnba8a2yy5m-libssh2-1.11.0-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.6&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/9m8fx7phv5gr67b2yd5a43p287hg10g0-zlib-1.3-dev&quot;}, {&quot;attribute_path&quot;: &quot;CHOWTapeModel.out.buildInputs.2.propagatedBuildInputs.7&quot;, &quot;build_input_type&quot;: &quot;propagated_build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/zgflsh0x6c8lka6y0n2diiph6ahiglxh-zstd-1.5.5-dev&quot;}]}
...</code></pre></div>
<p>This command outputs the information about all derivations, top-level or any dependency,
from a flake (by default <code class="language-text">nixpkgs</code> from your flake registry).</p>
<p>What’s more, our processing is threaded in order to stream the result,
to make sure that the user does not have to wait for everything to be processed before being able to start consuming the data.
This is really convenient to pipe with other tools like <code class="language-text">jq</code>:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nixtract - | jq -r &#39;.derivation_path&#39;
/nix/store/90bclfvqnc0mzn6vsd99bzl383vl2j04-AMB-plugins-0.8.1.drv
/nix/store/vwz2w3arkprzxc28d0f621hbv8gq468f-ArchiSteamFarm-5.4.9.3.drv
...</code></pre></div>
<p>Not only can you extract the data from the <code class="language-text">nixpkgs</code> flake for your current system,
but you can extract the data from any flake and for any system:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nixtract --target-flake-ref &#39;github:nixos/nixpkgs/23.05&#39; --target-system &#39;x86_64-linux&#39; -
{&quot;attribute_path&quot;: &quot;AMB-plugins.out&quot;, &quot;derivation_path&quot;: &quot;/nix/store/ddw13azl05jp816s57zzkqlf7qa2c7xg-AMB-plugins-0.8.1.drv&quot;, &quot;output_path&quot;: &quot;/nix/store/sbkj5l8ynhywr1fqszxr91g043gvxjc1-AMB-plugins-0.8.1&quot;, &quot;outputs&quot;: [{&quot;name&quot;: &quot;out&quot;, &quot;output_path&quot;: &quot;/nix/store/sbkj5l8ynhywr1fqszxr91g043gvxjc1-AMB-plugins-0.8.1&quot;}], &quot;name&quot;: &quot;AMB-plugins-0.8.1&quot;, &quot;parsed_name&quot;: {&quot;name&quot;: &quot;AMB-plugins&quot;, &quot;version&quot;: &quot;0.8.1&quot;}, &quot;nixpkgs_metadata&quot;: {&quot;pname&quot;: &quot;AMB-plugins&quot;, &quot;version&quot;: &quot;0.8.1&quot;, &quot;broken&quot;: false, &quot;license&quot;: &quot;GNU General Public License v2.0 or later&quot;}, &quot;build_inputs&quot;: [{&quot;attribute_path&quot;: &quot;AMB-plugins.out.buildInputs.0&quot;, &quot;build_input_type&quot;: &quot;build_input&quot;, &quot;output_path&quot;: &quot;/nix/store/wkxygwfn22g8fjs6alq9j1x2jiaw679k-ladspa.h-1.15&quot;}]}
{&quot;attribute_path&quot;: &quot;AMB-plugins.out.buildInputs.0&quot;, &quot;derivation_path&quot;: &quot;/nix/store/3j3lzndm8ky97hc59s3bfdf6ln38wg2h-ladspa.h-1.15.drv&quot;, &quot;output_path&quot;: &quot;/nix/store/wkxygwfn22g8fjs6alq9j1x2jiaw679k-ladspa.h-1.15&quot;, &quot;outputs&quot;: [{&quot;name&quot;: &quot;out&quot;, &quot;output_path&quot;: &quot;/nix/store/wkxygwfn22g8fjs6alq9j1x2jiaw679k-ladspa.h-1.15&quot;}], &quot;name&quot;: &quot;ladspa.h-1.15&quot;, &quot;parsed_name&quot;: {&quot;name&quot;: &quot;ladspa.h&quot;, &quot;version&quot;: &quot;1.15&quot;}, &quot;nixpkgs_metadata&quot;: {&quot;pname&quot;: &quot;ladspa.h&quot;, &quot;version&quot;: &quot;1.15&quot;, &quot;broken&quot;: false, &quot;license&quot;: &quot;GNU Library General Public License v2&quot;}, &quot;build_inputs&quot;: []}
...</code></pre></div>
<h2 id="this-is-just-the-beginning" style="position:relative;"><a href="#this-is-just-the-beginning" aria-label="this is just the beginning 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>This is just the beginning</h2>
<p>We have released <code class="language-text">nixtract</code> 0.1.0,
check out the <a href="https://github.com/tweag/nixtract/">README</a> and try it now!</p>
<p>The data extracted by <code class="language-text">nixtract</code> is somewhat basic,
but you can expect it to extract more metadata from derivations in the future.</p>
<p><code class="language-text">nixtract</code> focuses on extracting the data, which is hardly enough.
We hope to combine <code class="language-text">nixtract</code> with other tools so that anyone is able to scan the whole Nixpkgs graph as they need.
It would also be exciting to combine this source of data with others,
like binary caches and CVE databases, to enable even more use cases.</p>
<p>We hope that <code class="language-text">nixtract</code> becomes a foundational brick that sparks new efforts
towards better understanding Nixpkgs and improving Software Supply Chain management.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">As I am writing this blog post, a coworker has pointed out to me that we basically ended up reimplementing <a href="https://github.com/nix-community/nix-eval-jobs"><code class="language-text">nix-eval-jobs</code></a> here.
We could consider using it directly.<a href="#fnref-1" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2023-10-26-nixtract-0-1-0/</link><guid isPermaLink="false">https://tweag.io/blog/2023-10-26-nixtract-0-1-0/</guid><pubDate>Thu, 26 Oct 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Announcing Nickel 1.0]]></title><description><![CDATA[<p>Today, I am very excited to announce the 1.0 release of <a href="https://www.nickel-lang.org">Nickel</a>.</p>
<p>A bit more than one year ago, we <a href="https://www.tweag.io/blog/2022-03-11-nickel-first-release/">released the very first public version Nickel
(0.1)</a>. Throughout various
<a href="https://www.tweag.io/blog/tags/nickel">write-ups</a> and public talks
(<a href="https://cfp.cfgmgmtcamp.org/2023/talk/LUUH8E/">1</a>,
<a href="https://fosdem.org/2023/schedule/event/nix_and_nixos_nixel/">2</a>,
<a href="https://cfp.cfgmgmtcamp.org/2023/talk/CTEVUE/">3</a>), we’ve been telling the
story of our dissatisfaction with the state of configuration management.</p>
<h2 id="the-need-for-a-new-deal" style="position:relative;"><a href="#the-need-for-a-new-deal" aria-label="the need for a new deal 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 need for a New Deal</h2>
<p>Configuration is everywhere. The manifest of a web app, the configuration of an
Apache virtual host, an Infrastructure-as-Code (IaC) cloud deployment
(Terraform, Kubernetes, etc.).</p>
<p>Configuration is more often than not considered a second-class engineering
discipline, like a side activity of software engineering. That’s a dangerous
mistake: with the advent of IaC for the cloud, configuration has become an
important aspect of modern software systems, and a critical point of failure.</p>
<p>All the different but connected configurations composing a system are scattered
across many languages, tools, and services (JSON, YAML, HCL, Puppet, Apache’s
Tcl, and so on). Configuration management is indeed a cross-cutting concern,
making failures harder to predict and often spectacular.</p>
<p>In the last decade, studies have shown that misconfigurations were the second
largest cause of service-level disruptions in one of Google’s main production
services <sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>. Misconfigurations also contribute to 16% of
production incidents at Facebook <sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup>, including the worst-ever
outage of Facebook and Instagram that occurred in March 2019
<sup id="fnref-4"><a href="#fn-4" class="footnote-ref">4</a></sup>. Fastly’s<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup> outage on the 8th June 2021, which
basically broke a substantial part of the internet, was triggered by a
configuration issue.</p>
<p>Modern configurations are complex. They require new tools to be dealt with,
which is why we developed the Nickel configuration language.</p>
<h2 id="nickel-10" style="position:relative;"><a href="#nickel-10" aria-label="nickel 10 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>Nickel 1.0</h2>
<p>Nickel is a lightweight and generic configuration language. It can replace YAML
as your new application’s configuration language, or it can generate static
configuration files (YAML, JSON or TOML) to be fed to existing tools.</p>
<p>Unlike YAML, though, it anticipates large configurations by being programmable
and modular. To minimize the risk of misconfigurations, Nickel features (opt-in)
static typing and contracts, a powerful and extensible data validation
framework.</p>
<p>Since the initial release (0.1), we’ve refined the semantics enough to be
confident in a core design that is unlikely to radically change.</p>
<p>Since the previous stable version (<a href="https://www.tweag.io/blog/2022-12-20-announcing-nickel-0.3.1/">0.3.1</a>), efforts have
been made on three principal fronts: tooling (in particular the language
server), the core language semantics (contracts, metadata, and merging), and the
surface language (the syntax and the stdlib). Please see the <a href="https://github.com/tweag/nickel/blob/master/RELEASES.md">release
notes</a> for more details.</p>
<h2 id="tooling--set-up" style="position:relative;"><a href="#tooling--set-up" aria-label="tooling  set up 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 &#x26; set-up</h2>
<p>Follow the <a href="https://nickel-lang.org/getting-started/">getting started guide</a> from Nickel’s website
to get a working binary. Nickel also comes with:</p>
<ul>
<li>An <a href="https://github.com/tweag/nickel/tree/master/lsp">LSP</a> language server.</li>
<li>A REPL <code class="language-text">nickel repl</code>, a markdown documentation generator <code class="language-text">nickel doc</code> and a
<code class="language-text">nickel query</code> command to retrieve metadata, types and contracts from code.</li>
<li>Plugins for <a href="https://github.com/nickel-lang/vim-nickel">(Neo)Vim</a>, <a href="https://github.com/tweag/nickel/tree/master/lsp#vs-code">VSCode</a>,
<a href="https://github.com/nickel-lang/nickel-mode">Emacs</a>, and a <a href="https://github.com/nickel-lang/tree-sitter-nickel">tree-sitter grammar</a>.</li>
<li>A code formatter, thanks to Tweag’s tree-sitter-based <a href="https://topiary.tweag.io/">Topiary</a>.</li>
</ul>
<p>Watch the <a href="https://youtu.be/DzuoYmA2pd4">companion video</a> for a tour of these tools and features.</p>
<h2 id="a-primer-on-nickel" style="position:relative;"><a href="#a-primer-on-nickel" aria-label="a primer on nickel 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 primer on Nickel</h2>
<p>Let me walk you through a small example showing what you can do in Nickel 1.0.
You can try the code examples from this section in the <a href="https://nickel-lang.org/playground/">online Nickel
playground</a>.</p>
<h3 id="just-a-fancy-json" style="position:relative;"><a href="#just-a-fancy-json" aria-label="just a fancy json 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>Just a fancy JSON</h3>
<p>I’ll use a basic <a href="https://github.com/kubernetes/examples/blob/master/mysql-cinder-pd/">Kubernetes deployment of a MySQL
service</a> as a working example. The following is a
direct conversion of a good chunk of <code class="language-text">mysql.yaml</code> into Nickel syntax, omitting
uninteresting values:</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">apiVersion</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"v1"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">kind</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"Pod"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">metadata</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 property">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql"</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">labels.name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql"</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 property">spec</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 property">containers</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"> </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">resources</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"> </span><span class="token property">image</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql"</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 property">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql"</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 property">ports</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"> </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"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token property">containerPort</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">3306</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"> </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">"mysql"</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">}</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"> </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 property">volumeMounts</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"> </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"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token comment"># name must match the volume name below</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"> </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">"mysql-persistent-storage"</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"> </span><span class="token operator"> </span><span class="token comment"># mount path within the container</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"> </span><span class="token property">mountPath</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"/var/lib/mysql"</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">}</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"> </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">}</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 property">volumes</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"> </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">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql-persistent-storage"</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 property">cinder</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"> </span><span class="token property">volumeID</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"bd82f7e2-wece-4c01-a505-4acf60b07f4a"</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 property">fsType</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"ext4"</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"> </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></code></pre></div>
<p>This snippet looks like JSON, with a few minor syntax differences. Nickel has
indeed the same primitive data types as JSON: numbers (arbitrary precision
rationals), strings, arrays, and records (objects in JSON).</p>
<p>The previous example has a lot of repetition: the string <code class="language-text">"mysql"</code>, the name of
the app, occurs several times.</p>
<p>Besides, a comment mentions that the name inside the <code class="language-text">volumeMounts</code> field must
match the name of the volume defined inside <code class="language-text">volumes</code>. Developers are
responsible for maintaining this invariant by hand. The absence of a single
source of truth might lead to inconsistencies.</p>
<p>Finally, imagine that you now need to reuse the previous configuration several
times with slight variations, where the app name and the MySQL port number may change.</p>
<p>This can’t be solved in pure YAML: all you can do is to copy and paste data, and
try to manually ensure that copies always all agree. Unlike YAML though, Nickel
is programmable.</p>
<h3 id="reusable-configuration" style="position:relative;"><a href="#reusable-configuration" aria-label="reusable 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>Reusable configuration</h3>
<p>Let’s upgrade our very first example:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token comment"># mysql-module.ncl</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator"> </span><span class="token keyword">not_exported</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">p</span><span class="token operator">o</span><span class="token operator">r</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 operator"> </span><span class="token operator"> </span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token property">volume_name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql-persistent-storage"</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 property">apiVersion</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"v1"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">kind</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"Pod"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">metadata</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 property">name</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token property">labels.name</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 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">spec</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 property">containers</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"> </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">resources</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"> </span><span class="token property">limits.cpu</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">0.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"> </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">image</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql"</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 property">name</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 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 property">ports</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"> </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"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token property">containerPort</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">p</span><span class="token operator">o</span><span class="token operator">r</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 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 property">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql"</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">}</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"> </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 property">volumeMounts</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"> </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"> </span><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 operator">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">v</span><span class="token operator">o</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 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 comment"># mount path within the container</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"> </span><span class="token property">mountPath</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"/var/lib/mysql"</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">}</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"> </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">}</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 property">volumes</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"> </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">name</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">v</span><span class="token operator">o</span><span class="token operator">l</span><span class="token operator">u</span><span class="token operator">m</span><span class="token operator">e</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 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 property">cinder</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"> </span><span class="token property">volumeID</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"bd82f7e2-wece-4c01-a505-4acf60b07f4a"</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 property">fsType</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"ext4"</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"> </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></code></pre></div>
<p>The most important change is the new <code class="language-text">config</code> field. The rest is almost the
same as before, but I replaced the strings <code class="language-text">"mysql"</code> with <code class="language-text">config.app_name</code>,
<code class="language-text">"mysql-persistent-storage"</code> with <code class="language-text">config.volume_name</code> and the hard-coded port
number <code class="language-text">3306</code> with <code class="language-text">config.port</code>.</p>
<p>We can directly use the new fields <code class="language-text">config.xxx</code> from within other fields.
Indeed, in Nickel, you can refer to another part of a configuration you are
defining from inside the very same configuration. It’s a natural way to describe
data dependencies, when parts of the configuration are generated from other
parts.</p>
<p>The <code class="language-text">|</code> symbol attaches metadata to fields. Here, <code class="language-text">| not_exported</code> indicates
that <code class="language-text">config</code> is an internal value that shouldn’t appear in the final exported
YAML.</p>
<p>This explains most of the new machinery. But this configuration has still
something off. Let us try to export it:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nickel export -f mysql-module.ncl --format yaml
error: missing definition for `port`
   ┌─ mysql.ncl:33:36
   │
 2 │     config | not_exported = {
   │ ╭───────────────────────────&#39;
 3 │ │     port,
 4 │ │     app_name,
 5 │ │     volume_name = &quot;mysql-persistent-storage&quot;,
 6 │ │   },
   │ ╰───&#39; in this record
   · │
33 │               containerPort = config.port,
   │                               -------^^^^
   │                               │      │
   │                               │      required here
   │                               accessed here</code></pre></div>
<p>Indeed, <code class="language-text">port</code> and <code class="language-text">app_name</code> don’t have a value! In fact, you should view this
snippet as a <em>partial configuration</em>, a configuration with holes to be filled.</p>
<p>To recover a complete configuration, we need the merge operator <code class="language-text">&amp;</code>. Merging is
a primitive operation that recursively combines records together. Merge is also
able to provide a definite value to the fields <code class="language-text">app_name</code> and <code class="language-text">port</code>:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token comment"># file: mysql-final.ncl</span>
<span class="token operator">(</span><span class="token keyword">import</span><span class="token operator"> </span><span class="token string">"mysql-module.ncl"</span><span class="token operator">)</span><span class="token operator"> </span><span class="token operator">&amp;</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">config</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 property">app_name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql-backend"</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">port</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">10500</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>Running <code class="language-text">nickel export --format yaml -f mysql-final.ncl</code> will now produce a
valid YAML configuration.</p>
<p>Partial configurations bear similarities with functions: both are a solution to
the problem of how to make repetitive code reusable and composable. But the former
seems more adapted to writing reusable configuration snippets.</p>
<p>It’s trivial to assemble several partial configurations together (as long as
there’s no conflict): just merge them together.</p>
<p>Partial configurations are data, which can be queried, inspected, and
transformed, as long as the missing fields aren’t required. For example, you can
get the list of fields of our partial configuration:</p>
<div class="gatsby-highlight" data-language="console"><pre class="language-console"><code class="language-console">$ nickel query -f mysql-module.ncl

Available fields
• apiVersion
• config
• kind
• metadata
• spec</code></pre></div>
<p>Finally, partial configurations are naturally overridable. Recall that
<code class="language-text">mysql-final.ncl</code> contains the final complete configuration, and consider this
example:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token comment"># file: override.ncl</span>
<span class="token operator">(</span><span class="token keyword">import</span><span class="token operator"> </span><span class="token string">"mysql-final.ncl"</span><span class="token operator">)</span><span class="token operator"> </span><span class="token operator">&amp;</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">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator">.</span><span class="token operator">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 operator"> </span><span class="token keyword">force</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql_overridden"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">metadata.labels.overridden</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"true"</span><span class="token operator">,</span>
<span class="token operator">}</span></code></pre></div>
<p>Exporting <code class="language-text">override.ncl</code> produces a configuration where all the values depending
on <code class="language-text">config.app_name</code> (directly or indirectly) are updated to use the new value
<code class="language-text">"mysql_overridden"</code>, and where <code class="language-text">metadata.labels</code> has a new entry <code class="language-text">overridden</code>
set to <code class="language-text">"true"</code>.</p>
<p>Overriding is useful to tweak existing code that you don’t control, and wasn’t
written to be customizable in the first place. It’s probably better to do
without whenever possible, but for some application domains, it simply can’t be
avoided.</p>
<p>Partial configurations are automatically extensible: you don’t have to even think
about it.</p>
<p>On the other hand, functions are opaque values, which need to be fed with
arguments before you can do anything with them. Assembling many of them,
inspecting them before they are applied or making their result overridable range
from technically possible but much more cumbersome to downright impossible.</p>
<h3 id="correct-configurations" style="position:relative;"><a href="#correct-configurations" aria-label="correct configurations 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>Correct configurations</h3>
<p>Nickel has two main mechanisms to ensure correctness and prevent
misconfigurations as much as possible: static typing and contracts.</p>
<p>Static typing is particularly adapted for small reusable functions. The
typechecker is rigorous and catches errors early, before the code path is even
triggered.</p>
<p>For configuration data, we tend to use contracts. Contracts are a principled
way of writing and applying runtime data validators. It feels like typing when
used, relying on annotations and contract constructors (array contract, function
contract, etc.), but the checks are performed at runtime. In return, you can
easily define your own contracts and compose them with the existing ones.</p>
<p>Contracts are useful to validate the produced configuration (in that case, they
will probably come from a library or be automatically generated from external
schemas such as <a href="https://github.com/tweag/tf-ncl">when writing Terraform configurations in
Nickel</a>). They can validate inputs as well:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token comment"># file: mysql-module-safe.ncl</span>
<span class="token operator"></span><span class="token keyword">let</span><span class="token operator"> </span><span class="token property">StartsWith</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">p</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">f</span><span class="token operator">i</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 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">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator">r</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">t</span><span class="token operator">.</span><span class="token operator">f</span><span class="token operator">r</span><span class="token operator">o</span><span class="token operator">m</span><span class="token operator">_</span><span class="token operator">p</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">d</span><span class="token operator">i</span><span class="token operator">c</span><span class="token operator">a</span><span class="token operator">t</span><span class="token operator">e</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">"^%{prefix}"</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 operator">c</span><span class="token operator">o</span><span class="token operator">n</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">g</span><span class="token operator"> </span><span class="token operator">|</span><span class="token operator"> </span><span class="token keyword">not_exported</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">a</span><span class="token operator">p</span><span class="token operator">p</span><span class="token operator">_</span><span class="token operator">n</span><span class="token operator">a</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 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 class-name">String</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">S</span><span class="token operator">t</span><span class="token operator">a</span><span class="token operator">r</span><span class="token operator">t</span><span class="token operator">s</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">"mysql"</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 keyword">doc</span><span class="token operator"> </span><span class="token string">m%"
          The name of the mysql application. The name must start with `"mysql"`,
          as enforced by the `StartsWith` contract.
        "%</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 keyword">default</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">"mysql"</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">p</span><span class="token operator">o</span><span class="token operator">r</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 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 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 operator">|</span><span class="token operator"> </span><span class="token keyword">default</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 number">3306</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">volume_name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql-persistent-storage"</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 comment"># ...</span>
<span class="token operator">}</span></code></pre></div>
<p>The builtin <code class="language-text">String</code> contract and the custom contract <code class="language-text">StartsWith "mysql"</code>
have been attached to <code class="language-text">app_name</code>. The latter enforces that the value starts
with <code class="language-text">"mysql"</code>, if we have this requirement for some reason. We won’t enter the
details of custom contracts here, but it’s a pretty straightforward function.</p>
<p>We’ve used other metadata as well: <code class="language-text">doc</code> is for documentation, while <code class="language-text">default</code>
indicates that the following definition is a default value. Metadata are
leveraged by the Nickel tooling (the LSP, <code class="language-text">nickel query</code>, <code class="language-text">nickel doc</code>, etc.)</p>
<p>If we provide a faulty value for <code class="language-text">app_name</code>, the evaluation will raise a
proper error.</p>
<h3 id="going-further" style="position:relative;"><a href="#going-further" aria-label="going further 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>Going further</h3>
<p>You can look at the main <a href="https://github.com/tweag/nickel/">README</a> for a general description of the
project. The first <a href="https://www.tweag.io/blog/2020-10-22-nickel-open-sourcing/">blog post series</a> explains the
inception of Nickel, and the following posts focus on specific aspects of the
language. The most complete source remains the <a href="https://nickel-lang.org/user-manual/introduction/">user manual</a>.</p>
<h2 id="configure-your-configuration" style="position:relative;"><a href="#configure-your-configuration" aria-label="configure your 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>Configure your configuration</h2>
<p>Zooming out from technical details, I would like to paint a broader picture
here. My overall impression is that using bare YAML for e.g. Kubernetes is like
programming in assembly. It’s certainly doable, but not desirable. It’s tedious,
low-level, and lacks the minimal abstractions to make it scalable.</p>
<p>Some solutions do make configuration more flexible: YAML templating (Helm),
tool-specific configuration languages (HCL), etc. But many of them feel like a
band-aid over pure JSON or YAML which somehow accidentally grew up to become
semi-programming languages.</p>
<p>Our final example is a snippet mapping a pair of high-level values — the only
values we care configuring, <code class="language-text">app_name</code> and <code class="language-text">port</code> — to a “low-level” Kubernetes
configuration. Once written, the only remaining thing to do is to… <strong>configure
your configuration</strong>!</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><span class="token operator">(</span><span class="token keyword">import</span><span class="token operator"> </span><span class="token string">"mysql-module.ncl"</span><span class="token operator">)</span><span class="token operator"> </span><span class="token operator">&amp;</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">config.app_name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"mysql_backup"</span><span class="token operator">,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">config.port_number</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token number">10400</span>
<span class="token operator">}</span></code></pre></div>
<p>Nickel allows to provide a well-defined interface for reusable parts of
configuration, while still providing an escape hatch to override anything else
when you need it. Such configuration snippets can be reused, composed, and
validated thanks to types and contracts. Although Nickel undeniably brings in
complexity, Nickel might paradoxically empower users to make configuration
simple again.</p>
<p>If JSON is enough for your use-case, that’s perfect! There’s really no better
place to be. But if you’ve ever felt underequipped to handle, write and evolve
large configurations, Nickel might be for you.</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 are happy to announce the 1.0 milestone for the Nickel configuration
language. You can use it wherever you would normally use JSON, YAML, or TOML,
but feel limited by using static text or ad-hoc templating languages. You can
use it if your configuration is spread around many tools and ad-hoc
configuration languages. Nickel could become one fit-them-all configuration
language, enabling the sharing of the abstractions, code, schemas (contracts)
and tooling across all your stack. Don’t hesitate to check the
<a href="https://youtu.be/DzuoYmA2pd4">companion video</a> to help you getting set up.</p>
<p>In our next blog post, we’ll show how to configure Terraform providers using
Nickel, and thereby gain the ability to check the code against provider-specific
contracts, ahead of performing the deployments. Stay tuned!</p>
<p>Your feedback, ideas, and opinions are invaluable: please use Nickel, break it,
do cool things we haven’t even imagined, and most importantly, please let us
know about it! Email us at <a href="mailto:hello@tweag.io">hello@tweag.io</a> or go to Nickel’s open
source <a href="https://github.com/tweag/nickel/">GitHub repository</a>.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">L. A. Barroso, U. Hölzle, and P. Ranganathan. The Datacenter as a Computer: An Introduction to the Design of Warehouse-scale Machines (Third Edition). Morgan and Claypool Publishers, 2018.<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">C. Tang, T. Kooburat, P. Venkatachalam, A. Chander, Z. Wen, A. Narayanan, P. Dowell, and R. Karl. Holistic Configuration Management at Facebook, in Proceedings of the 25th ACM Symposium on Operating System Principles (SOSP’15), October 2015<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-4"><a href="https://techcrunch.com/2019/03/14/facebook-blames-a-misconfigured-server-for-yesterdays-outage/"><em>Facebook blames a misconfigured server for yesterday’s outage</em>, TechCrunch</a><a href="#fnref-4" class="footnote-backref">↩</a></li>
<li id="fn-3"><a href="https://www.fastly.com/blog/summary-of-june-8-outage">Fastly’s statement</a><a href="#fnref-3" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2023-05-17-nickel-1.0-release/</link><guid isPermaLink="false">https://tweag.io/blog/2023-05-17-nickel-1.0-release/</guid><pubDate>Wed, 17 May 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Taking the pulse of infrastructure management in 2023]]></title><description><![CDATA[<p>February started with a busy week for several of us Tweagers. We went as a group
— or, dare I say, a delegation — to <a href="https://fosdem.org/2023/">FOSDEM23</a> and
<a href="https://cfgmgmtcamp.eu/ghent2023/">CfgMgmtCamp23</a> (Config Management Camp if, like me, you can’t
read through so many consonants). Tweagers <a href="https://github.com/bryanhonof">Bryan Honof</a> and
<a href="https://github.com/thufschmitt">Théophane Hufschmitt</a>, together with <a href="https://github.com/RaitoBezarius">Ryan
Lahfa</a>, <a href="https://github.com/JulienMalka">Julien Malka</a> and <a href="https://twitter.com/matthewcroughan">Matthew
Croughan</a> from the Nix community, got us the first Nix DevRoom
at FOSDEM. And not the last, as you can see from the picture below; the room
just couldn’t handle the Nix fame!</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/08f908d5df88d3e0f3ca7228349d492c/d2602/fosdem-nixdevroom-1.jpg"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEAf/EABYBAQEBAAAAAAAAAAAAAAAAAAIAAf/aAAwDAQACEAMQAAABmfHoTCQ2/8QAGhAAAQUBAAAAAAAAAAAAAAAAAQACAxESIv/aAAgBAQABBQISOWjTpaMnLSTnRX//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAgEBPwFn/8QAGRAAAgMBAAAAAAAAAAAAAAAAABEBMTJB/9oACAEBAAY/AssyVJKFxln/xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMZFBcf/aAAgBAQABPyH3n4mws5AqeUSrFEhqXCpLdqf/2gAMAwEAAgADAAAAEDvP/8QAFhEAAwAAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EFCo/8QAFhEBAQEAAAAAAAAAAAAAAAAAABEh/9oACAECAQE/EKxH/8QAHRABAAICAgMAAAAAAAAAAAAAAQARITFRYaHR8P/aAAgBAQABPxAdB3Lz5hUVPftKHzX3cQpqKdmYySLDgi+7Wt1P/9k='); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="FOSDEM Nix Dev Room #1"
        title="FOSDEM Nix Dev Room #1"
        src="/static/08f908d5df88d3e0f3ca7228349d492c/1c72d/fosdem-nixdevroom-1.jpg"
        srcset="/static/08f908d5df88d3e0f3ca7228349d492c/a80bd/fosdem-nixdevroom-1.jpg 148w,
/static/08f908d5df88d3e0f3ca7228349d492c/1c91a/fosdem-nixdevroom-1.jpg 295w,
/static/08f908d5df88d3e0f3ca7228349d492c/1c72d/fosdem-nixdevroom-1.jpg 590w,
/static/08f908d5df88d3e0f3ca7228349d492c/a8a14/fosdem-nixdevroom-1.jpg 885w,
/static/08f908d5df88d3e0f3ca7228349d492c/fbd2c/fosdem-nixdevroom-1.jpg 1180w,
/static/08f908d5df88d3e0f3ca7228349d492c/d2602/fosdem-nixdevroom-1.jpg 4032w"
        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>
<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/28423438a1bdf9b8e4b95f8055a6e093/d2602/fosdem-nixdevroom-2.jpg"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABAACA//EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAEuiIiusaH/xAAZEAADAQEBAAAAAAAAAAAAAAABAgMAEyH/2gAIAQEAAQUCNSpFm3V8fbLnoyP/AP/EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPwGNf//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAgEBPwGsf//EABoQAAICAwAAAAAAAAAAAAAAAAABAjERMnH/2gAIAQEABj8ClhqykaEujGos/8QAHBAAAgICAwAAAAAAAAAAAAAAAAERMUFxgcHx/9oACAEBAAE/IauEIGc3J5WXOTTTsZJSk//aAAwDAQACAAMAAAAQCA//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAwEBPxCEP//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAECAQE/ECn/xAAdEAEBAAIBBQAAAAAAAAAAAAABEQAhMUFxgbHB/9oACAEBAAE/EKullLZXrZiaXjNLADk+H5gAbiXAXhTRnZgxXwg+8//Z'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="FOSDEM Nix Dev Room #2"
        title="FOSDEM Nix Dev Room #2"
        src="/static/28423438a1bdf9b8e4b95f8055a6e093/1c72d/fosdem-nixdevroom-2.jpg"
        srcset="/static/28423438a1bdf9b8e4b95f8055a6e093/a80bd/fosdem-nixdevroom-2.jpg 148w,
/static/28423438a1bdf9b8e4b95f8055a6e093/1c91a/fosdem-nixdevroom-2.jpg 295w,
/static/28423438a1bdf9b8e4b95f8055a6e093/1c72d/fosdem-nixdevroom-2.jpg 590w,
/static/28423438a1bdf9b8e4b95f8055a6e093/a8a14/fosdem-nixdevroom-2.jpg 885w,
/static/28423438a1bdf9b8e4b95f8055a6e093/fbd2c/fosdem-nixdevroom-2.jpg 1180w,
/static/28423438a1bdf9b8e4b95f8055a6e093/d2602/fosdem-nixdevroom-2.jpg 4032w"
        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>Our involvement didn’t end there. At FOSDEM:</p>
<ul>
<li>Théophane <a href="https://youtu.be/_uPnVNoghzo">presented the new Nix (core) team</a>.</li>
<li><a href="https://twitter.com/gdforj">Guillaume Desforges</a> lightened the mood with his
enthusiastic talk on <a href="https://youtu.be/FdxvWFDwgZw">making anyone use Nix</a>.</li>
<li>I went on to <a href="https://youtu.be/E4VtKSrSSu8">introduce Nixel</a>, a framework to write Nix
expressions in Nickel.</li>
</ul>
<p>Followed right away by CfgMgmtCamp:</p>
<ul>
<li>Bryan first did a lightning talk to tease Nix, then took curious people on a
deeper dive during a 50-minute gentle introduction to Nix.</li>
<li><a href="https://github.com/vkleen">Viktor Kleen</a> presented his work on <a href="https://github.com/tweag/tf-ncl">Terraform-Nickel</a>,
a library to use Nickel in place of HCL for Terraform.</li>
<li>I talked about how to write modular and customizable configuration using Nickel’s
merging system.</li>
</ul>
<p>What follows are my takeaways on the trends and the trajectory of infrastructure
and configuration management. Please don’t take my word as a confident
prediction, as predictions don’t age well. This post is merely a patchwork of my
biased, fresh impressions — as a programming language nerd
rabbit-holed into devops and infrastructure by Nix — on my way back home.</p>
<h2 id="self-service-infrastructure" style="position:relative;"><a href="#self-service-infrastructure" aria-label="self service infrastructure 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>Self-service infrastructure</h2>
<p>Several key speakers at CfgMgmtCamp emphasized how much infrastructure is
critical for many business services and how, more often than not, it is a
bottleneck in practice.</p>
<p>Developers must be able to spin up new services rapidly, not in hours or days.
They can’t wait one week for someone from the infra team to finally find time,
between daily maintenance tasks, to hit the green button (or do something more
complex), potentially followed by a long painful back-and-forth. Non-technical
roles, such as marketing and sales, might also need to get a small website or a
service up, but are even less empowered.</p>
<p>This doesn’t mean one should give everyone full access to the infrastructure.
I’ve rather heard a call for simpler, safer, and better automated processes. For
example, having a simple web interface for spinning up instances, with a clear
policy on cleaning inactive resources. The interface shouldn’t require much
infrastructure knowledge nor expose low-level options: this isn’t simply about
granting people access to an AWS console. If users are developers, this can be
achieved using infrastructure as code as well, with adapted restrictions.</p>
<p>Continuous integration, continuous delivery, one-off experiments — all those
components should be automated, fully integrated in the software development
workflow, and just a few clicks (or keystrokes, for you Vimmers) away.</p>
<h2 id="yaml-is-dead" style="position:relative;"><a href="#yaml-is-dead" aria-label="yaml is dead 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>YAML is dead</h2>
<p>I’m sorry, that was just clickbait. YAML has been pronounced dead for the last 10
years or so, and unfortunately, here it stands.</p>
<p>But something is in the air. There is an undeniable movement toward a unified
and structured approach to writing and managing configuration.</p>
<p>Scattering configuration data, schemas and knowledge across many different tools,
written in many different languages (HCL, YAML, JSON, TOML, Puppet, Ansible,
Helm, etc.) isn’t sustainable. And I feel like we can state a variant of
<a href="https://wiki.c2.com/?GreenspunsTenthRuleOfProgramming">Greenspuns’ tenth rule of programming</a> for
configuration:</p>
<blockquote>
<p>Every sufficiently complicated infrastructure tool’s native configuration
language is an ad-hoc, informally-specified, bug-ridden, arbitrarily-limited
reinvention of half of CommonLisp.</p>
</blockquote>
<p>This is exactly why we created <a href="https://github.com/tweag/nickel">Nickel</a>. Projects that were the new kids
on the block a few years ago — I’m thinking about <a href="https://www.pulumi.com/">Pulumi</a> or <a href="https://cuelang.org/">CUE</a>,
for example — are gaining strength and run today in production. Those are not
curiosities any more. The actors behind those tools seem to share the same
dissatisfaction with the state of configuration management and infrastructure
engineering. While our experience and vision led us to come up with our own
answer (Nickel), we are all trying to unify configuration management by using a
common language or platform.</p>
<h2 id="declarative-but-what-about-interactive" style="position:relative;"><a href="#declarative-but-what-about-interactive" aria-label="declarative but what about interactive 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>Declarative, but what about interactive?</h2>
<p><a href="https://twitter.com/adamhjk?s=20">Adam Jacob’s</a> opening talk on Breaking The Rules, as well
as <a href="https://twitter.com/ringods">Ringo De Smet’s</a> talk on Pulumi’s <em>imperative on top of
declarative</em> approach made me think about the user experience aspect of
declarative configuration. I’m still convinced that the declarative approach is
ultimately the right one, or the only sustainable one to this day.</p>
<p>However, there is something to be said about offering different interfaces
to this declarative layer: Pulumi is one example, providing an imperative API to
manipulate it. <code class="language-text">npm add</code> is another simple example of an imperative interface to
a declarative configuration (<code class="language-text">packages.json</code>).</p>
<p>Adam introduced the notion of “infrastructure as a model”, instead of code, to
which code would only be one possible interface. Which reminded me of similar
internal discussion at Tweag about
<a href="https://martinfowler.com/bliki/ProjectionalEditing.html">projectional editors</a>,
which is in fact the very same idea but applied to programming in general.
Textual code is just one view, but other views — such as a graphical interface —
of the abstract representation of a program are also possible, and more
desirable for some tasks (refactoring, renaming, diffing, visualizing, …).
Typically, a diagrammatic view is often better than text at clarifying
the relations and dependencies between the entities being managed (e.g.
Terraform resources). To each task, its projection?</p>
<h2 id="nix-is-getting-stronger-every-year" style="position:relative;"><a href="#nix-is-getting-stronger-every-year" aria-label="nix is getting stronger every year 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 is getting stronger every year</h2>
<p><a href="https://mobile.twitter.com/ronefroni">Ron Efroni</a>, from Flox, often makes the half-joke that the number of
GitHub stars on the Nix repos are following Moore’s law: they double every two
years. So far, he’s mostly right.</p>
<p>I was impressed by the number of people flooding the Nix DevRoom at FOSDEM (the
pictures at the beginning of this post), queuing in the corridor to wait for a
free spot.</p>
<p>At CfgMgmtCamp, Bryan’s lightning talk on Nix took place on the main track
during the very first morning. The (huge) auditorium was full, fitting several
hundred people. When he asked who knew about Nix, I expected a solid 50 people
to raise their hand. More than half, probably around two third of the audience,
raised their hand!</p>
<h2 id="nix-is-vibrant" style="position:relative;"><a href="#nix-is-vibrant" aria-label="nix is vibrant 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 is vibrant</h2>
<p>A lot of cool things are happening in the Nix world. The ecosystem is evolving
at a fast pace. The NixOS Foundation, itself rather new, is structuring to
handle the change of scale, for example with a dedicated
<a href="https://discourse.nixos.org/t/nix-team-creation/22228">Nix maintenance team</a>.
<a href="https://floxdev.com/">Flox</a> just released the first version of their product.
<a href="https://twitter.com/domenkozar">Domen Kozar</a> introduced <a href="https://devenv.sh/">devenv.sh</a>, which has gained
traction rapidly. We presented <a href="https://github.com/nickel-lang/nickel-nix">Nixel</a>, the starting point of a whole new
user experience for writing Nix.</p>
<h2 id="nickel" style="position:relative;"><a href="#nickel" aria-label="nickel 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>Nickel</h2>
<p>We got a lot of encouraging words after our various talks on <a href="https://github.com/tweag/nickel">Nickel</a>.
The project has been making steady progress in the long run, but the hard and
technical work on the core language, the type system and the semantics of
merging is naturally less palpable.</p>
<p>However, we have reached the point from where we can start (and have started, in
fact) to harvest impactful outcomes, which is exciting. A handful of months’ work
on the LSP by our own Gaga Ebresafe and we are already able to provide
completions based on definitions, types and contracts, in-code documentation,
code navigation and typechecking live in the editor, which is quite ahead of the
current Nix experience. Although it’s only the beginning, we can already write
basic derivations and modular and overridable development shells using
<a href="https://github.com/nickel-lang/nickel-nix">Nixel</a>. Daniele Palombi is doing an internship to bring incremental
evaluation and self-adjusting computations to the interpreter, for fast
re-evaluation of a configuration upon a small change.</p>
<h2 id="concluding-words" style="position:relative;"><a href="#concluding-words" aria-label="concluding words 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>Concluding words</h2>
<p>It’s hard to say where the current momentum will bring us. Getting people
excited and aware of a cool technology is not the same as making it established
and used in production. There are many non-technical determining factors, even
irrational ones. Nonetheless, it’s clear that infrastructure engineering is
distinguished by the ebullience of its youth. It’s an exciting time to be around!</p>]]></description><link>https://tweag.io/blog/2023-02-23-infrastructure-pulse-2023/</link><guid isPermaLink="false">https://tweag.io/blog/2023-02-23-infrastructure-pulse-2023/</guid><pubDate>Thu, 23 Feb 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[opam-nix: Nixify Your OCaml Projects]]></title><description><![CDATA[<p><a href="https://opam.ocaml.org/">opam</a> is a source-based package manager for <a href="https://ocaml.org">OCaml</a>. It is the de-facto
standard for package management in the OCaml ecosystem. <a href="https://opam.ocaml.org/packages/">opam’s main package
repository</a> contains over 4000 individual
packages, on average spanning 7 versions each.</p>
<p>Like many other language-specific package managers (e.g. cargo, cabal, etc.), opam
performs four main tasks:</p>
<ol>
<li>Download the sources.</li>
<li>Resolve the needed dependencies of a package.</li>
<li>Provide those dependencies such that the build system can find them.</li>
<li>Run the build system.</li>
</ol>
<p>It is pretty good at this. However, there are some problems with step (4):</p>
<ul>
<li>The build is not properly isolated: it can (in theory) fetch arbitrary things
from the network, access arbitrary files on the filesystem, and even modify
other packages. This allows for irreproducible builds, and makes it easy to
forget to explicitly list a system dependency if it happens to be installed
on the author’s system.</li>
<li>“External” (system) dependencies, such as non-OCaml binaries and libraries,
are taken from the user’s distribution repository (using the distribution’s
package manager, e.g. <code class="language-text">apt-get</code>), resulting in version inconsistencies and
the possibility for breakage.</li>
<li>The builds are not easily cached and reused, meaning you have to compile
packages locally.</li>
</ul>
<p>Also, opam’s user interface is based around imperative commands, meaning
that the developer environment setup has to be a script that modifies a <em>switch</em>
(opam’s term for an independent collection of interdependent packages), which
is fragile, difficult to update, and prone to inconsistencies. While there are
<a href="https://github.com/tweag/check_opam_switch">tools</a> that solve some of those
issues, this can still be painful.</p>
<p>Finally, opam is a package manager for OCaml. It can’t easily integrate with
other programming languages. This is a problem for modern software stacks,
which often feature multiple programming languages in a single project.</p>
<h1 id="introducing-opam-nix" style="position:relative;"><a href="#introducing-opam-nix" aria-label="introducing opam 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>Introducing <code class="language-text">opam-nix</code></h1>
<p>If you’re familiar with Nix, you might have noticed that it doesn’t have any
of the aforementioned problems. However, Nix on its own doesn’t know how to
build opam packages. The solution to this? Make a library that “translates” opam
packages into a format which Nix can understand. That’s exactly what <a href="https://github.com/tweag/opam-nix"><code class="language-text">opam-nix</code></a> is!</p>
<p><code class="language-text">opam-nix</code> provides low-level functions which parse <a href="https://opam.ocaml.org/doc/Manual.html#Common-file-format">opam files</a> into Nix data structures (using <a href="https://github.com/ocaml/opam-file-format">opam-file-format</a>), interpret those
data structures, resolve the dependency tree (using opam itself), and turn the
dependency tree into derivations. It also has some overrides which ensure that a
lot of popular packages build and function correctly.</p>
<p>If that sounds scary, don’t worry, <code class="language-text">opam-nix</code> also provides high-level tools
which make Nixifying your OCaml projects a breeze. In this blog post, we’ll
show some examples of how to quickly Nixify your existing opam-based projects.
Whether you’re working on improving the onboarding experience on a project or are an
OCaml developer yourself, we hope you’ll find it useful.</p>
<p>In the following, we assume that you already <a href="https://nixos.org/download.html">have Nix installed</a> and are using
<a href="https://www.tweag.io/blog/2020-05-25-flakes/">flakes</a>. Also, the templates are suited to building opam projects.</p>
<h2 id="simple-package" style="position:relative;"><a href="#simple-package" aria-label="simple package 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>Simple package</h2>
<p>To get started, fetch the template provided with <code class="language-text">opam-nix</code>. From your project’s
root:</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ nix flake init <span class="token parameter variable">-t</span> github:tweag/opam-nix
$ <span class="token function">git</span> <span class="token function">add</span> flake.nix</code></pre></div>
<p>Nix will create a <code class="language-text">flake.nix</code> for you. Note that you have to add it to the Git
index, otherwise Nix will not pick it up. Open it with your editor, look through
the file, and replace the <code class="language-text">throw</code> with your package name, as specified in the
comment:</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>  outputs = { self, flake-utils, opam-nix, nixpkgs }@inputs:
</span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span>    # Don't forget to put the package name instead of `throw':
<span class="token prefix deleted">-</span>    let package = throw "Put the package name here!";
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>    let package = "my-package";
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>    in flake-utils.lib.eachDefaultSystem (system:</span></code></pre></div>
<p>Nix is famous for its “shells”: ad-hoc, on-the-fly environments, allowing
developers to quickly get started on and switch between projects. <code class="language-text">opam-nix</code>
allows you to leverage this potential to make onboarding to your projects quick
and easy.</p>
<p>Now run</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ nix develop</code></pre></div>
<p>Nix will download and lock opam-nix, nixpkgs, opam-repository,
and some other dependencies, then build all the dependencies of your project. Once
that’s done, it will drop you into a shell with all the dependencies available.</p>
<p>Unfortunately, that won’t always happen; <code class="language-text">opam-nix</code> can’t provide perfect
compatibility with opam. Most errors you will get are actually symptoms of
problematic packaging of your dependencies, e.g. missing a system dependency
requirement, arbitrary network access during the build, etc. The proper way to
fix such errors is to fix the packaging upstream. However, you can also just
override the dependency using the <code class="language-text">overlay</code> in your <code class="language-text">flake.nix</code>. You can read
more about <a href="https://nixos.org/manual/nixpkgs/stable/#chap-overlays">overlays</a> and <a href="https://nixos.org/manual/nixpkgs/stable/#chap-overrides">package overrides</a> if you are not familiar. For
example, you can change the commands used to build a package like this:</p>
<div class="gatsby-highlight" data-language="diff"><pre class="language-diff"><code class="language-diff"><span class="token unchanged"><span class="token prefix unchanged"> </span>        overlay = final: prev:
<span class="token prefix unchanged"> </span>          {
<span class="token prefix unchanged"> </span>            # Your overrides go here
</span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span>            dune = prev.dune.overrideAttrs (_: { buildPhase = "make release"; });
</span><span class="token unchanged"><span class="token prefix unchanged"> </span>          };
<span class="token prefix unchanged"> </span>      in {
<span class="token prefix unchanged"> </span>        legacyPackages = scope.overrideScope' overlay;</span></code></pre></div>
<p>Additionally, if your project requires libraries or tools written in other
languages, you can <a href="https://search.nixos.org/packages">look for them in nixpkgs</a> or package them
with Nix (maybe using other *-nix tools). Once you have the package, you can
simply then inject it into <code class="language-text">buildInputs</code> of your project using <code class="language-text">overrideAttrs</code>
inside an <code class="language-text">overlay</code>.</p>
<p>Once you get to the shell, you can use the usual tools to build your package.
Typically, that would be something like <code class="language-text">dune build</code> or <code class="language-text">make</code>. You can also use
<a href="https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases">nixpkgs phases</a> to build and test your project using commands specified in the
opam file:</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ <span class="token builtin class-name">eval</span> <span class="token string">"<span class="token variable">$prePatch</span>"</span>
$ <span class="token builtin class-name">eval</span> <span class="token string">"<span class="token variable">$configurePhase</span>"</span>
$ <span class="token builtin class-name">eval</span> <span class="token string">"<span class="token variable">$buildPhase</span>"</span>
$ <span class="token builtin class-name">eval</span> <span class="token string">"<span class="token variable">$checkPhase</span>"</span></code></pre></div>
<p>Note that this approach combines the benefits of Nix (reproducibility, reliable
caching, isolation) for your dependencies with the benefits of your build system
(fast, incremental builds, granular caching) for your project itself.</p>
<p>If you want to go all-in on Nix, you can build your project as a Nix derivation
too. Just run:</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ nix build</code></pre></div>
<p>This will build and check your project. You can find the build artifacts in the
<code class="language-text">result</code> folder.</p>
<h2 id="fully-featured-development-environment" style="position:relative;"><a href="#fully-featured-development-environment" aria-label="fully featured development environment 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>Fully-featured development environment</h2>
<p>Many developers are not content with simply being able to build the package,
though; they also want to have modern amenities such as a language server to get
interactive documentation, type information, and code navigation.</p>
<p>Worry not! <code class="language-text">opam-nix</code> can also help with that. We’ll use a different template
here, so make sure to back up any changes to <code class="language-text">flake.nix</code> you might have made in
the previous section.</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ <span class="token function">mv</span> flake.nix flake.nix.bkp
$ nix flake init <span class="token parameter variable">-t</span> github:tweag/opam-nix<span class="token comment">#multi-package</span>
$ <span class="token function">git</span> <span class="token function">add</span> flake.nix</code></pre></div>
<p>Replicate the overrides you made before, if any. You don’t need to specify
the package name here, since this template picks up all packages in your
repository. It’s also a good idea to look through <code class="language-text">flake.nix</code>, as it might give
you ideas for improving your development experience.</p>
<p>You can now use</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">nix develop</code></pre></div>
<p>to get a development environment, as before.</p>
<p>However, now you also get <code class="language-text">ocaml-lsp-server</code> and <code class="language-text">ocamlformat</code> available. You
can add other OCaml tools to the <code class="language-text">devPackagesQuery</code> as well. For your editor to
pick them up, you’ll have to start it from this environment. Alternatively, since
this template provides an <code class="language-text">.envrc</code>, you can use <a href="https://direnv.net/">direnv</a>, which has integrations
with many popular editors.</p>
<p>Note that for <code class="language-text">ocaml-lsp</code> to work, it must be able to find type information for
your project. Typically, you can ensure this by running</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ dune build @check</code></pre></div>
<p>If you wish, you can also build your package with:</p>
<div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">$ nix build <span class="token builtin class-name">.</span><span class="token comment">#&lt;your-package></span></code></pre></div>
<h2 id="diving-deeper" style="position:relative;"><a href="#diving-deeper" aria-label="diving deeper 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>Diving deeper</h2>
<p>Since <code class="language-text">opam-nix</code> is a Nix tool, you get a lot of advantages of Nix, like
reproducibility, <a href="https://www.tweag.io/blog/2020-07-08-buildkite-for-nix-ci/">easy CI with binary caching</a> for even faster
developer onboarding, integration with <a href="https://nixos.org">NixOS</a> to get reproducible system
deployments, or with <a href="https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTools">Docker</a> for compatibility with most of the world.</p>
<p>Also, because <code class="language-text">opam-nix</code> follows the philosophy of composing small, low-level
parts to make a bigger, user-friendly whole, you can make use of it
even if your project doesn’t fit the requirements of <code class="language-text">buildOpamProject</code>.</p>
<p>For <a href="https://dune.readthedocs.io/en/stable/dune-files.html#dune-project-1"><code class="language-text">dune-project</code></a>-based projects, you can use <a href="https://github.com/tweag/opam-nix#buildduneproject"><code class="language-text">buildDuneProject</code></a> instead
of <code class="language-text">buildOpamProject</code>.</p>
<p>If you just have a single opam export, that’s then imported with <code class="language-text">opam import</code>.
This setup can be replicated with a snippet like this:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">let</span>
  <span class="token comment"># This is a list of package names installed in the switch</span>
  switch <span class="token operator">=</span> opam<span class="token operator">-</span>nix<span class="token punctuation">.</span>opamListToQuery <span class="token punctuation">(</span>opam<span class="token operator">-</span>nix<span class="token punctuation">.</span>fromOPAM <span class="token url">./opam.export</span><span class="token punctuation">)</span><span class="token punctuation">.</span>installed<span class="token punctuation">;</span>

  scope <span class="token operator">=</span> <span class="token keyword">with</span> opam<span class="token operator">-</span>nix<span class="token punctuation">;</span>
    queryToScope <span class="token punctuation">{</span> repos <span class="token operator">=</span> <span class="token punctuation">[</span> opamRepository <span class="token punctuation">(</span>makeOpamRepo <span class="token url">./.</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>switch <span class="token operator">//</span> <span class="token punctuation">{</span> my<span class="token operator">-</span>package <span class="token operator">=</span> <span class="token string">"dev"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">in</span> scope<span class="token punctuation">.</span>my<span class="token operator">-</span>package</code></pre></div>
<p>Or, if you want to build a <a href="https://mirage.io/">Mirage unikernel</a>, you can
do so using <a href="https://github.com/RyanGibb/hillingar">hillingar</a>, an <code class="language-text">opam-nix</code>-
based tool.</p>
<p>If you want to speed up your project by avoiding <a href="https://nixos.wiki/wiki/Import_From_Derivation">Import From Derivation</a>,
<code class="language-text">opam-nix</code> <a href="https://github.com/tweag/opam-nix/blob/main/README.md#materialization">supports</a> <code class="language-text">haskell.nix</code>-style materialization.</p>
<p>Also, if you’re using <a href="https://jupyenv.io/documentation/options/#kernel-ocaml">jupyenv’s OCaml kernel</a>, you’re actually
using <code class="language-text">opam-nix</code> under the hood; this means all the tricks mentioned previously
can also work there</p>
<p>You can check out the documentation for all the public functions provided by
<code class="language-text">opam-nix</code> in <a href="https://github.com/tweag/opam-nix/blob/main/README.md">the README</a>. Let us know if you make something cool with this!</p>
<h2 id="inspirations--alternatives" style="position:relative;"><a href="#inspirations--alternatives" aria-label="inspirations  alternatives 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>Inspirations &#x26; alternatives</h2>
<p><code class="language-text">opam-nix</code> wouldn’t be possible without <a href="https://opam.ocaml.org/">opam</a> and <a href="https://github.com/ocaml/opam-file-format">opam-file-format</a>. It uses
them directly and reimplements some parts of them in Nix.</p>
<p>The way <code class="language-text">opam-nix</code> works is similar to <a href="https://github.com/kolloch/crate2nix/">crate2nix</a>, <a href="https://github.com/NixOS/cabal2nix">cabal2nix</a>, and
<a href="https://github.com/nix-community/poetry2nix">poetry2nix</a>. Inspiration for many technical decisions was drawn from these
projects.</p>
<p>Finally, there are a couple of similar projects which serve a similar purpose
but achieve it slightly differently.</p>
<ul>
<li><a href="https://github.com/timbertson/opam2nix">opam2nix</a> is the original in this space. However, it requires committing
generated files into the repository, doesn’t integrate well with Flakes, and
is not as flexible.</li>
<li><a href="https://github.com/vapourismo/opam-nix-integration">opam-nix-integration</a> is quite similar to opam-nix. However, it’s not
as flexible since it doesn’t allow to import opam switches or easily call
multiple packages from the same workspace.</li>
</ul>
<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><code class="language-text">opam-nix</code> is a flexible yet user-friendly library to turn opam packages
into Nix derivations. It is already mature enough to be used by <a href="https://github.com/ocaml/dune/blob/ce578be06da8a78655200c56b345ed9e50be9dec/flake.nix#L8">dune</a>, <a href="https://github.com/ocaml/ocaml-lsp/blob/1423a90b236f7c907690e8ad4269d5dc617a3ede/flake.nix#L6">ocaml-lsp</a>,
and others, and yet there are still features and interface improvements
waiting to happen. We encourage you to try it on your project and share your
experience and feedback in the <a href="https://github.com/tweag/opam-nix/issues">issue tracker</a>.</p>]]></description><link>https://tweag.io/blog/2023-02-16-opam-nix/</link><guid isPermaLink="false">https://tweag.io/blog/2023-02-16-opam-nix/</guid><pubDate>Thu, 16 Feb 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Running a NixOS VM on macOS]]></title><description><![CDATA[<p>In this post I want to explore the current issues with developing parts of NixOS on
macOS and how we can make this task easier.</p>
<h2 id="why-would-i-want-to-run-a-nixos-virtual-machine-on-macos" style="position:relative;"><a href="#why-would-i-want-to-run-a-nixos-virtual-machine-on-macos" aria-label="why would i want to run a nixos virtual machine on macos 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 would I want to run a NixOS virtual machine on macOS?</h2>
<p>My colleague at Tweag, Dominic Steinitz, asked me this question after I shared
my first minor achievement in this area, and it struck me that I have never
described why exactly I run virtual machines (VMs) on my laptop and why I want
to make it easier for myself (and others).</p>
<p>Like many members of the Nix community – more than 25% according to the <a href="https://discourse.nixos.org/t/2022-nix-survey-results/18983">Nix
community survey</a> – I rely on macOS to provide me a stable
shiny desktop environment. However NixOS is based on Linux and so many pieces
that are used for developing and testing it require running it in VMs. I’ll
describe some major use cases for it below.</p>
<h3 id="local-remote-builder" style="position:relative;"><a href="#local-remote-builder" aria-label="local remote builder 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>Local remote builder</h3>
<p>macOS is a great desktop environment, but as soon as you move beyond your
machine, be it to a remote server, VM or container, you most likely end up in
Linux, which is a different system. Practically speaking, this means that
a derivation for any of them requires a Linux machine with Nix to be
built.</p>
<p>Therefore, you have to set up a <a href="https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html">remote builder</a>, either on
some other machine (in the cloud, some datacenter, or under your desk), or in a
VM on your machine. If you have a powerful enough machine, you might want to
opt for the latter, reaching for either running a complete Linux VM in
VirtualBox, UTM, Parallels or vmWare, or using <a href="https://github.com/LnL7/nix-docker#running-as-a-remote-builder">nix-docker</a> to run
it in Docker’s VM or <a href="https://github.com/nix-community/linuxkit-nix">linuxkit-nix</a> to spawn a minimal Linux VM
with only Nix on board.</p>
<p>Most of these options require you to install some app that will manage VMs for
you and most of those are proprietary. As for <a href="https://github.com/nix-community/linuxkit-nix">linuxkit-nix</a>,
<a href="https://github.com/Gabriella439">Gabriella Gonzales</a> wrote in her
<a href="https://www.haskellforall.com/2022/12/nixpkgs-support-for-linux-builders.html">blog post</a> a great overview of how it is lacking in
different areas.</p>
<h3 id="test-nixos-configurations-locally" style="position:relative;"><a href="#test-nixos-configurations-locally" aria-label="test nixos configurations locally 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>Test NixOS configurations locally</h3>
<p>Now that you can build and run packages for your target Linux system,
you might want to start deploying NixOS to remote machines.
When you are running Linux locally, you can easily run <code class="language-text">nixos-rebuild build-vm</code>
to generate a script that will start a local QEMU virtual machine. This allows
you to verify if the configuration works without affecting any actual system.
It is very lightweight because it mounts your <code class="language-text">/nix/store</code> directory directly
into the VM and boots the VM straight from it, with no additional image
overhead. You can even rely on it to wrap some services into a VM and configure
it to run as a daemon on your host by using the <code class="language-text">system.build.vm</code> attribute of
any NixOS configuration. It uses a NixOS configuration variant from
<code class="language-text">virtualisation.vmVariant</code> that imports the <code class="language-text">virtualisation/qemu-vm.nix</code> module
to build the VM.</p>
<p>However, on macOS none of that will work. <code class="language-text">nixos-rebuild</code> by default makes the
assumption that the local machine is Linux and links to Linux Bash and QEMU
binaries, so you can’t run it locally. Hence, an alternative approach is
needed.</p>
<h3 id="run-nixos-tests-locally" style="position:relative;"><a href="#run-nixos-tests-locally" aria-label="run nixos tests locally 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>Run NixOS tests locally</h3>
<p>NixOS provides <a href="https://nixos.org/manual/nixos/stable/#sec-nixos-tests">a powerful framework</a> for testing different
services and modules in VMs. You just provide it with a piece of NixOS
configuration for all machines that you need and it provides a neat API to run
them, interact with them and check their state. This framework is based on the
same mechanism as other NixOS VMs, so it doesn’t work on macOS out of the box
either. Here, we also need an alternative.</p>
<h2 id="what-has-been-done" style="position:relative;"><a href="#what-has-been-done" aria-label="what has been done 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 has been done?</h2>
<p>Since the conventional means of working on NixOS configurations relies on a
Linux host, multiple changes to the status quo are needed to make it
comfortable to work on them on macOS, presented in the following sections.</p>
<h3 id="build-vms-from-nixos-configurations" style="position:relative;"><a href="#build-vms-from-nixos-configurations" aria-label="build vms from nixos configurations 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>Build VMs from NixOS configurations</h3>
<p>The first issue that needed to be fixed is the lack of a simple interface for
building and running NixOS VMs on macOS. Starting with a Linux host before
migrating our workflow to macOS, you could have a NixOS configuration defined
in a flake like this:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><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>
    nixosModules<span class="token punctuation">.</span>base <span class="token operator">=</span> <span class="token punctuation">{</span>pkgs<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 punctuation">{</span>
      system<span class="token punctuation">.</span>stateVersion <span class="token operator">=</span> <span class="token string">"22.05"</span><span class="token punctuation">;</span>

      <span class="token comment"># Configure networking</span>
      networking<span class="token punctuation">.</span>useDHCP <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
      networking<span class="token punctuation">.</span>interfaces<span class="token punctuation">.</span>eth0<span class="token punctuation">.</span>useDHCP <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

      <span class="token comment"># Create user "test"</span>
      services<span class="token punctuation">.</span>getty<span class="token punctuation">.</span>autologinUser <span class="token operator">=</span> <span class="token string">"test"</span><span class="token punctuation">;</span>
      users<span class="token punctuation">.</span>users<span class="token punctuation">.</span>test<span class="token punctuation">.</span>isNormalUser <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

      <span class="token comment"># Enable passwordless ‘sudo’ for the "test" user</span>
      users<span class="token punctuation">.</span>users<span class="token punctuation">.</span>test<span class="token punctuation">.</span>extraGroups <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"wheel"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      security<span class="token punctuation">.</span>sudo<span class="token punctuation">.</span>wheelNeedsPassword <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>
    nixosConfigurations<span class="token punctuation">.</span>linuxBase <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>lib<span class="token punctuation">.</span>nixosSystem <span class="token punctuation">{</span>
      system <span class="token operator">=</span> <span class="token string">"x86_64-linux"</span><span class="token punctuation">;</span>
      modules <span class="token operator">=</span> <span class="token punctuation">[</span>
        self<span class="token punctuation">.</span>nixosModules<span class="token punctuation">.</span>base
      <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 punctuation">}</span></code></pre></div>
<p>Note that this configuration lacks some key settings, like filesystems or
bootloader configuration required for it to be actually deployed, but there is
just enough to build a VM out of it.</p>
<p>Still on Linux, you can use <code class="language-text">nixos-rebuild</code> to create a VM using this
configuration and run the result:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nixos-rebuild <span class="token parameter variable">--flake</span> <span class="token builtin class-name">.</span><span class="token comment">#linuxBase build-vm</span>
building the system configuration<span class="token punctuation">..</span>.
warning: creating lock <span class="token function">file</span> <span class="token string">'.../flake.lock'</span>

Done.  The virtual machine can be started by running /nix/store/3ad15806lclvmfzxkppabncp5sq9i93n-nixos-vm/bin/run-nixos-vm
$ ./result/bin/run-nixos-vm
Formatting <span class="token string">'.../nixos.qcow2'</span>, <span class="token assign-left variable">fmt</span><span class="token operator">=</span>qcow2 <span class="token assign-left variable">cluster_size</span><span class="token operator">=</span><span class="token number">65536</span> <span class="token assign-left variable">extended_l2</span><span class="token operator">=</span>off <span class="token assign-left variable">compression_type</span><span class="token operator">=</span>zlib <span class="token assign-left variable">size</span><span class="token operator">=</span><span class="token number">1073741824</span> <span class="token assign-left variable">lazy_refcounts</span><span class="token operator">=</span>off <span class="token assign-left variable">refcount_bits</span><span class="token operator">=</span><span class="token number">16</span></code></pre></div>
<p>It will start a VM, with an external window emulating its graphical terminal
screen, where the test user will be automatically logged in and at their
shell prompt. It’s easier to follow this article along without switching
between the terminal and external windows, so let’s change that in a separate
module specific to the VM in our flake. We’ll instruct <code class="language-text">vmVariant</code> of our NixOS
configuration to not use graphics for virtualisation. That would force it to
use a text based serial terminal output linked directly to the terminal from
which we’re running the script.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  outputs <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 punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment"># ...</span>
    nixosModules<span class="token punctuation">.</span>vm <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 punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment"># Make VM output to the terminal instead of a separate window</span>
      virtualisation<span class="token punctuation">.</span>vmVariant<span class="token punctuation">.</span>virtualisation<span class="token punctuation">.</span>graphics <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>
    nixosConfigurations<span class="token punctuation">.</span>linuxVM <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>lib<span class="token punctuation">.</span>nixosSystem <span class="token punctuation">{</span>
      system <span class="token operator">=</span> <span class="token string">"x86_64-linux"</span><span class="token punctuation">;</span>
      modules <span class="token operator">=</span> <span class="token punctuation">[</span>
        self<span class="token punctuation">.</span>nixosModules<span class="token punctuation">.</span>base
        self<span class="token punctuation">.</span>nixosModules<span class="token punctuation">.</span>vm
      <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 punctuation">}</span></code></pre></div>
<p>Now let’s build it with <code class="language-text">nixos-rebuild</code> to run it in our terminal this time:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nixos-rebuild <span class="token parameter variable">--flake</span> <span class="token builtin class-name">.</span><span class="token comment">#linuxVM build-vm</span>
building the system configuration<span class="token punctuation">..</span>.
warning: Git tree <span class="token string">'.../blog'</span> is dirty

Done.  The virtual machine can be started by running /nix/store/f9a3ac5ydcm6anv9zpf2sqykzivwccaw-nixos-vm/bin/run-nixos-vm
$ ./result/bin/run-nixos-vm
SeaBIOS <span class="token punctuation">(</span>version rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org<span class="token punctuation">)</span>
<span class="token punctuation">..</span>.
<span class="token punctuation">[</span>test@nixos:~<span class="token punctuation">]</span>$ <span class="token function">uname</span> <span class="token parameter variable">-a</span>
Linux nixos <span class="token number">5.15</span>.74 <span class="token comment">#1-NixOS SMP Sat Oct 15 05:59:05 UTC 2022 x86_64 GNU/Linux</span>

<span class="token punctuation">[</span>test@nixos:~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> poweroff
<span class="token punctuation">..</span>.
<span class="token punctuation">[</span>   <span class="token number">75.762105</span><span class="token punctuation">]</span> reboot: Power down</code></pre></div>
<p>Note that you can use <code class="language-text">sudo poweroff</code> to turn the machine off, or the
<code class="language-text">Ctrl-A X</code> QEMU shortcut. The latter might leave your terminal in a weird
state, so you might need to run <code class="language-text">reset</code> to fix it.</p>
<p>Also note that I’ve created a Git repo and added <code class="language-text">flake.nix</code> and <code class="language-text">flake.lock</code>
files to it with <code class="language-text">git init &amp;&amp; git add flake.*</code>. Outside a Git repository, Nix
considers all files in the directory (including any temporary files, like
<code class="language-text">nixos.qcow2</code> or <code class="language-text">result</code>) relevant to the flake and pulls them into the build
process. When Nix detects that its target is a Git repository, it only
considers files that Git knows about and ignores all untracked files.</p>
<p>So far we’ve been using <code class="language-text">nixos-rebuild</code> to build our VMs, but it doesn’t
provide flexibility to build on other systems. Since we’ve imported the
<code class="language-text">qemu-vm.nix</code> module, we can use its output to run the VM instead:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix run <span class="token builtin class-name">.</span><span class="token comment">#nixosConfigurations.linuxVM.config.system.build.vm</span>
warning: Git tree <span class="token string">'.../blog'</span> is dirty
SeaBIOS <span class="token punctuation">(</span>version rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org<span class="token punctuation">)</span>
<span class="token punctuation">..</span>.</code></pre></div>
<p>We could also add it as a package to our flake so that we don’t have to spell
it out every time:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  outputs <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 punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment"># ...</span>
    packages<span class="token punctuation">.</span>x86_64<span class="token operator">-</span>linux<span class="token punctuation">.</span>linuxVM <span class="token operator">=</span> self<span class="token punctuation">.</span>nixosConfigurations<span class="token punctuation">.</span>linuxVM<span class="token punctuation">.</span>config<span class="token punctuation">.</span>system<span class="token punctuation">.</span>build<span class="token punctuation">.</span>vm<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Now we can run it with a short command:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix run <span class="token builtin class-name">.</span><span class="token comment">#linuxVM</span>
warning: Git tree <span class="token string">'.../blog'</span> is dirty
SeaBIOS <span class="token punctuation">(</span>version rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org<span class="token punctuation">)</span>
<span class="token punctuation">..</span>.</code></pre></div>
<p>So far we haven’t been doing anything that hasn’t been possible on any old
version of Nixpkgs. Let’s move to our macOS machine and try running this VM:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix run <span class="token builtin class-name">.</span><span class="token comment">#linuxVM</span>
warning: Git tree <span class="token string">'.../blog'</span> is dirty
error: flake <span class="token string">'.../blog'</span> does not provide attribute <span class="token string">'apps.aarch64-darwin.linuxVM'</span>, <span class="token string">'packages.aarch64-darwin.linuxVM'</span>, <span class="token string">'legacyPackages.aarch64-darwin.linuxVM'</span> or <span class="token string">'linuxVM'</span>
$ nix run <span class="token builtin class-name">.</span><span class="token comment">#nixosConfigurations.linuxVM.config.system.build.vm</span>
warning: Git tree <span class="token string">'.../blog'</span> is dirty
error: a <span class="token string">'x86_64-linux'</span> with features <span class="token punctuation">{</span><span class="token punctuation">}</span> is required to build <span class="token string">'/nix/store/297gh5gn4ihnd0av0qbfx14vg0azly8x-append-initrd-secrets.drv'</span>, but I am a <span class="token string">'x86_64-darwin'</span> with features <span class="token punctuation">{</span>benchmark, big-parallel, hvf, nixos-test<span class="token punctuation">}</span></code></pre></div>
<p>We don’t have an output named <code class="language-text">linuxVM</code> that would work on <code class="language-text">aarch64-darwin</code>,
and even if we were to make it, it would require a Linux builder to be built.
You can spin up a NixOS VM somewhere in a cloud or on your local machine and
<a href="https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html">configure</a> your Nix daemon to use it. You can run it
locally using one of the methods described <a href="#local-remote-builder">above</a> or
read on to <a href="#bootstrapping-a-local-builder-for-linux">the next section</a> for
a new one.</p>
<p>Assuming you have a Linux builder configured, let’s go one step further:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix run <span class="token builtin class-name">.</span><span class="token comment">#nixosConfigurations.linuxVM.config.system.build.vm --options builders ssh-ng://some-linux-builder</span>
warning: Git tree <span class="token string">'.../blog'</span> is dirty
/nix/store/f9a3ac5ydcm6anv9zpf2sqykzivwccaw-nixos-vm/bin/run-nixos-vm: line <span class="token number">7</span>: /nix/store/4vjigg3pr8bns6id4af51mza5p73l9lx-coreutils-9.1/bin/readlink: cannot execute binary <span class="token function">file</span></code></pre></div>
<p>The error output suggests that you cannot run this on macOS, likely because
this output is only expected to be run on Linux.</p>
<p>Looking for a solution for this I found an old <a href="https://github.com/NixOS/nixpkgs/issues/108984">issue</a>
where <a href="https://github.com/infinisil">Silvan Mosberger</a> started a discussion about running NixOS
VMs on macOS and developed a <a href="https://github.com/infinisil/nixpkgs/commit/4d244410ee0f3e3ece5494533217bbafbd95d9b3">prototype solution</a>. It allows us to
specify a different package set for the host where the VM is supposed to be
running. Several patches to support using sharing directories from the host and
running on Apple Silicon hardware have landed in QEMU master since, and using
the 7.0.0 release plus <a href="https://gitlab.com/qemu/qemu/-/commit/f5643914a9e8f79c606a76e6a9d7ea82a3fc3e65">a couple patches</a> it finally worked. I’ve
<a href="https://github.com/NixOS/nixpkgs/pull/180222">adapted</a> the prototype and <a href="https://github.com/NixOS/nixpkgs/pull/180221">backported</a>
the required QEMU fixes to 7.0.0 in Nixpkgs.</p>
<p>With all these changes, we can specify <code class="language-text">virtualisation.host.pkgs</code> to run a VM
directly on macOS:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  outputs <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 punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment"># ...</span>
    nixosConfigurations<span class="token punctuation">.</span>darwinVM <span class="token operator">=</span> nixpkgs<span class="token punctuation">.</span>lib<span class="token punctuation">.</span>nixosSystem <span class="token punctuation">{</span>
      system <span class="token operator">=</span> <span class="token string">"aarch64-linux"</span><span class="token punctuation">;</span>
      modules <span class="token operator">=</span> <span class="token punctuation">[</span>
        self<span class="token punctuation">.</span>nixosModules<span class="token punctuation">.</span>base
        self<span class="token punctuation">.</span>nixosModules<span class="token punctuation">.</span>vm
        <span class="token punctuation">{</span>
          virtualisation<span class="token punctuation">.</span>vmVariant<span class="token punctuation">.</span>virtualisation<span class="token punctuation">.</span>host<span class="token punctuation">.</span>pkgs <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>
        <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>
    packages<span class="token punctuation">.</span>aarch64<span class="token operator">-</span>darwin<span class="token punctuation">.</span>darwinVM <span class="token operator">=</span> self<span class="token punctuation">.</span>nixosConfigurations<span class="token punctuation">.</span>darwinVM<span class="token punctuation">.</span>config<span class="token punctuation">.</span>system<span class="token punctuation">.</span>build<span class="token punctuation">.</span>vm<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Note that I’ve changed it to <code class="language-text">aarch64-linux</code> because I’m running on Apple
Silicon and want to take advantage of its hardware assisted virtualisation.
I’ve also set <code class="language-text">virtualisation.host.pkgs</code> for <code class="language-text">vmVariant</code> of this NixOS
configuration that is used to build the VM. Now let’s run it:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nix run <span class="token builtin class-name">.</span><span class="token comment">#darwinVM</span>
warning: Git tree <span class="token string">'.../blog'</span> is dirty
Formatting <span class="token string">'.../blog/nixos.qcow2'</span>, <span class="token assign-left variable">fmt</span><span class="token operator">=</span>qcow2 <span class="token assign-left variable">cluster_size</span><span class="token operator">=</span><span class="token number">65536</span> <span class="token assign-left variable">extended_l2</span><span class="token operator">=</span>off <span class="token assign-left variable">compression_type</span><span class="token operator">=</span>zlib <span class="token assign-left variable">size</span><span class="token operator">=</span><span class="token number">1073741824</span> <span class="token assign-left variable">lazy_refcounts</span><span class="token operator">=</span>off <span class="token assign-left variable">refcount_bits</span><span class="token operator">=</span><span class="token number">16</span>
<span class="token punctuation">[</span>    <span class="token number">0.152640</span><span class="token punctuation">]</span> armv8-pmu pmu: hw perfevents: failed to probe PMU<span class="token operator">!</span>
<span class="token punctuation">..</span>.
<span class="token punctuation">[</span>test@nixos:~<span class="token punctuation">]</span>$ <span class="token function">uname</span> <span class="token parameter variable">-a</span>
Linux nixos <span class="token number">5.15</span>.74 <span class="token comment">#1-NixOS SMP Sat Oct 15 05:59:05 UTC 2022 aarch64 GNU/Linux</span>

<span class="token punctuation">[</span>test@nixos:~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> poweroff
<span class="token punctuation">..</span>.
<span class="token punctuation">[</span>   <span class="token number">11.565895</span><span class="token punctuation">]</span> reboot: Power down</code></pre></div>
<h3 id="bootstrapping-a-local-builder-for-linux" style="position:relative;"><a href="#bootstrapping-a-local-builder-for-linux" aria-label="bootstrapping a local builder for linux 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>Bootstrapping a local builder for Linux</h3>
<p>As I mentioned before, to build any Linux configuration on macOS you need a
remote Linux builder. There are some existing options for that:
<a href="https://github.com/LnL7/nix-docker#running-as-a-remote-builder">nix-docker</a> that relies on Docker running and essentially
providing a Linux VM on its own, or <a href="https://github.com/nix-community/linuxkit-nix">linuxkit-nix</a> that creates a
custom Runit-based system with very little room for customisation.</p>
<p>With the <a href="https://github.com/NixOS/nixpkgs/pull/180222">pull request providing <code class="language-text">virtualisation.host.pkgs</code></a>
option merged, <a href="https://github.com/Gabriella439">Gabriella Gonzales</a> created a pure NixOS-based
<a href="https://github.com/Gabriella439/macos-builder">builder</a>, which has been <a href="https://github.com/NixOS/nixpkgs/pull/206951">merged into
Nixpkgs</a>. Now you can <a href="https://nixos.org/manual/nixpkgs/stable/#sec-darwin-builder">start using the local
builder</a> straight from the upstream Nix cache, then
modify its <a href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/macos-builder.nix">configuration</a> and rebuild it locally. See
<a href="https://www.haskellforall.com/2022/12/nixpkgs-support-for-linux-builders.html">Gabriella’s blog post</a> for more information.</p>
<h3 id="running-nixos-tests-on-macos" style="position:relative;"><a href="#running-nixos-tests-on-macos" aria-label="running nixos tests on macos 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 NixOS tests on macOS</h3>
<p>There is an overarching issue on <a href="https://github.com/NixOS/nixpkgs/issues/5241">running NixOS tests on
different virtualisation technologies on different platforms</a>.
<a href="https://github.com/roberth">Robert Hensing</a> summarises the latest changes in this area pretty
well in his <a href="https://github.com/NixOS/nixpkgs/issues/5241#issuecomment-1260721556">comment</a>. They include his
<a href="https://github.com/NixOS/nixpkgs/pull/191540">work</a> on making NixOS tests modular and a <a href="https://github.com/NixOS/nixpkgs/pull/193336">draft
implementation</a> of providing an option to run them on macOS.</p>
<h2 id="what-else-can-be-done" style="position:relative;"><a href="#what-else-can-be-done" aria-label="what else can be done 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 else can be done?</h2>
<h3 id="making-nixos-rebuild-aware-of-darwin" style="position:relative;"><a href="#making-nixos-rebuild-aware-of-darwin" aria-label="making nixos rebuild aware of darwin 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>Making nixos-rebuild aware of Darwin</h3>
<p>On Linux <code class="language-text">nixos-rebuild</code> allows you to build a NixOS system configuration, run
it in a VM or apply it to a remote NixOS system. On Darwin you can use it to
build a configuration, although it’s hard to get man pages that are distributed
<a href="https://github.com/NixOS/nixpkgs/blob/21.11/nixos/doc/manual/default.nix#L235-L253">with NixOS itself</a>, rather than <a href="https://github.com/NixOS/nixpkgs/tree/21.11/pkgs/os-specific/linux/nixos-rebuild">in the <code class="language-text">nixos-rebuild</code>
package</a>. You can also build a VM with <code class="language-text">nixos-rebuild build-vm</code>,
but it will only run on Linux. We could detect what platform <code class="language-text">nixos-rebuild</code> is
running on and provide a VM compatible with said platform or allow the user to
specify it.</p>
<p>When managing remote NixOS systems from macOS, you have to be aware that all
derivations have to be built on a Linux platform, so you might want to put your
remote system in <code class="language-text">--build-host</code> to avoid having to fetch all dependencies
locally, then copying dependencies and derivations to the builder, then copying
the NixOS configuration with all its dependencies from the builder to the local
machine, and finally copying them to your remote system. You also have to use
<code class="language-text">--fast</code> because by default <code class="language-text">nixos-rebuild</code> builds Nix and itself from the
NixOS configuration that you provide to it, and those are Linux binaries. With
all these parameters on macOS you have to specify a command line like this:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ nixos-rebuild --build-host user@remote-host --target-host user@remote-host --use-remote-sudo <span class="token parameter variable">--fast</span> <span class="token punctuation">..</span>.</code></pre></div>
<p>We could detect that <code class="language-text">nixos-rebuild</code> is not running on Linux and default to
building on the target host and either not use newly built binaries at all or
build them for the platform it is running on.</p>
<h3 id="reducing-number-of-system-dependent-derivations" style="position:relative;"><a href="#reducing-number-of-system-dependent-derivations" aria-label="reducing number of system dependent derivations 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>Reducing number of system-dependent derivations</h3>
<p>If you try running <code class="language-text">nixos-rebuild dry-build</code> on a new NixOS configuration, you
will see a lot of paths that will be fetched from the cache, but also at least
200 derivations that will be built, all requiring Linux to do so. It would
seem logical that a Linux system requires Linux packages, but all packages are
already built by Hydra and available from the cache. All these derivations
produce different configuration files like systemd services, symlink trees
like <code class="language-text">/etc</code> and some shell scripts like <code class="language-text">nixos-rebuild</code> itself, that are built
by substituting a bunch of strings in a text file. Aside from being
<a href="https://discourse.nixos.org/t/benchmarking-stdenv-mkderivation-vs-derivation-for-trivial-builds/24298">penalised for using <code class="language-text">mkDerivation</code></a> for such simple
steps, all those actions don’t really depend on the platform they are running
on. Nix requires us to guarantee that the result of the derivation will not
change by specifying a concrete platform and a concrete builder that will run
on that platform, which is Bash in all these cases. We could lean onto some
virtual machine to handle this guarantee and run the same builder binaries on
all supported platforms instead. The current idea is to <a href="https://github.com/NixOS/nix/issues/6697">provide a
<code class="language-text">wasm32-wasi</code> platform</a> that will run builders
compiled to WASM, and allow building most of those derivations locally.</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 want to make macOS a first class citizen in the Nix ecosystem, and this post
shows some of the weaknesses that macOS users face when working with Nix and
NixOS. We as a community should strive to overcome these difficulties, and it’s
great to see a lot of progress in this area. There’s still a lot to be done
though and I hope to be able to help moving this forward.</p>]]></description><link>https://tweag.io/blog/2023-02-09-nixos-vm-on-macos/</link><guid isPermaLink="false">https://tweag.io/blog/2023-02-09-nixos-vm-on-macos/</guid><pubDate>Thu, 09 Feb 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[announcing jupyenv 0.1.0]]></title><description><![CDATA[<p>In November 2022, we released an API update to jupyterWith.
That was the first step in improving the user experience and paved the way for many future enhancements.
Today we are announcing another update to the API, a new project name, jupyenv, and new features on the site.
You can now find the new site at <a href="https://jupyenv.io">jupyenv.io</a>.</p>
<p>Why the name change?
We want to reach a bigger audience.
If you are a regular Nix user, jupyterWith is a good name.
It is analogous to the <code class="language-text">python.withPackages</code> function in nixpkgs.
But outside of the Nix ecosystem, the <code class="language-text">with</code> naming scheme is non-existent.
To better target people outside the Nix ecosystem, we are changing the name that emphasizes Jupyter environments (hence jupyenv).</p>
<h2 id="new-api" style="position:relative;"><a href="#new-api" aria-label="new api 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 API</h2>
<h3 id="module-system" style="position:relative;"><a href="#module-system" aria-label="module system 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>Module System</h3>
<p>jupyenv now uses <a href="https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules">NixOS modules</a> to configure the JupyterLab environment and kernels.
The previous API update reduced complexity, but using modules reduces it even further without sacrificing capability.
A minimal working Python kernel used to be configured like so.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># Old interface</span>
<span class="token punctuation">{</span>
  availableKernels<span class="token punctuation">,</span>
  name<span class="token punctuation">,</span>
  extraArgs<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">:</span>
availableKernels<span class="token punctuation">.</span>python <span class="token punctuation">{</span>
  name <span class="token operator">=</span> <span class="token string">"<span class="token interpolation"><span class="token antiquotation important">$</span><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span>-example"</span><span class="token punctuation">;</span>
  <span class="token keyword">inherit</span> <span class="token punctuation">(</span>extraArgs<span class="token punctuation">)</span> system<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>While this is not terribly complicated, it exposes the user to the internal machinery of jupyenv.
What is <code class="language-text">availableKernels</code>?
What is <code class="language-text">extraArgs</code> and what else can I pass in?
Is there something special about the <code class="language-text">name</code> attribute?
(<em>Spoiler: Yes! They have to be unique and without spaces.</em>)</p>
<p>Now with modules, we can hide all the inner workings.
The following is equivalent to the previous example, but uses the new modules API.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token comment"># New interface</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 punctuation">{</span>
  kernel<span class="token punctuation">.</span>python<span class="token punctuation">.</span>example<span class="token punctuation">.</span>enable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Using modules not only simplifies configuring kernels, but it also simplifies how they can be organized.
You are no longer <em>required</em> to have separate kernel directories.
You can if you prefer, but using modules provides the flexibility to configure all the kernels in one location.</p>
<p>The following is a working example configuration with two different Python kernels, a Bash kernel, and an OCaml kernel.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><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 punctuation">{</span>
  kernel<span class="token punctuation">.</span>python<span class="token punctuation">.</span>minimal<span class="token punctuation">.</span>enable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

  kernel<span class="token punctuation">.</span>python<span class="token punctuation">.</span>science<span class="token punctuation">.</span>enable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  kernel<span class="token punctuation">.</span>python<span class="token punctuation">.</span>science<span class="token punctuation">.</span>extraPackages <span class="token operator">=</span> ps<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    ps<span class="token punctuation">.</span>numpy
    ps<span class="token punctuation">.</span>scipy
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  kernel<span class="token punctuation">.</span>bash<span class="token punctuation">.</span>minimal<span class="token punctuation">.</span>enable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

  kernel<span class="token punctuation">.</span>ocaml<span class="token punctuation">.</span>science<span class="token punctuation">.</span>enable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  kernel<span class="token punctuation">.</span>ocaml<span class="token punctuation">.</span>science<span class="token punctuation">.</span>ocamlPackages <span class="token operator">=</span> <span class="token punctuation">{</span>
    hex <span class="token operator">=</span> <span class="token string">"*"</span><span class="token punctuation">;</span>
    owl <span class="token operator">=</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></code></pre></div>
<p>In the previous examples, we used names like <code class="language-text">example</code>, <code class="language-text">minimal</code>, and <code class="language-text">science</code> to configure a specific kernel.
Those names are purely descriptive and used so you can configure multiple kernels of the same type (e.g. <code class="language-text">python.minimal</code> and <code class="language-text">python.science</code>), and have them both available in your JupyterLab environment.
For more information, see the <a href="https://jupyenv.io/documentation/how-to/#kernels">Kernels</a> section on the How To page.</p>
<h3 id="documentation" style="position:relative;"><a href="#documentation" aria-label="documentation 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>Documentation</h3>
<p>One of the perks of using NixOS modules is their ability to create documentation.
Though the previous update was an improvement, the user would have to read the source code to find what arguments were available and how to use them.
Ideally, documentation would be available on the site, but it cannot be done manually, since it would quickly go out of date.
Now, with modules, the documentation is part of the option definitions and automatically uploaded to the jupyenv site as it changes.</p>
<p>We have taken great care to ensure the new modules documentation is tidy and easy to navigate.
With a bit of pre-processing and styling, child options are nested under their parents making the structure clearly visible.
Additionally, with a small amount of JavaScript, each option can be expanded.
You can see a demo of the new <a href="https://jupyenv.io/documentation/options/">Options documentation</a> in the video below.</p>
<p>
    <span
      style="
        position: relative;
        display: block;
        margin-left: auto;
        margin-right: auto;
        max-width: 590px;
        "
    >
      <video
        src=/c7eaeea3e5619b99583256757768cf1d/jupyenv-options-demo.mp4
        width="100%"
        height="auto"
        preload="auto"
        title="Options Demo"
        
        muted
        playsinline
        controls
        
      ></video>
    </span>
  </p>
<p>If you are not a fan of JavaScript or use a Text-Mode Web Browser, the page will still render nicely.
It is also navigable without the use of a mouse and should have the relevant context for screen readers.</p>
<h2 id="blog" style="position:relative;"><a href="#blog" aria-label="blog 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>Blog</h2>
<p>With the last API update, we found it was difficult to inform our users that things were changing.
Sure, there is the Tweag blog, and it does well for major announcements, but there are times when we want to inform our users without having to write a major blog post.
To keep users in the loop, we have added a Blog tab to the site.
Here we will post about new releases, interface changes, and bug fixes.</p>
<h2 id="releases" style="position:relative;"><a href="#releases" aria-label="releases 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>Releases</h2>
<p>The aforementioned new releases are going to be available as Git annotated tags in our GitHub repository
It is not so useful to mention a new release without some reference to a point in time in the repository.
So to help users not only see what changed, but when also when it changed, we will begin versioning jupyenv and announcing it on the blog.</p>
<h2 id="community" style="position:relative;"><a href="#community" aria-label="community 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>Community</h2>
<p>We want to create a community of jupyenv users, but so far there was no space for them to communicate directly.
Where can they ask questions?
Where can they collaborate with other users?
To answer this, we have created a Matrix Space which you can find in the Community tab on the site or <a href="https://matrix.to/#/#jupyenv:matrix.org">here</a>.</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>There are a lot of new updates.
A new name, a new module based API, updated and improved documentation, a blog, tagged releases, and a matrix space.
If you are a current jupyenv user, we hope the changes improve your user experience.
If you have never tried it but are interested in reproducible Jupyter environments or reproducible data science, please give it a try.
Join us on the journey to make Jupyter reproducible.</p>]]></description><link>https://tweag.io/blog/2023-02-02-jupyenv-0-1-0/</link><guid isPermaLink="false">https://tweag.io/blog/2023-02-02-jupyenv-0-1-0/</guid><pubDate>Thu, 02 Feb 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Nix with; with Nickel]]></title><description><![CDATA[<p>Tweag is a big supporter and user of Nix and NixOS. In our experience,
however, we have seen that it is hard to maintain a Nix codebase as it
grows. Indeed, the only way to know if a Nix expression is correct is to
evaluate it, and when an error occurs it can be hard to locate the root
cause. This is more of a problem with bigger codebases, such as the ones we write.</p>
<p>At Tweag, we are working on <a href="https://github.com/tweag/nickel">Nickel</a>, a configuration language
featuring a <a href="https://www.tweag.io/blog/2021-03-18-nickel-gradual-typing/">gradual type system</a>. Defining
Nix derivations, NixOS modules or flakes using Nickel would catch more
mistakes, catch them earlier and identify their precise location.</p>
<p>However, the Nix language is used to define more than 80,000
packages,<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup> to which one could add all <a href="https://www.tweag.io/blog/2020-05-25-flakes/">flakes</a>, NixOS modules and
Home Manager configurations. For all this historical effort to not go to
waste, Nickel code needs to be able to leverage Nixpkgs.</p>
<p>We can either make Nix able to call Nickel code, or the other way
around. We chose the latter: make the Nickel interpreter able to
evaluate Nix code. Indeed, making Nix evaluate Nickel code would negate
its benefits, namely, its error reporting, the integrated documentation
and the nice way records are merged with priorities.</p>
<p>This comes with several technical challenges. One being the handling of
the widely used, yet <a href="https://nix.dev/anti-patterns/language#with-attrset-expression">controversial</a>, <a href="https://nix.dev/tutorials/nix-language#with"><code class="language-text">with</code>
construct</a>.</p>
<p>In this blog post, we’ll explain the challenges of calling Nix from
Nickel, specifically for expressions which use the <code class="language-text">with</code> construct.</p>
<h2 id="the-challenge-of-calling-nix-from-nickel" style="position:relative;"><a href="#the-challenge-of-calling-nix-from-nickel" aria-label="the challenge of calling nix from nickel 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 challenge of calling Nix from Nickel</h2>
<p>Evaluating Nix presents two contradictory challenges. On the one hand,
calling Nix from Nickel should be done without breaking the safety
provided by the Nickel type system. Nix is dynamically typed, but one
purpose of the gradual type system of Nickel is precisely to make
statically typed code and dynamically typed code interact gracefully.
On the other hand, we want to update the core of Nickel only when
strictly necessary, because Nickel is not limited to targeting Nix and
NixOS. Fortunately, Nickel is almost a superset of Nix, so a balance
between these objectives can be found.</p>
<p>To evaluate Nix expressions in Nickel, we can use either an
<a href="https://en.wikipedia.org/wiki/Foreign_function_interface">FFI</a> or code translation (a.k.a. transpilation). We decided
to go for the latter: translating Nix code into Nickel code, which can
then be evaluated. Indeed, since the Nix and Nickel languages are built
on similar foundations (loosely, a lazy JSON with functions),
translating from Nix’s AST to Nickel’s is almost seamless. Such
translation also allows us to implement special features specific to
this use case. For instance, Nixpkgs annotates types as comments for
most of its functions, so we could infer types from these. Moreover, we
estimated that implementing an FFI system would be too complex for what
we wanted to achieve here. Thus, we made a Nix to Nickel transpiler.</p>
<p>However, some constructions weren’t so straightforward to translate:</p>
<ul>
<li>
<p>the <a href="https://nix.dev/tutorials/nix-language#inherit"><code class="language-text">inherit</code></a> keyword (which will probably be one
of the Nix constructs added to Nickel’s core.<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup>), and</p>
</li>
<li>
<p>the <code class="language-text">with</code> keyword.</p>
</li>
</ul>
<p>In addition, none of the standard library built-ins can be evaluated at the moment, as most of them don’t have an equivalent in Nickel.</p>
<h2 id="nix-with-is-useful-but-confusing" style="position:relative;"><a href="#nix-with-is-useful-but-confusing" aria-label="nix with is useful but confusing 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 <code class="language-text">with</code> is useful, but confusing</h2>
<p>In Nix, the <code class="language-text">with</code> keyword is used to bring all the fields of a record
in scope.</p>
<p>Let’s consider a simple Nix expression.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> pkgs<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 punctuation">{</span>
  packages <span class="token operator">=</span> <span class="token punctuation">[</span>
    pkgs<span class="token punctuation">.</span>firefox
    pkgs<span class="token punctuation">.</span>thunderbird
    pkgs<span class="token punctuation">.</span>libreoffice
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>As you can see, all the elements of the list are accessed via <code class="language-text">pkgs</code>.
This looks quite repetitive, and is arguably harder to read. We can use <code class="language-text">with</code>
to make it clearer.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> pkgs<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 punctuation">{</span>
  packages <span class="token operator">=</span> <span class="token keyword">with</span> pkgs<span class="token punctuation">;</span> <span class="token punctuation">[</span>
    firefox
    thunderbird
    libreoffice
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Such Nix expressions can be found anywhere, from Nixpkgs to your own
NixOS configuration. Thanks to the <code class="language-text">with</code> construct, you don’t have to
prefix each item with <code class="language-text">pkgs.</code>. That is quite convenient.</p>
<p>So what could be the issue here?</p>
<p>At first, one might think that the <code class="language-text">with</code> construct is syntactic sugar
that statically prepends record field access (here <code class="language-text">pkgs.</code>) to some
identifiers. Unfortunately, the way it actually works is more complex
than that.</p>
<p>To demonstrate some possible issues, let’s look at a more complex Nix
expression.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">let</span>
 env <span class="token operator">=</span> <span class="token punctuation">{</span>
    linux <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"linux-env"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    system <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"system-env"</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>
  lib <span class="token operator">=</span> <span class="token punctuation">{</span>
    linux <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"linux-lib"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    systemd <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"systemd-lib"</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>
  linux <span class="token operator">=</span> <span class="token string">"x86_64_linux_gnu"</span><span class="token punctuation">;</span>
<span class="token keyword">in</span>

<span class="token keyword">with</span> env<span class="token punctuation">;</span> <span class="token punctuation">{</span>
  system <span class="token operator">=</span> system<span class="token punctuation">.</span>name<span class="token punctuation">;</span>
  deps <span class="token operator">=</span> <span class="token keyword">with</span> lib<span class="token punctuation">;</span> <span class="token punctuation">[</span>
    linux
    system
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>What would this evaluate to? Try to think about it, then check the
answer below.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">let</span>
 env <span class="token operator">=</span> <span class="token punctuation">{</span>
    linux <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"linux-env"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    system <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"system-env"</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>
  lib <span class="token operator">=</span> <span class="token punctuation">{</span>
    linux <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"linux-lib"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    systemd <span class="token operator">=</span> <span class="token punctuation">{</span>name <span class="token operator">=</span> <span class="token string">"systemd-lib"</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>
  linux <span class="token operator">=</span> <span class="token string">"x86_64_linux_gnu"</span><span class="token punctuation">;</span>
<span class="token keyword">in</span>

<span class="token keyword">with</span> env<span class="token punctuation">;</span> <span class="token punctuation">{</span>
  system <span class="token operator">=</span> system<span class="token punctuation">.</span>name<span class="token punctuation">;</span> <span class="token comment"># "system-env"</span>
  deps <span class="token operator">=</span> <span class="token keyword">with</span> lib<span class="token punctuation">;</span> <span class="token punctuation">[</span>
    linux        <span class="token comment"># We get the one defined in `let`, not lib.linux,</span>
                 <span class="token comment"># which is "x86_64_linux_gnu"</span>
    system       <span class="token comment"># It's a typo! We wanted lib.systemd, but we get</span>
                 <span class="token comment"># env.system instead of an error</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Did you get it right? Probably not!</p>
<p>When <code class="language-text">with</code> blocks are nested, the behaviour can be
confusing.<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup></p>
<p>Finally, as you may have noticed, we can’t know before evaluation which
field contains the record passed to <code class="language-text">with</code>. This is due to Nix’s dynamic
typing. Because of this behaviour, we encounter something that looks
like standard (static) variable access, but in reality, is dynamic record
access. In code where one would expect “unbound variable” errors, Nix
will instead throw runtime errors that are generally tricky to
debug.<sup id="fnref-4"><a href="#fn-4" class="footnote-ref">4</a></sup> Moreover, these errors cannot be caught by an
LSP, so they cannot be shown in a code editor. For the same reason, we
can’t provide auto-completion hints within a <code class="language-text">with</code> block, at least not
without hacks that perform evaluations.</p>
<h2 id="transpiling-with-to-nickel" style="position:relative;"><a href="#transpiling-with-to-nickel" aria-label="transpiling with to nickel 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>Transpiling <code class="language-text">with</code> to Nickel</h2>
<p>Because of the problems explained above, the Nickel team decided to not
implement any <code class="language-text">with</code>-like operator in the interpreter. At least not with
the exact same behaviour. Regardless, for compatibility with Nix, we should
find a way to evaluate it anyway.</p>
<p>What we propose in this article, which will be implemented in the near
future, is a mix of Nickel code generation and compatibility function
calls. To detail this, step-by-step, let’s revisit our simple example from
earlier:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> pkgs<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 punctuation">{</span>
  packages <span class="token operator">=</span> <span class="token keyword">with</span> pkgs<span class="token punctuation">;</span> <span class="token punctuation">[</span>
    firefox
    thunderbird
    libreoffice
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>This will be translated to something 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">{</span><span class="token operator"> </span><span class="token operator">p</span><span class="token operator">k</span><span class="token operator">g</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 property">packages</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">o</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">a</span><span class="token operator">t</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 operator">[</span><span class="token operator">p</span><span class="token operator">k</span><span class="token operator">g</span><span class="token operator">s</span><span class="token operator">]</span><span class="token operator"> </span><span class="token string">"firefox"</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">o</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">a</span><span class="token operator">t</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 operator">[</span><span class="token operator">p</span><span class="token operator">k</span><span class="token operator">g</span><span class="token operator">s</span><span class="token operator">]</span><span class="token operator"> </span><span class="token string">"thunderbird"</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">o</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">a</span><span class="token operator">t</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 operator">[</span><span class="token operator">p</span><span class="token operator">k</span><span class="token operator">g</span><span class="token operator">s</span><span class="token operator">]</span><span class="token operator"> </span><span class="token string">"libreoffice"</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>You will notice two things on the <code class="language-text">compat.with</code> call:</p>
<ol>
<li>
<p>The array <code class="language-text">[pkgs]</code> is the first parameter. We will detail why later,
but it has to do with nested <code class="language-text">with</code>.</p>
</li>
<li>
<p>The field being looked for is passed as a string and not a static
identifier. That’s because, at parse time, we can’t know which record
contains which fields. Indeed, in Nickel, every variable access is
checked statically; the usage of an identifier instead of a string
here would throw an “unbound variable” error.</p>
</li>
</ol>
<p>An implementation of the <code class="language-text">compat.with</code> function might look like:</p>
<div class="gatsby-highlight" data-language="nickel"><pre class="language-nickel"><code class="language-nickel"><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 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 operator">{</span><span class="token operator">_</span><span class="token operator">:</span><span class="token operator"> </span><span class="token class-name">Dyn</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">r</span><span class="token operator"> </span><span class="token operator">-></span><span class="token operator"> </span><span class="token class-name">Dyn</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">e</span><span class="token operator">n</span><span class="token operator">v</span><span class="token operator">s</span><span class="token operator"> </span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">e</span><span class="token operator">l</span><span class="token operator">d</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">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">f</span><span class="token operator">o</span><span class="token operator">l</span><span class="token operator">d</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">c</span><span class="token operator">u</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator"> </span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">c</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 keyword">if</span><span class="token operator"> </span><span class="token operator">!</span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">c</span><span class="token operator">.</span><span class="token operator">f</span><span class="token operator">o</span><span class="token operator">u</span><span class="token operator">n</span><span class="token operator">d</span><span class="token operator"> </span><span class="token operator">&amp;&amp;</span><span class="token operator"> </span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">c</span><span class="token operator">o</span><span class="token operator">r</span><span class="token operator">d</span><span class="token operator">.</span><span class="token operator">h</span><span class="token operator">a</span><span class="token operator">s</span><span class="token operator">_</span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">e</span><span class="token operator">l</span><span class="token operator">d</span><span class="token operator"> </span><span class="token operator">f</span><span class="token operator">i</span><span class="token operator">e</span><span class="token operator">l</span><span class="token operator">d</span><span class="token operator"> </span><span class="token operator">c</span><span class="token operator">u</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">n</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 operator"> </span><span class="token operator"> </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 property">value</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">u</span><span class="token operator">r</span><span class="token operator">r</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">t</span><span class="token operator">.</span><span class="token string">"%{field}"</span><span class="token operator">,</span><span class="token operator"> </span><span class="token property">found</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token constant">true</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 keyword">else</span><span class="token operator"> </span><span class="token operator">a</span><span class="token operator">c</span><span class="token operator">c</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">found</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token constant">false</span><span class="token operator">,</span><span class="token operator"> </span><span class="token property">value</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token constant">null</span><span class="token operator">}</span><span class="token operator"> </span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">v</span><span class="token operator">s</span><span class="token operator">)</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></code></pre></div>
<p>The function folds on the first parameter, <code class="language-text">envs</code>, which is an array of
<code class="language-text">with</code> records. This array has to be ordered from the outermost to the
innermost <code class="language-text">with</code> block; the outermost being the head of the array. This
order is justified because the more internal a <code class="language-text">with</code>, the
higher its priority. Please note that <code class="language-text">fold</code> is a right folding. If
multiple records contain <code class="language-text">field</code>, the rightmost one is returned by the
fold, that is, the innermost one. In the case where none of them have
this field, the initial value of fold (<code class="language-text">{found = false}</code>) is returned.
Finally, we try to access the field <code class="language-text">value</code>. This last access operation will
differ in the final implementation: it will probably use a contract
application to assert on <code class="language-text">found</code>.</p>
<p>This operation will fail only if <code class="language-text">{}</code> is returned. In other words, only
if none of the records in <code class="language-text">envs</code> contain the required field.</p>
<p>That’s all for the stuff that happens at runtime.</p>
<p>For what is done at parse time, we have:</p>
<ul>
<li><code class="language-text">let</code>-defined variables,</li>
<li>functions parameters, and</li>
<li>fields inside a recursive record.</li>
</ul>
<p>Let’s revisit our more complicated example:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> env<span class="token punctuation">,</span> lib<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 keyword">let</span>
linux <span class="token operator">=</span> <span class="token string">"x86_64_linux_gnu"</span><span class="token punctuation">;</span>
<span class="token keyword">in</span>

<span class="token keyword">with</span> env<span class="token punctuation">;</span> <span class="token punctuation">{</span>
  system <span class="token operator">=</span> system<span class="token punctuation">.</span>name<span class="token punctuation">;</span>
  compatible <span class="token operator">=</span> system <span class="token operator">==</span> linux<span class="token punctuation">;</span>
  deps <span class="token operator">=</span> <span class="token keyword">with</span> lib<span class="token punctuation">;</span> <span class="token punctuation">[</span>
    linux
    system
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>It will be translated as follows, during the parse
phase:<sup id="fnref-5"><a href="#fn-5" class="footnote-ref">5</a></sup></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">env</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">linux</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">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"linux-env"</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">system</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">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"system-env"</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 property">lib</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">linux</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">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"linux-lib"</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">systemd</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">name</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"systemd-lib"</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 property">linux</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token string">"x86_64_linux_gnu"</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 property">_with_</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">o</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">a</span><span class="token operator">t</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 operator">[</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">v</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 comment"># NOTE Even though records, in Nickel, are recursive by default,</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token comment"># the record in this AST is not.</span>

<span class="token operator"> </span><span class="token operator"> </span><span class="token comment"># No var named "system" found, so substitute by a call to __with__</span>
<span class="token operator"> </span><span class="token operator"> </span><span class="token property">system</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">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 operator"> </span><span class="token string">"system"</span><span class="token operator">)</span><span class="token operator">.</span><span class="token operator">n</span><span class="token operator">a</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 operator"> </span><span class="token property">deps</span><span class="token operator"> </span><span class="token operator">=</span><span class="token operator"> </span><span class="token keyword">let</span><span class="token operator"> </span><span class="token property">_with_</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">o</span><span class="token operator">m</span><span class="token operator">p</span><span class="token operator">a</span><span class="token operator">t</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 operator">[</span><span class="token operator">e</span><span class="token operator">n</span><span class="token operator">v</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator">l</span><span class="token operator">i</span><span class="token operator">b</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 operator"> </span><span class="token operator"> </span><span class="token operator"> </span><span class="token operator">l</span><span class="token operator">i</span><span class="token operator">n</span><span class="token operator">u</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 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 comment"># Nickel statically found a defined var named "linux" so didn't substitute</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">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 operator"> </span><span class="token string">"system"</span><span class="token operator">,</span><span class="token operator"> </span><span class="token operator"> </span><span class="token comment"># "system" isn't found in lib so it takes the env field</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>To perform this translation in our Rust implementation, we pass around
two extra things: a list of in-scope variables, and a stack of <code class="language-text">with</code>
records. The first is updated by inserting the identifiers when entering
any of:</p>
<ul>
<li>a <code class="language-text">let</code> binding body,</li>
<li>a function definition, or</li>
<li>a recursive record.</li>
</ul>
<p>When leaving these blocks, the list has to be reset to the state it was in before
entering. The <code class="language-text">with</code> stack is updated during a <code class="language-text">with</code> block translation: we
push the identifier at the beginning and pop it at the end.</p>
<p>Finally, for each variable translation, we perform these steps:</p>
<ol>
<li>
<p>We check the existence of the variable identifier in the list of
variables. If we find one, or if the <code class="language-text">with</code> stack is empty, the
variable is translated to a Nickel variable.</p>
</li>
<li>
<p>If the variable has not been defined before, and if the <code class="language-text">with</code> stack
is not empty, the variable is translated to a <code class="language-text">compat.with</code> call with
the <code class="language-text">with</code> stack as its first parameter, and the identifier stringified
as its second.</p>
</li>
</ol>
<p>By checking the <code class="language-text">with</code> stack as described above, we keep the undefined
variable errors at type checking time when we are not in a <code class="language-text">with</code> block.
This is more or less how Nix evaluates <code class="language-text">with</code> inside its interpreter. We
only rewrite the static part into Rust and the runtime one with Nickel.</p>
<h2 id="the-future-of-with-in-nickel" style="position:relative;"><a href="#the-future-of-with-in-nickel" aria-label="the future of with in nickel 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 future of <code class="language-text">with</code> in Nickel</h2>
<p>As we discussed, Nickel cannot avoid the use of dynamic fields access
to emulate Nix <code class="language-text">with</code>. So then, what could a Nickel-friendly <code class="language-text">with</code> look
like? Moreover, given Nickel’s type system and the fact that it already has a way
to <a href="https://github.com/tweag/nickel/issues/81">destructure records</a>, do we really want a Nickelized
<code class="language-text">with</code>?</p>
<p>Remember the two main issues regarding Nix <code class="language-text">with</code>:</p>
<ul>
<li>shadowing or, more accurately, not shadowing of already-defined variables; and</li>
<li>the fact that the variables made available by <code class="language-text">with</code> are unknown statically.</li>
</ul>
<p>To handle the first issue we could make <code class="language-text">with</code> overload already-defined variables.
Alternatively, we could throw an error if we call <code class="language-text">with</code> on a record
containing a field that shares a name with a variable in scope. Without knowing the
fields of the record, the first option remains confusing, while the
second is not possible.</p>
<p>In Nickel, the only way to know for sure that a record contains a field
is to rely on the type checker. So a correct <code class="language-text">with</code> can exist in Nickel
only if it requires a statically typed record, where the fields we want
to use must also be typed (e.g.: <code class="language-text">myrec: {field: Dyn, otherfield: Num}</code>).
Obviously, a type like <code class="language-text">{_: SomeType}</code> does not respect this rule
because fields are not statically defined.</p>
<p>That said, the use of a closed record contract is possible. In this
case, we can see the contract as a dynamic cast. The power of this
approach is to permit static typing and auto-completion in the <code class="language-text">with</code>
block. This goes a long way towards fulfilling the necessity of statically
knowing every record’s fields, and focuses the possibility of evaluation
errors to the position of the cast. These errors are clear enough (e.g.
missing or extra fields). In this manner, a Nickel <code class="language-text">with</code> would behave like
an <code class="language-text">import</code> or an <code class="language-text">open</code> in languages with statically typed modules
(Rust, OCaml, Haskell, etc.).</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 is only the beginning of the Nix/Nickel story. For now, Nickel is
only able to evaluate Nix expressions which do not use any standard
built-ins or <code class="language-text">inherit</code>, and the error reporting is not always as
detailed as Nix’s.</p>
<p>The question of Nickel <code class="language-text">with</code> is far from settled. It is clearly useful,
when we need to bring a lot of fields from a record into scope, but the
requirement for types may only end up moving the extra code to the type
annotation, from the destructuring pattern. Moreover, at least two
proposals for Nix (<a href="https://github.com/r-burns/nixos-rfcs/blob/rfc-inherit-as-list/rfcs/0110-inherit-as-list.md">RFC 110</a> and <a href="https://github.com/zseri/rfcs/blob/no-nested-with/rfcs/0120-no-nested-with.md">RFC 120</a>) open
the possibility of a future deprecation of <code class="language-text">with</code>. Their proposed syntax
is a better way to write the most common use case of <code class="language-text">with</code>, so that may
also be a direction that we consider for Nickel.</p>
<p>In summary, we are not totally rejecting the idea of a Nickel <code class="language-text">with</code>,
but ultimately, it will be really different from its Nix cousin.</p>
<!-- Footnotes -->
<!-- Links -->
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">As of writing (2022-09-02). See <a href="https://search.nixos.org/packages">https://search.nixos.org/packages</a><a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">See Nickel’s <a href="https://github.com/tweag/nickel/issues/747">field punning</a> proposal.<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-3">See Nix <a href="https://github.com/NixOS/nix/issues/490">issue #490</a> and <a href="https://github.com/NixOS/nix/issues/1361">#1361</a>.<a href="#fnref-3" class="footnote-backref">↩</a></li>
<li id="fn-4">For example, see Nixpkgs <a href="https://github.com/NixOS/nixpkgs/pull/101139">PR #101139</a> and, again,
the <a href="https://nix.dev/anti-patterns/language#with-attrset-expression"><code class="language-text">with</code> antipattern</a>.<a href="#fnref-4" class="footnote-backref">↩</a></li>
<li id="fn-5">Heads up: This is a print-out of the AST, which may differ from the result of parsing.<a href="#fnref-5" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2023-01-24-nix-with-with-nickel/</link><guid isPermaLink="false">https://tweag.io/blog/2023-01-24-nix-with-with-nickel/</guid><pubDate>Tue, 24 Jan 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Bazel and Nix: A Migration Experience]]></title><description><![CDATA[<p>I’ve recently been learning how to use <a href="https://bazel.build/">Bazel</a> and <a href="https://github.com/NixOS/nixpkgs#readme">Nix</a>
to build projects. Reading documentation and playing around with <a href="https://github.com/tweag/nix_bazel_codelab/tree/main#nixbazel-codelab">codelab
exercises</a> is a great way to get started, but when learning a new
technology there comes a point where you have to start applying it for real to
make further progress. To this end I decided to migrate an old hobby project of
mine to use Bazel. This post describes my experience and I hope serves as a
useful example for others.</p>
<p>Bazel is a fantastic build system which by itself provides <a href="https://bazel.build/about/vision#bazel-core-competencies">many
benefits</a>. However dependency management could be
considered an area of weakness for it. This is understandable given its
<a href="https://en.wikipedia.org/wiki/Monorepo">monorepo</a> origins at Google but when not working in a monorepo, what
is the best way to provide the dependencies a project needs?</p>
<p>One approach is to simply assume the dependencies exist in the environment
(having been put there by <code class="language-text">apt install</code> or the like), but this is just ignoring
the problem. Bazel has the concept of <a href="https://bazel.build/docs/external">external repositories</a>
and is able to fetch and build source code from a variety of places<sup id="fnref-1"><a href="#fn-1" class="footnote-ref">1</a></sup>. Under
some circumstances this might be a reasonable solution, but we probably don’t
want to spend significant amounts of time building a large dependency tree for
a project that itself only takes a few minutes to build.</p>
<p>Instead, Nix can be used to supply the dependencies to Bazel prebuilt. Nix and
Bazel are very similar technologies (both build code from source and both place
strong emphasis on properties like determinism and hermeticity), but without
<a href="https://www.tweag.io/blog/2018-03-15-bazel-nix/">delving into the details</a> it is perhaps best to think of Nix
as a package manager and Bazel as a build system. The advantage of Nix is that
while it ostensibly builds everything from source, in practice it usually just
downloads artifacts from the Nix binary cache.</p>
<p>The <a href="https://github.com/benradf/space-game#space-game">project</a> I migrated is an unfinished game with around fifteen
thousand lines of C++ code. It was originally built using some very convoluted
makefiles and is structured as follows:</p>
<p align="center">
<img src="/fc4ce0c7b9c46b73ccc20af86c4c1d8a/project-structure.svg" alt="Project structure" style="width: 100% !important;">
</p>
<p>This game has a few attributes that make it an interesting case study:</p>
<ul>
<li>
<p>It has a non-trivial structure with modules<sup id="fnref-2"><a href="#fn-2" class="footnote-ref">2</a></sup> shared between client and
server executables.</p>
</li>
<li>
<p>It depends on several third party libraries. Previously these were manually
built from source. Now with the help of <a href="https://github.com/tweag/rules_nixpkgs#nixpkgs-rules-for-bazel"><code class="language-text">rules_nixpkgs</code></a> they
are provided by Nix. Typically this means they will be fetched prebuilt from
the Nix cache, saving the time it would have taken to compile them.</p>
</li>
<li>
<p>In addition to code that needs to be built, the game has assets like 3D
meshes and textures that need to be transformed into a format that can be
loaded at runtime.</p>
</li>
<li>
<p>One particular transformation is handled by the custom <code class="language-text">zonebuild</code> tool. If
any of the source code that goes into this tool changes and it gets rebuilt,
the data transformation it performs also needs to be rerun.</p>
</li>
</ul>
<p>In a more traditional build system it is difficult to express a build graph for
the above in a way that gives perfectly <a href="https://docs.bazel.build/versions/main/guide.html#correct-incremental-rebuilds">correct and
incremental</a> builds. Things tend to be more ad-hoc and
changes to tools or across language boundaries are usually best dealt with by
cleaning and rebuilding from scratch.</p>
<h2 id="building-the-source-code" style="position:relative;"><a href="#building-the-source-code" aria-label="building the source 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>Building the source code</h2>
<p>Every Bazel project requires a <a href="https://docs.bazel.build/versions/main/build-ref.html#workspace"><code class="language-text">WORKSPACE.bazel</code></a> file at its
root. As the migration advances this file will grow in complexity, but to begin
with it simply specifies the workspace name:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">workspace<span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"space-game"</span><span class="token punctuation">)</span></code></pre></div>
<p>A Bazel workspace is organised into <a href="https://docs.bazel.build/versions/main/build-ref.html#packages">packages</a> by <code class="language-text">BUILD.bazel</code>
files. There are many ways you might do this (e.g. a separate package for each
module and executable) but for a project of this scale it is reasonable to
build all the source code from a single package named <code class="language-text">//common/src</code>.</p>
<p>As it has no dependencies, building the <code class="language-text">core</code> module is a good place to start.
It just requires a <a href="https://bazel.build/reference/be/c-cpp#cc_library"><code class="language-text">cc_library</code></a> rule in
<code class="language-text">common/src/BUILD.bazel</code>:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">cc_library<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"core"</span><span class="token punctuation">,</span>
    hdrs <span class="token operator">=</span> glob<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"core/*.hpp"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> glob<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"core/*.hpp"</span><span class="token punctuation">,</span> <span class="token string">"core/*.cpp"</span><span class="token punctuation">]</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++17"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    includes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"."</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></code></pre></div>
<p>At this point it is possible to build the <code class="language-text">core</code> module by running the following command:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">bazel build //common/src:core</code></pre></div>
<p>Bazel does not pollute the source tree with intermediate build files and output
files. These are instead created within the <a href="https://bazel.build/remote/output-directories#layout"><code class="language-text">outputRoot</code></a>
directory and some convenient symlinks are added to the workspace root. If you
follow the <code class="language-text">bazel-bin</code> symlink you’ll find that <code class="language-text">cc_library</code> has produced both
static and shared libraries:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">bazel-bin/common/src/libcore.a
bazel-bin/common/src/libcore.so</code></pre></div>
<p>This is pretty cool. In the old days, linking even a moderately complex project
was quite a tricky affair. You had to determine which parts it made sense to
link statically and which parts dynamically. Code going into a shared library
needed to be compiled with <a href="https://stackoverflow.com/questions/966960/what-does-fpic-mean-when-building-a-shared-library"><code class="language-text">-fPIC</code></a> because shared libraries might
be loaded anywhere in process address space. Getting something wrong usually
led to unhelpful linker errors about missing or duplicate symbols. With Bazel
all those messy details have been abstracted away.</p>
<p>Next let’s examine how the <code class="language-text">net</code> module is built. If you check the graph above
you’ll see that <code class="language-text">net</code> depends on <code class="language-text">core</code> and two third party libraries,
<a href="https://www.boost.org/"><code class="language-text">boost</code></a> and <a href="http://enet.bespin.org/"><code class="language-text">enet</code></a>. As mentioned in the introduction,
these libraries will be fetched from Nix. First we need a <code class="language-text">nixpkgs.nix</code> file:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token keyword">let</span> nixpkgs <span class="token operator">=</span> <span class="token function">fetchTarball</span> <span class="token punctuation">{</span>
  url <span class="token operator">=</span> <span class="token string">"https://github.com/NixOS/nixpkgs/archive/ce6aa13369b667ac2542593170993504932eb836.tar.gz"</span><span class="token punctuation">;</span>
  sha256 <span class="token operator">=</span> <span class="token string">"0d643wp3l77hv2pmg2fi7vyxn4rwy0iyr8djcw1h5x72315ck9ik"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">in</span> <span class="token function">import</span> nixpkgs</code></pre></div>
<p>This pins the nixpkgs version to git commit <a href="https://github.com/NixOS/nixpkgs/commit/ce6aa13369b667ac2542593170993504932eb836"><code class="language-text">ce6aa13</code></a>, which
corresponds to the 22.05 release. You can pick any commit you like but if you
are unsure, picking the commit with the latest <a href="https://github.com/NixOS/nixpkgs/tags">release tag</a> is a
good place to start. The correct <code class="language-text">sha256</code> value can be determined by setting it
to empty and running <code class="language-text">bazel build //...</code>. You will get an error telling you
what the hash should be:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">error: hash mismatch in file downloaded from 'https://github.com/NixOS/nixpkgs/archive/ce6aa13369b667ac2542593170993504932eb836.tar.gz':
         specified: sha256:0000000000000000000000000000000000000000000000000000
         got:       sha256:0d643wp3l77hv2pmg2fi7vyxn4rwy0iyr8djcw1h5x72315ck9ik</code></pre></div>
<p>Next some additions to <code class="language-text">WORKSPACE.bazel</code> are required:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">load<span class="token punctuation">(</span><span class="token string">"@bazel_tools//tools/build_defs/repo:http.bzl"</span><span class="token punctuation">,</span> <span class="token string">"http_archive"</span><span class="token punctuation">)</span>

<span class="token comment"># Import the rules_nixpkgs repository.</span>
http_archive<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"io_tweag_rules_nixpkgs"</span><span class="token punctuation">,</span>
    sha256 <span class="token operator">=</span> <span class="token string">"b01f170580f646ee3cde1ea4c117d00e561afaf3c59eda604cf09194a824ff10"</span><span class="token punctuation">,</span>
    strip_prefix <span class="token operator">=</span> <span class="token string">"rules_nixpkgs-0.9.0"</span><span class="token punctuation">,</span>
    urls <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"https://github.com/tweag/rules_nixpkgs/archive/v0.9.0.tar.gz"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Import the transitive dependencies of rules_nixpkgs.</span>
load<span class="token punctuation">(</span><span class="token string">"@io_tweag_rules_nixpkgs//nixpkgs:repositories.bzl"</span><span class="token punctuation">,</span> <span class="token string">"rules_nixpkgs_dependencies"</span><span class="token punctuation">)</span>
rules_nixpkgs_dependencies<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment"># Import a repository for the version of nixpkgs we pinned in nixpkgs.nix above.</span>
load<span class="token punctuation">(</span><span class="token string">"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl"</span><span class="token punctuation">,</span> <span class="token string">"nixpkgs_local_repository"</span><span class="token punctuation">)</span>
nixpkgs_local_repository<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"nixpkgs"</span><span class="token punctuation">,</span>
    nix_file <span class="token operator">=</span> <span class="token string">"//:nixpkgs.nix"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token comment"># Configure a toolchain from the nixpkgs repository above. This means we will</span>
<span class="token comment"># use a fixed version of the C++ compiler, which helps with reproducible builds.</span>
load<span class="token punctuation">(</span><span class="token string">"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl"</span><span class="token punctuation">,</span> <span class="token string">"nixpkgs_cc_configure"</span><span class="token punctuation">)</span>
nixpkgs_cc_configure<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"nixpkgs_config_cc"</span><span class="token punctuation">,</span>
    repository <span class="token operator">=</span> <span class="token string">"@nixpkgs"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

load<span class="token punctuation">(</span><span class="token string">"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl"</span><span class="token punctuation">,</span> <span class="token string">"nixpkgs_package"</span><span class="token punctuation">)</span></code></pre></div>
<p>The <a href="https://github.com/tweag/rules_nixpkgs#nixpkgs_package"><code class="language-text">nixpkgs_package</code></a> rule loaded by the last line can be
used to import a Nix package as a Bazel repository. It has many optional
parameters, allowing its behaviour to be customised precisely as needed (though
this requires some familiarity with Nix and Bazel).</p>
<p>The <a href="https://github.com/tweag/rules_nixpkgs#nixpkgs_package-attribute_path"><code class="language-text">attribute_path</code></a> parameter indicates the particular Nix
package to be imported. You can find attribute paths by searching for a Nix
package with the <a href="https://nixos.org/manual/nix/stable/command-ref/nix-env.html#operation---install"><code class="language-text">nix-env</code></a> command:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ nix-env <span class="token parameter variable">-f</span> nixpkgs.nix <span class="token parameter variable">-qaP</span> <span class="token string">'.*enet.*'</span>  <span class="token comment"># Note, the last argument is a regex.</span>
nixpkgs.enet                                           enet-1.3.17
nixpkgs.freenet                                        freenet-build01480</code></pre></div>
<p>If <code class="language-text">attribute_path</code> is not given, it defaults to the value given for the <code class="language-text">name</code>
parameter. So for <code class="language-text">enet</code> it can be left out. Once you’ve found a Nix package
you can examine what files it contains:</p>
<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">$ tree $(nix-build nixpkgs.nix -A enet)
/nix/store/75xz5q742sla5q4l0kj6cm90vgzh8qv3-enet-1.3.17
|-- include
|   `-- enet
|       |-- callbacks.h
|       |-- enet.h
|       |-- list.h
|       |-- protocol.h
|       |-- time.h
|       |-- types.h
|       |-- unix.h
|       |-- utility.h
|       `-- win32.h
`-- lib
    |-- libenet.la
    |-- libenet.so -> libenet.so.7.0.5
    |-- libenet.so.7 -> libenet.so.7.0.5
    |-- libenet.so.7.0.5
    `-- pkgconfig
        `-- libenet.pc</code></pre></div>
<p>This information will be useful when crafting <code class="language-text">build_file_content</code>. The value
given for this parameter is written to a <code class="language-text">BUILD.bazel</code> file in the root of the
<code class="language-text">@enet</code> repository. Again in <code class="language-text">WORKSPACE.bazel</code>:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">nixpkgs_package<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"enet"</span><span class="token punctuation">,</span>
    repository <span class="token operator">=</span> <span class="token string">"@nixpkgs"</span><span class="token punctuation">,</span>
    build_file_content <span class="token operator">=</span> <span class="token triple-quoted-string string">"""\
cc_library(
    name = "enet",
    hdrs = glob(["include/**/*.h"]),
    srcs = ["lib/libenet.so.7"],
    includes = ["include"],
    visibility = ["//visibility:public"],
)
"""</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span></code></pre></div>
<p>The <code class="language-text">cc_library</code> rule is quite versatile. It is not obvious from the name but
its <code class="language-text">srcs</code> parameter can take prebuilt libraries in addition to source files.
Note that there are multiple symlinks for the shared library but you cannot
choose one arbitrarily<sup id="fnref-3"><a href="#fn-3" class="footnote-ref">3</a></sup>. You will get a runtime error if you choose wrong,
but fortunately the error will indicate the correct choice.</p>
<p>This general pattern is used for all the third party dependencies (including
<code class="language-text">boost</code>) so let’s see how the <code class="language-text">net</code> module is built:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">cc_library<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"net"</span><span class="token punctuation">,</span>
    hdrs <span class="token operator">=</span> glob<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"net/*.hpp"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> glob<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"net/*.hpp"</span><span class="token punctuation">,</span> <span class="token string">"net/*.cpp"</span><span class="token punctuation">]</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++17"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    includes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"."</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    deps <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token string">"//common/src:core"</span><span class="token punctuation">,</span>
        <span class="token string">"@boost"</span><span class="token punctuation">,</span>
        <span class="token string">"@enet"</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></code></pre></div>
<p>Note that the <code class="language-text">@enet</code> dependency is syntactic sugar for <code class="language-text">@enet//:enet</code> and this
target is just the <code class="language-text">cc_library(name="enet", ...)</code> rule defined a little
earlier. The remaining modules (<code class="language-text">math</code>, <code class="language-text">physics</code> and <code class="language-text">script</code>) are built in a
similar fashion.</p>
<p>The final piece of the puzzle is to build the executables. Taking <code class="language-text">client</code> as
an example:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">cc_binary<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"client"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> glob<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"client/*.hpp"</span><span class="token punctuation">,</span> <span class="token string">"client/*.cpp"</span><span class="token punctuation">]</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++17"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    includes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"."</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    deps <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token string">"//common/src:core"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/src:net"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/src:physics"</span><span class="token punctuation">,</span>
        <span class="token string">"@cegui"</span><span class="token punctuation">,</span>
        <span class="token string">"@ogre"</span><span class="token punctuation">,</span>
        <span class="token string">"@ois"</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></code></pre></div>
<p>Apart from using <a href="https://bazel.build/reference/be/c-cpp#cc_binary"><code class="language-text">cc_binary</code></a>, this looks much the same as
the rules we have already seen. It depends on some internal modules and also on
<code class="language-text">cegui</code>, <code class="language-text">ogre</code> and <code class="language-text">ois</code>. Like <code class="language-text">enet</code>, these are external repositories
provided by <code class="language-text">rules_nixpkgs</code>.</p>
<h2 id="the-asset-transformation-pipeline" style="position:relative;"><a href="#the-asset-transformation-pipeline" aria-label="the asset transformation pipeline 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 asset transformation pipeline</h2>
<p>Having seen how the code is built, let’s turn to the game assets. An asset
could be a polygon mesh or a texture for a 3D model. It could be a music track,
a voice recording or a pre-rendered video. These assets are usually authored
and stored in a high resolution lossless format. They must be transformed into
a format more suitable for distribution and optimised for runtime use. For
proper games the asset pipeline can be quite complicated and might take hours
to run in full, but for this project it is simple:</p>
<p align="center">
<img src="/08b2b550ac6ed13332a9ac24e0175494/asset-transformations.svg" alt="Asset transformations" style="width: auto !important;">
</p>
<p>Files with no incoming arrows are checked into source control while the rest
are produced by Bazel during the build. A brief description of each follows:</p>
<ul>
<li><code class="language-text">geometry.xml</code>: List of the triangles and faces comprising a mesh.</li>
<li><code class="language-text">texture.tga</code>: <a href="https://en.wikipedia.org/wiki/UV_mapping">UV mapped</a> texture for the mesh.</li>
<li><code class="language-text">material.script</code>: A high level representation of a <a href="https://en.wikipedia.org/wiki/Shader">shader program</a>.</li>
<li><code class="language-text">mesh.bin</code>: An efficient binary format for meshes used by Ogre3D.</li>
<li><code class="language-text">assets.zip</code>: Contains all the assets needed to render a 3D model.</li>
<li><code class="language-text">collision.dat</code>: Stores map geometry in a <a href="https://en.wikipedia.org/wiki/K-d_tree">k-d tree</a> for fast collision detection.</li>
</ul>
<p>Bazel has no built-in rules for these transformations but
<a href="https://bazel.build/reference/be/general#genrule"><code class="language-text">genrule</code></a> can be used to run arbitrary Bash commands. To avoid
duplication I decided to define a couple of helper macros in a
<a href="https://github.com/benradf/space-game/blob/1edcdf95cc2e372090561eb8077d3f3ecc321397/common/data/resources.bzl"><code class="language-text">resources.bzl</code></a> extension file. In Bazel a
<a href="https://bazel.build/extending/macros">macro</a> is a function that can instantiate rules. The
<code class="language-text">assemble_assets</code> macro is used in <code class="language-text">//common/data/ships</code> like this:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">load<span class="token punctuation">(</span><span class="token string">"//common/data:resources.bzl"</span><span class="token punctuation">,</span> <span class="token string">"assemble_assets"</span><span class="token punctuation">)</span>

assemble_assets<span class="token punctuation">(</span><span class="token string">"bomber"</span><span class="token punctuation">)</span>
assemble_assets<span class="token punctuation">(</span><span class="token string">"spider"</span><span class="token punctuation">)</span>
assemble_assets<span class="token punctuation">(</span><span class="token string">"warbird"</span><span class="token punctuation">)</span></code></pre></div>
<p>The version of <code class="language-text">assemble_assets</code> shown below has been simplified a little for clarity:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">load<span class="token punctuation">(</span><span class="token string">"@rules_pkg//pkg:zip.bzl"</span><span class="token punctuation">,</span> <span class="token string">"pkg_zip"</span><span class="token punctuation">)</span>

<span class="token keyword">def</span> <span class="token function">assemble_assets</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token comment"># Convert xml mesh data to ogre binary format.</span>
    mesh_converter <span class="token operator">=</span> <span class="token string">"@ogre//:bin/OgreXMLConverter"</span>
    native<span class="token punctuation">.</span>genrule<span class="token punctuation">(</span>
        name <span class="token operator">=</span> <span class="token string">"convert_{name}_mesh"</span><span class="token punctuation">,</span>
        srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">":{name}-geometry.xml"</span><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">"{name}-mesh.bin"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        tools <span class="token operator">=</span> <span class="token punctuation">[</span>mesh_converter<span class="token punctuation">]</span><span class="token punctuation">,</span>
        cmd <span class="token operator">=</span> <span class="token triple-quoted-string string">"""$(execpath {mesh_converter}) \
            $(execpath {name}-geometry.xml) \
            $(execpath {name}-mesh.bin)
        """</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token comment"># Package the assets into a zip.</span>
    pkg_zip<span class="token punctuation">(</span>
        name <span class="token operator">=</span> <span class="token string">"zip_{name}_assets"</span><span class="token punctuation">,</span>
        out <span class="token operator">=</span> <span class="token string">"{name}-assets.zip"</span><span class="token punctuation">,</span>
        srcs <span class="token operator">=</span> <span class="token punctuation">[</span>
            <span class="token string">"{name}-mesh.bin"</span><span class="token punctuation">,</span>
            <span class="token string">"{name}-texture.tga"</span><span class="token punctuation">,</span>
            <span class="token string">"{name}-material.script"</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">)</span></code></pre></div>
<p>The most interesting thing here is that the <code class="language-text">OgreXMLConverter</code> tool is being
used to transform mesh data. This tool comes as part of the <code class="language-text">ogre</code> Nix package
and it must be <a href="https://github.com/benradf/space-game/blob/1edcdf95cc2e372090561eb8077d3f3ecc321397/WORKSPACE.bazel#L202">explicitly exported from <code class="language-text">BUILD.bazel</code></a>. It
also needs to be passed to the <a href="https://bazel.build/reference/be/general#genrule.tools"><code class="language-text">tools</code></a> parameter of the
<code class="language-text">genrule</code> or else Bazel will complain that it has not been declared as a
prerequisite.</p>
<p>Ships are treated as spherical objects by the physics engine so they do not
need their geometry to be transformed into a k-d tree for collision detection,
but for maps this extra step is needed:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">assemble_map</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">:</span>
    assemble_assets<span class="token punctuation">(</span>name<span class="token punctuation">)</span>

    <span class="token comment"># Build k-d tree for collision detection.</span>
    native<span class="token punctuation">.</span>genrule<span class="token punctuation">(</span>
        name <span class="token operator">=</span> <span class="token string">"{name}_collision"</span><span class="token punctuation">,</span>
        srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">":{name}-geometry.xml"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        tools <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"//common/src:zonebuild"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        cmd <span class="token operator">=</span> <span class="token triple-quoted-string string">"""$(execpath //common/src:zonebuild) \
            $(execpath {name}-geometry.xml) \
            $(execpath {name}-collision.dat)
        """</span><span class="token punctuation">,</span>
        outs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"{name}-collision.dat"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span></code></pre></div>
<p>Maps have meshes, textures and materials and the transformation of those is
delegated to <code class="language-text">assemble_assets</code>. Then the k-d tree is built by invoking
<code class="language-text">zonebuild</code> from another <code class="language-text">genrule</code>. This is essentially the same as invoking
<code class="language-text">OgreXMLConverter</code> except this time the tool is built in the main repository
instead of coming prebuilt from an external repository.</p>
<p>Beyond what we have seen above there are some <a href="https://github.com/benradf/space-game/blob/1edcdf95cc2e372090561eb8077d3f3ecc321397/common/data/BUILD.bazel"><code class="language-text">pkg_zip</code> rules</a>
for various other game resources like background textures, configuration files,
fonts, etc.</p>
<h2 id="running-the-game" style="position:relative;"><a href="#running-the-game" aria-label="running the game 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 the game</h2>
<p>With both the code and data building we now turn to actually running the game.
For the <code class="language-text">server</code> executable this is relatively straightforward. Its only data
dependency is the map collision data.</p>
<p>However the graphics engine used by <code class="language-text">client</code> requires all assets to be listed
in a <code class="language-text">resources.cfg</code> file. It also needs to dynamically load various plugins
with <a href="https://man7.org/linux/man-pages/man3/dlopen.3.html"><code class="language-text">dlopen</code></a>. This is facilitated by a wrapper script which
enumerates all the resources and sets the plugin folder appropriately. To
preserve the locality of the logic, it is convenient to inline such scripts
directly in <code class="language-text">BUILD.bazel</code> using a <a href="https://github.com/bazelbuild/bazel-skylib/blob/main/docs/write_file_doc.md"><code class="language-text">write_file</code></a> rule:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">write_file<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"write-run-client-sh"</span><span class="token punctuation">,</span>
    out <span class="token operator">=</span> <span class="token string">"run-client.sh"</span><span class="token punctuation">,</span>
    content <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token string">"sed -i \"s|^PluginFolder=.*|PluginFolder=$1|\" plugins.cfg"</span><span class="token punctuation">,</span>
        <span class="token string">"scripts/enum-resources.sh . >resources.cfg"</span><span class="token punctuation">,</span>
        <span class="token string">"common/src/client"</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>
<span class="token punctuation">)</span></code></pre></div>
<p>This causes Bazel to generate the script file as needed and avoids the hassle
of creating and committing another file to version control. The script is
invoked by a <a href="https://docs.bazel.build/versions/main/be/shell.html#sh_binary"><code class="language-text">sh_binary</code></a> rule:</p>
<div class="gatsby-highlight" data-language="python"><pre class="language-python"><code class="language-python">sh_binary<span class="token punctuation">(</span>
    name <span class="token operator">=</span> <span class="token string">"run-client"</span><span class="token punctuation">,</span>
    srcs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">":run-client.sh"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    data <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token string">"//common/data/maps:base03.dat"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data/maps:base03.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data/ships:bomber.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data/ships:spider.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data/ships:warbird.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data:cegui"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data:config"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data:materials.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data:particles.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/data:textures.zip"</span><span class="token punctuation">,</span>
        <span class="token string">"//common/src:client"</span><span class="token punctuation">,</span>
        <span class="token string">"//scripts:enum-resources.sh"</span><span class="token punctuation">,</span>
        <span class="token string">"@ogre//:lib/OGRE"</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    args <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token string">"$(rootpath @ogre//:lib/OGRE)"</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>Bazel ensures that all the data dependencies are available in the
<code class="language-text">run-client.runfiles</code> directory where they will be found by the
<code class="language-text">enumerate-resources.sh</code> script. The full path to the plugin directory is
obtained by using <a href="https://bazel.build/reference/be/make-variables#predefined_label_variables"><code class="language-text">rootpath</code></a> to resolve the
<code class="language-text">@ogre//:lib/OGRE</code> label. This path is then passed as an argument to
<code class="language-text">run-client.sh</code> and then substituted into <code class="language-text">plugins.cfg</code>.</p>
<p>Now the entire game can be <a href="https://github.com/benradf/space-game#building-and-running">built and run</a> with just two
commands:</p>
<div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">bazel run //common:run-server
bazel run //common:run-client</code></pre></div>
<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>It took around three days to complete the migration from start to finish.
However the majority of that time was spent updating the code to work with
C++17 and fixing various issues that arose from upgrading to newer versions of
the third party libraries. The Bazel migration itself was fairly easy and
wouldn’t have taken more than a day if the code had been otherwise up-to-date.
Of course this was a pretty small project so your mileage may vary.</p>
<p>I was initially concerned that there might not be Nix packages for some of the
more obscure dependencies but I was pleasantly surprised. Every single one of
the libraries I had chosen for this project in 2009 can now be found in Nix.
Having easy access to over 80,000 packages is reason enough to pair Bazel with
Nix, but if reproducible builds matter to you <a href="https://www.tweag.io/blog/2018-03-15-bazel-nix/">it makes even more
sense</a>.</p>
<p><a href="https://docs.bazel.build/versions/main/guide.html#correct-incremental-rebuilds">Correct and incremental</a> builds are awesome. Bazel is
very good at managing complex dependency graphs where some build outputs (e.g.
tools) are themselves used as part of the build. When you change something
Bazel will rebuild exactly what is necessary and it will do so in the correct
order.</p>
<p>Clearly, not building more than necessary saves time, but so does not building less.
When a build system neglects to rebuild something it should have, the
result is confusion and wasted time because runtime behaviour does not match
expectations. After being bitten by this a few times developers will tend to clean the
build more frequently, incurring the cost of a full rebuild each time. So the
confidence Bazel provides here is great for developer productivity.</p>
<!-- Footnotes -->
<!-- Links -->
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">For example, elsewhere on the filesystem or from a remote Git repository.<a href="#fnref-1" class="footnote-backref">↩</a></li>
<li id="fn-2">The term <em>module</em> is used here informally to refer to a grouping of related
source files in a directory under <code class="language-text">//common/src</code>. These are not <a href="https://en.cppreference.com/w/cpp/language/modules">C++20
modules</a>.<a href="#fnref-2" class="footnote-backref">↩</a></li>
<li id="fn-3">The <code class="language-text">srcs</code> parameter should exactly match the shared library name in the
dynamic section of the executable. You can use <code class="language-text">readelf -d EXECUTABLE</code> to check
this.<a href="#fnref-3" class="footnote-backref">↩</a></li>
</ol>
</div>]]></description><link>https://tweag.io/blog/2022-12-15-bazel-nix-migration-experience/</link><guid isPermaLink="false">https://tweag.io/blog/2022-12-15-bazel-nix-migration-experience/</guid><pubDate>Thu, 15 Dec 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Optimizing NixOS Search]]></title><description><![CDATA[<p>With their introduction in Nix 2.4, <a href="https://www.tweag.io/blog/2020-05-25-flakes/">flakes</a> are quickly becoming an integral part of the Nix ecosystem. For anyone unfamiliar, flakes exist to provide a standard format to package Nix-based projects.</p>
<p>To allow for better user experience and composability for existing flakes, discoverability of flakes is a necessary feature.</p>
<p>The site <a href="https://search.nixos.org">search.nixos.org</a> is used to search for packages, options, and flakes. It also provides metadata information such as descriptions, maintainers, and compatible platforms.</p>
<p>Currently, search.nixos.org imports flake metadata through custom nix code that wraps flake evaluations. However, wrapping employs too much memory when used with large repositories such as <code class="language-text">nixpkgs</code>. This causes a segmentation fault. As currently implemented, search.nixos.org cannot show all flakes.</p>
<p>My internship involved demonstrating a solution to this issue by updating the JSON schema for <code class="language-text">nix flake show</code> to natively export the necessary metadata for packages.</p>
<h2 id="metadata-for-export" style="position:relative;"><a href="#metadata-for-export" aria-label="metadata for export 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>Metadata For Export</h2>
<p><code class="language-text">nix flake show</code> reads in the file <code class="language-text">flake.nix</code> which provides an interface to artifacts like packages. Packages contain metadata fields (also called meta-attributes) detailing information such as a description of the package (<code class="language-text">meta.description</code>) and a list of supported Nix platforms (<code class="language-text">meta.platforms</code>).
To showcase a working solution, I decided to implement support for three additional fields: <code class="language-text">meta.homepage</code>, <code class="language-text">meta.licenses</code>, and <code class="language-text">meta.maintainers</code>.</p>
<p>These fields are more or less how <code class="language-text">nixpkgs</code> defines them in their flake files. In theory, these changes to <code class="language-text">nix flake show</code> should already be compatible.</p>
<p>Let’s explore these three additional fields:</p>
<p><code class="language-text">homepage</code> is the package’s homepage. It takes the form of a simple string:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">meta<span class="token punctuation">.</span>homepage <span class="token operator">=</span> <span class="token string">"https://code.visualstudio.com/"</span><span class="token punctuation">;</span></code></pre></div>
<p><code class="language-text">licenses</code> appears as an attribute set:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">meta<span class="token punctuation">.</span>licenses <span class="token operator">=</span>
  <span class="token punctuation">{</span>
    spdxId <span class="token operator">=</span> <span class="token string">"MIT"</span><span class="token punctuation">;</span>
    fullName <span class="token operator">=</span> <span class="token string">"MIT License"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<p><code class="language-text">maintainers</code> includes a list of package maintainers and is defined as a list of attribute sets which include basic identifying information:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">meta<span class="token punctuation">.</span>maintainers <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            email <span class="token operator">=</span> <span class="token string">"maintainer@nixos.org"</span><span class="token punctuation">;</span>
            matrix <span class="token operator">=</span> <span class="token string">"@user:matrix.org"</span><span class="token punctuation">;</span>
            name <span class="token operator">=</span> <span class="token string">"first last"</span><span class="token punctuation">;</span>
            github <span class="token operator">=</span> <span class="token string">"username"</span><span class="token punctuation">;</span>
            githubid <span class="token operator">=</span> <span class="token number">0000000</span><span class="token punctuation">;</span>
            keys <span class="token operator">=</span> <span class="token punctuation">{</span>
                fingerprint <span class="token operator">=</span> <span class="token string">"8w5w jc2b d0h8 q3l9 0tc4 m55v i87r 23zj 0fbj r30x"</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 punctuation">]</span>
</code></pre></div>
<p>For example, the <a href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/hello/default.nix">GNU Hello package</a> declares its meta fields as follows:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">meta <span class="token operator">=</span> <span class="token keyword">with</span> lib<span class="token punctuation">;</span> <span class="token punctuation">{</span>
  description <span class="token operator">=</span> <span class="token string">"A program that produces a familiar, friendly greeting"</span><span class="token punctuation">;</span>
  longDescription <span class="token operator">=</span> <span class="token string">''
    GNU Hello is a program that prints "Hello, world!" when you run it.
    It is fully customizable.
  ''</span><span class="token punctuation">;</span>
  homepage <span class="token operator">=</span> <span class="token string">"https://www.gnu.org/software/hello/manual/"</span><span class="token punctuation">;</span>
  license <span class="token operator">=</span> licenses<span class="token punctuation">.</span>gpl3Plus<span class="token punctuation">;</span>
  maintainers <span class="token operator">=</span> <span class="token punctuation">[</span> maintainers<span class="token punctuation">.</span>eelco <span class="token punctuation">]</span><span class="token punctuation">;</span>
  platforms <span class="token operator">=</span> platforms<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<h2 id="flake-show-command" style="position:relative;"><a href="#flake-show-command" aria-label="flake show command 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>Flake Show Command</h2>
<p>The <code class="language-text">nix flake show</code> command is implemented by <code class="language-text">CmdFlakeShow</code>’s <code class="language-text">run()</code> function. The <code class="language-text">run()</code> function consists of a definition and call to the local function <code class="language-text">visit()</code>. For recognized nested attributes, <code class="language-text">visit()</code> will be recursively run until all recognized attributes have been processed. When applicable, another nested function <code class="language-text">showDerivation()</code> will be run, printing a derivation’s attributes to standard output.</p>
<p>When the <code class="language-text">json</code> flag is passed into the program, output information and metadata will also be stored in a JSON object. Upon recursing, <code class="language-text">showDerivation</code> will check for a <code class="language-text">meta</code> symbol within the <code class="language-text">flake.nix</code> file. If found, the program will continue to check for recognized symbols defined as elements under <code class="language-text">meta</code>. To collect our metadata, we run <code class="language-text">maybeGetAttr</code> which returns <code class="language-text">std::shared_ptr&lt;AttrCursor></code>. As <code class="language-text">AttrCursor</code> serves as a wrapper for an <code class="language-text">Attr</code>, we can use one of its member functions to access each metadata value based on its type.</p>
<p>For <code class="language-text">description</code> and <code class="language-text">homepage</code>, this is straightforward. <code class="language-text">AttrCursor</code> implements a <code class="language-text">getString()</code> function that returns a <code class="language-text">std::string</code> value for a given attribute.</p>
<p><code class="language-text">license</code> consists of an attribute set. Therefore, we can use the <code class="language-text">getAttrs()</code> function to obtain a <code class="language-text">std::vector&lt;Symbol></code>. We can then do a lookup of the symbols in our <code class="language-text">EvalState</code> for our <code class="language-text">std::string</code> values.</p>
<p>So far, the previous two fields are easy to implement. However, <code class="language-text">maintainers</code> requires a bit more work. The main trouble comes from the fact that <code class="language-text">maintainers</code> is defined as a list of attribute sets. There exists no getter function for this attribute value type. This requires us to implement our own <code class="language-text">getListOfAttrs()</code> function for <code class="language-text">AttrCursor</code>.</p>
<h2 id="implementing-getlistofattrs" style="position:relative;"><a href="#implementing-getlistofattrs" aria-label="implementing getlistofattrs 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>Implementing getListOfAttrs</h2>
<p>For performance reasons, Nix internally caches the evaluation results on-disk.</p>
<p>This cache uses a SQlite database with a custom schema designed for efficiently caching lazy recursive data-structures.</p>
<p>However, this format does not yet handle lists properly, and only has hard-coded support for lists of strings, which is not sufficient for our purposes, as some of the metadata fields are represented as lists of compound structures.</p>
<p>Adding such support is easy in principle, but the actual implementation would have taken more time than what was available for my internship. My solution instead was to fall back on an uncached evaluation for evaluating these attributes. However, this means that we will not benefit from the cache, making it a temporary solution only.</p>
<p>Nevertheless, with this prototype solution, <code class="language-text">nix flake show</code> now shows all three metadata fields as JSON. This can now be used with nixos-search to deprecate our wrapped nix evaluations.</p>
<p>So what does the end result look like? Let us take a look at what <code class="language-text">nix flake show --json</code> outputs when given a <code class="language-text">flake.nix</code> file using the GNU Hello package:</p>
<div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token property">"homepage"</span><span class="token operator">:</span> <span class="token string">"https://www.gnu.org/software/hello/manual/"</span><span class="token punctuation">,</span>
<span class="token property">"license"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  <span class="token property">"deprecated"</span><span class="token operator">:</span> <span class="token string">"false"</span><span class="token punctuation">,</span>
  <span class="token property">"free"</span><span class="token operator">:</span> <span class="token string">"true"</span><span class="token punctuation">,</span>
  <span class="token property">"fullName"</span><span class="token operator">:</span> <span class="token string">"GNU General Public License v3.0 or later"</span><span class="token punctuation">,</span>
  <span class="token property">"redistributable"</span><span class="token operator">:</span> <span class="token string">"true"</span><span class="token punctuation">,</span>
  <span class="token property">"shortName"</span><span class="token operator">:</span> <span class="token string">"gpl3Plus"</span><span class="token punctuation">,</span>
  <span class="token property">"spdxId"</span><span class="token operator">:</span> <span class="token string">"GPL-3.0-or-later"</span><span class="token punctuation">,</span>
  <span class="token property">"url"</span><span class="token operator">:</span> <span class="token string">"https://spdx.org/licenses/GPL-3.0-or-later.html"</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token property">"maintainers"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  <span class="token property">"edolstra"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token property">"email"</span><span class="token operator">:</span> <span class="token string">"edolstra+nixpkgs@gmail.com"</span><span class="token punctuation">,</span>
    <span class="token property">"github"</span><span class="token operator">:</span> <span class="token string">"edolstra"</span><span class="token punctuation">,</span>
    <span class="token property">"githubId"</span><span class="token operator">:</span> <span class="token string">"1148549"</span><span class="token punctuation">,</span>
    <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Eelco Dolstra"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<h2 id="future-work-to-be-done" style="position:relative;"><a href="#future-work-to-be-done" aria-label="future work to be done 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>Future Work To Be Done</h2>
<ul>
<li>As mentioned above, <code class="language-text">getListOfAttrs()</code> should utilize caching in production. This will require revamping how lists are internally represented. Ideally, there should be a generalized <code class="language-text">getList()</code> function that behaves similarly to <code class="language-text">getAttrs()</code>. One proposed solution is to implement lists exactly like attribute sets but with a different tag and increasing integer key values.</li>
<li>There are many more metadata fields that can be queried. Implementing support for these in nix should be relatively straightforward.</li>
<li><code class="language-text">nix flake check</code> should warn users about missing or improperly formatted metadata fields.</li>
<li>Finally, we should implement our solution at search.nixos.org and benchmark it to verify that the import of nixpkgs is fast enough.</li>
</ul>
<h2 id="interning-at-tweag" style="position:relative;"><a href="#interning-at-tweag" aria-label="interning at tweag 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>Interning at Tweag</h2>
<p>As my first internship experience, Tweag has been a fantastic work environment. Tweagers share a mutual love for discussion and group learning, which fosters the exploration and application of new ideas.</p>
<p>During my time here, I have had the chance to put common software engineering techniques into practice and immerse myself in all things Nix. My mentor Rok provided extensive advice throughout this process and taught me the value of avoiding premature optimizations.</p>
<p>My chapter at the Tweag Paris office gave me an inside glimpse into the brilliant minds behind all those GitHub commits. I would like to thank Tweag for this opportunity and my coworkers for their thought-provoking insights.</p>
<p>For a chance to experience working with a company where research and engineering intersect, I would highly recommend checking out Tweag.</p>]]></description><link>https://tweag.io/blog/2022-12-13-optimizing-nixos-search/</link><guid isPermaLink="false">https://tweag.io/blog/2022-12-13-optimizing-nixos-search/</guid><pubDate>Tue, 13 Dec 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[JupyterWith Next]]></title><description><![CDATA[<p><a href="https://jupyterwith.tweag.io">JupyterWith</a> has been around for several years with growing popularity.
Over the years, we found that researchers struggled with the Nix language and jupyterWith API.
Since researchers are our primary target audience, we decided to improve the usability of jupyterWith.</p>
<p>Today, we are proud to announce the release of a new version!
The new simplified API makes jupyterWith easier to use and provides more options for creating kernels.</p>
<h2 id="what-is-jupyterwith" style="position:relative;"><a href="#what-is-jupyterwith" aria-label="what is jupyterwith 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 jupyterWith?</h2>
<p>JupyterLab is a web-based interactive development environment for notebooks, code, and data.
These notebooks can be shared with other users and the residing code can be rerun providing repeatability.</p>
<p>The Jupyter ecosystem allows users to produce and repeat research and results, but it lacks in facilitating reproducible results.
There may not appear to be a difference between repeatable and reproducible, but there is a meaningful difference; reproducibility guarantees that our code and results will be exactly the same while repeatability does not.</p>
<p>While many Jupyter kernels are available as Python packages, just as many are not (e.g. haskell and julia).
Projects such as <a href="https://pdm.fming.dev/latest/">PDM</a> and <a href="https://github.com/thoth-station/jupyterlab-requirements">JupyterLab Requirements</a> can create reproducible environments but are restricted to the Python kernels.</p>
<p>jupyterWith was <a href="https://www.tweag.io/blog/2019-02-28-jupyter-with/">announced</a> in early 2019 and provides a Nix-based framework for declarative and reproducible JupyterLab environments with configurable kernels.
It actively supports over a dozen kernels and provides example setups and notebooks for users to try out.
jupyterWith can create entirely reproducible JupyterLab environments for any kernel.</p>
<h2 id="why-jupyterwith" style="position:relative;"><a href="#why-jupyterwith" aria-label="why jupyterwith 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 jupyterWith?</h2>
<p>If you can run an experiment multiple times in the same environment and get to the same conclusion, you have repeatability.
In our case, running the same code on the same machine should give the same outputs.
Consider what would happen if you handed off your code to another user and they ran it on their system.
Different operating systems or different versions of the same operating system may fetch different versions of the same package.
Fetching the same package at different times may not return the same version due to patch or security updates.
If you can guarantee the same outputs given all that has changed, then you have reproducibility.</p>
<p>With repeatability, we cannot guarantee that the packages and dependencies of our code will remain constant.
Using jupyterWith we can give that guarantee and ensure that on any system, run by any user, and given identical inputs, the code will produce identical outputs.
This guarantee is what makes our code and therefore our research reproducible.</p>
<h2 id="what-is-new" style="position:relative;"><a href="#what-is-new" aria-label="what is new 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 new?</h2>
<p>This release focuses on helping users quickly and easily get their project started, and making it easier to extend kernels to fit their needs.</p>
<h3 id="new-templates" style="position:relative;"><a href="#new-templates" aria-label="new templates 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 templates</h3>
<p>The new version of jupyterWith provides new kernel templates which makes it easier for users to bootstrap their project using Nix flakes.
They are small, easily digestible, and ready to be customized.</p>
<h3 id="better-python-kernels" style="position:relative;"><a href="#better-python-kernels" aria-label="better python kernels 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 Python kernels</h3>
<p>It used to be difficult to select particular Python packages because we were tied to <code class="language-text">nixpkgs</code>.
jupyterWith now uses <a href="https://python-poetry.org/">Poetry</a> and <a href="https://github.com/nix-community/poetry2nix">poetry2nix</a> to install kernels that are packaged with Python and their dependencies.
Poetry allows users to easily select the desired version of a package and can resolve dependencies.
poetry2nix greatly simplifies the kernel files, which helps with readability and maintainability.</p>
<h3 id="better-kernel-definition-interface" style="position:relative;"><a href="#better-kernel-definition-interface" aria-label="better kernel definition interface 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 kernel definition interface</h3>
<p>Finally, we have simplified and standardized the interfaces for kernel files.
This makes it easier for users to implement completely new kernels.</p>
<h2 id="getting-started" style="position:relative;"><a href="#getting-started" aria-label="getting started 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>Getting Started</h2>
<p>The following code will initialize a new project directory with a flake template from the jupyterWith repository and start the JupyterLab environment.
With a renewed focus on user ease, this is all that is necessary to get started.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ <span class="token function">mkdir</span> my-project
$ <span class="token builtin class-name">cd</span> my-project
$ nix flake init <span class="token parameter variable">--template</span> github:tweag/jupyterWith
$ nix run</code></pre></div>
<p>Each kernel provided will generally only have the standard libraries and packages available, but there is a readme provided with the template with instructions on extending existing kernels, creating a custom kernel, and installing extensions.</p>
<h2 id="migration" style="position:relative;"><a href="#migration" aria-label="migration 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>Migration</h2>
<p>If you have used jupyterWith in the past, you are probably used to seeing kernel files like the ipython kernel example below.
The version of Python used and the packages available to the kernel, can be set using the <code class="language-text">python3</code> and <code class="language-text">packages</code> attributes respectively.</p>
<p><em>Old interface</em></p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  iPython <span class="token operator">=</span> iPythonWith <span class="token punctuation">{</span>
    <span class="token comment"># Identifier that will appear on the Jupyter interface.</span>
    name <span class="token operator">=</span> <span class="token string">"nixpkgs"</span><span class="token punctuation">;</span>
    <span class="token comment"># Libraries to be available to the kernel.</span>
    packages <span class="token operator">=</span> p<span class="token punctuation">:</span> <span class="token keyword">with</span> p<span class="token punctuation">;</span> <span class="token punctuation">[</span> numpy pandas <span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token comment"># Optional definition of `python3` to be used.</span>
    <span class="token comment"># Useful for overlaying packages.</span>
    python3 <span class="token operator">=</span> pkgs<span class="token punctuation">.</span>python3Packages<span class="token punctuation">;</span>
    <span class="token comment"># Optional value to true that ignore file collisions inside the packages environment</span>
    ignoreCollisions <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></code></pre></div>
<p>The new interface is similar but there are a few key differences.
All kernels are provided through <code class="language-text">availableKernels</code> and the kernels are named by the language rather than the kernel project name.
For example, before there was <code class="language-text">iPythonWith</code> and <code class="language-text">iHaskellWith</code>, and now it is <code class="language-text">availableKernels.python</code> and <code class="language-text">availableKernels.haskell</code>.
The version of Python uses is passed through the <code class="language-text">python</code> attribute and additional packages are provided with the <code class="language-text">extraPackages</code> attribute.
There is one new attribute, <code class="language-text">editablePackageSources</code>, which is used by poetry2nix, to add packages to the environment in editable mode.</p>
<p><em>New interface!</em></p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span>
  pkgs<span class="token punctuation">,</span>
  availableKernels<span class="token punctuation">,</span>
  kernelName<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">:</span>
availableKernels<span class="token punctuation">.</span>python<span class="token punctuation">.</span>override <span class="token punctuation">{</span>
  name <span class="token operator">=</span> <span class="token string">"python-with-numpy"</span><span class="token punctuation">;</span> <span class="token comment"># must be unique</span>
  displayName <span class="token operator">=</span> <span class="token string">"python with numpy"</span><span class="token punctuation">;</span> <span class="token comment"># name that appears in JupyterLab Web UI</span>
  python <span class="token operator">=</span> pkgs<span class="token punctuation">.</span>python3<span class="token punctuation">;</span>
  extraPackages <span class="token operator">=</span> ps<span class="token punctuation">:</span> <span class="token punctuation">[</span> ps<span class="token punctuation">.</span>numpy <span class="token punctuation">]</span><span class="token punctuation">;</span>
  editablePackageSources <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></code></pre></div>
<p>Both of these are still subject to the package versions available in <code class="language-text">nixpkgs</code>.
However, with Poetry, we can create a completely custom kernel with a <code class="language-text">pyproject.toml</code> file and specify exactly which package versions we want.
The full details are available in the <strong>How To</strong> and <strong>Tutorials</strong> sections of the <a href="https://jupyterwith.tweag.io">documentation</a>.</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>Usability has been improved, but there is much more to do.
The next major items on the roadmap include:</p>
<ul>
<li>Updating and improving the flake templates.</li>
<li>Updating and improving documentation on configuring existing kernels and packaging new kernels.</li>
<li>Providing better MacOS support.</li>
<li>Adding new and improving existing kernels.</li>
<li>Create a website indexing kernels that can be used and configured in jupyterWith.</li>
</ul>
<p>Join us in contributing to the project.
You can find the repository <a href="https://github.com/tweag/jupyterWith">here</a>.</p>]]></description><link>https://tweag.io/blog/2022-11-17-jupyter-with-next/</link><guid isPermaLink="false">https://tweag.io/blog/2022-11-17-jupyter-with-next/</guid><pubDate>Thu, 17 Nov 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Hard user separation with NixOS]]></title><description><![CDATA[<p>This guide explains how to install NixOS on a computer, with a twist.</p>
<p>If you use the same computer in different contexts, let’s say for work
and for your private life, you may wish to install two different
operating systems to protect your private life data from mistakes or
hacks from your work. For instance a cryptolocker you got from a
compromised work email won’t lock out your family photos.</p>
<p>But then you have two different operating systems to manage, and you
may consider that it’s not worth the effort and simply use the same
operating system for your private life and for work, at the cost of
the security you desired.</p>
<p>I offer you a third alternative, a single NixOS managing two securely
separated contexts. You choose your context at boot time, and you can
configure both context from either of them.</p>
<p>You can safely use the same machine at work with your home directory and
confidential documents, and you can get into your personal context with
your private data by doing a reboot. Compared to a dual boot system, you
have the benefits of a single system to manage and no duplicated package.</p>
<p>For this guide, you need a system either physical or virtual that is
supported by NixOS, and some knowledge like using a command line. You
don’t necessarily need to understand all the commands. The system disk
will be erased during the process.</p>
<p>You can find an example of NixOS configuration files to help
you understand the structure of the setup on <a href="https://github.com/tweag/nixos-specialisation-dual-boot">this GitHub
repository</a>.</p>
<h2 id="disks" style="position:relative;"><a href="#disks" aria-label="disks 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>Disks</h2>
<p>Here is a diagram showing the whole setup and the partitioning.</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/1ccb81a52eb667a0fc5ef6811ca5a958/ea64c/setup-diagram.png"
    style="display: block"
    target="_blank"
    rel="noopener"
  >
    <span
    class="gatsby-resp-image-background-image"
    style="padding-bottom: 78.37837837837837%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACKElEQVQ4y4VUiY7aMBDl/7+tUtV2CYQA4Sq7IeQkjm+/asZhl0Vt19KLnfHMy5yZhQB8hcf1le7M+yv+B+dKjOOZYcwFIVQI4d/6sxDWAAgbeL+EcwsGne/vSr2g77+jrr9BiB+wNoH3H3qPNjPvt3AuhXOrCSmADFrP4dySz9amMCZhMil/sY73KwAraDPn3XmSZeRhhq5LMQxp/JpNcetzVNUGdbXFKNbwPoUY9ixrmx2MWcPaFZ9Z1uYwJn5kRqxG72FNPrmeYhyP6Psjuu4IpbYT4QFNvUPbkO6GCYfbCU2zgxhOHMVEuAV5Sbm8h01hUrjWLRCQsaLWCYdm7QLWLjk8DlnPJ/0pZHqEsGLSmOg5e6r1CxcjJj3BOP7ke5LRXQgfctqjXUKE1VPpS24LY16hFLVKwZDyhBBKaH1mWFswSE42722Dvy4POR4h5QFCbKBUjq4lw1cIccYwHCAlyfcYbgeEMLxbzkIIuMMYC6UstO7R9wmUWnF/EoqC0pGjbTP0PbUWtdsaXZdAygJaO2htIiEQeGzaRqF4ExDDjYsUwjYa2hzn31tovUdd7VDXUR4LuoZSJcqLxLUUMeQ4r49Dq7hQcYKoghlOxyWszXG9ZqiqlO/ilKU8jp9CvnsYQ6fdTkmOc2tthbqiApRo2wuGW8kksaBUkPHdfiJ8JAuf/irsrwy4FAMuhUDxdkPTaHj//KeJ9k9V/iCP50c8r8/397T9AWww070omPSxAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
  ></span>
  <img
        class="gatsby-resp-image-image"
        alt="Picture showing a diagram of disks and partitions"
        title="Picture showing a diagram of disks and partitions"
        src="/static/1ccb81a52eb667a0fc5ef6811ca5a958/fcda8/setup-diagram.png"
        srcset="/static/1ccb81a52eb667a0fc5ef6811ca5a958/12f09/setup-diagram.png 148w,
/static/1ccb81a52eb667a0fc5ef6811ca5a958/e4a3f/setup-diagram.png 295w,
/static/1ccb81a52eb667a0fc5ef6811ca5a958/fcda8/setup-diagram.png 590w,
/static/1ccb81a52eb667a0fc5ef6811ca5a958/efc66/setup-diagram.png 885w,
/static/1ccb81a52eb667a0fc5ef6811ca5a958/ea64c/setup-diagram.png 1116w"
        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="partitioning" style="position:relative;"><a href="#partitioning" aria-label="partitioning 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>Partitioning</h3>
<p>We will create a 512 MB space for the /boot partition that will contain
the kernels, and allocate the space left for an LVM partition we can
split later.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">parted</span> /dev/sda -- mklabel gpt
<span class="token function">parted</span> /dev/sda -- mkpart ESP fat32 1MiB 512MiB
<span class="token function">parted</span> /dev/sda -- mkpart primary 512MiB <span class="token number">100</span>%
<span class="token function">parted</span> /dev/sda -- <span class="token builtin class-name">set</span> <span class="token number">1</span> esp on</code></pre></div>
<p>Note that these instructions are valid for UEFI systems, for older
systems you can refer to the NixOS manual to create a MBR partition.</p>
<p>In <a href="https://nixos.org/manual/nixos/stable/index.html#sec-installation-partitioning">the NixOS Manual</a>
you can find documentation with regard to disks and partitioning.</p>
<h3 id="create-lvm-volumes" style="position:relative;"><a href="#create-lvm-volumes" aria-label="create lvm volumes 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>Create LVM volumes</h3>
<p>We will use LVM so we need to initialize the partition and create a
Volume Group with all the free space.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">pvcreate /dev/sda2
vgcreate pool /dev/sda2</code></pre></div>
<p>We will then create three logical volumes, one for the store and two
for our environments:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">lvcreate <span class="token parameter variable">-L</span> 15G <span class="token parameter variable">-n</span> root-private pool
lvcreate <span class="token parameter variable">-L</span> 15G <span class="token parameter variable">-n</span> root-work pool
lvcreate <span class="token parameter variable">-l</span> <span class="token number">100</span>%FREE <span class="token parameter variable">-n</span> nix-store pool</code></pre></div>
<p>NOTE: The sizes to assign to each volume is up to you, the nix store
should have at least 30GB for a system with graphical sessions. LVM
allows you to keep free space in your volume group so you can increase
your volumes size later when needed.</p>
<h3 id="encryption" style="position:relative;"><a href="#encryption" aria-label="encryption 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>Encryption</h3>
<p>We will enable encryption for the three volumes, but we want the nix-store
partition to be unlockable with either of the keys used for the two
root partitions. This way, you don’t have to type two passphrases at boot.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">cryptsetup luksFormat /dev/pool/root-work
cryptsetup luksFormat /dev/pool/root-private
cryptsetup luksFormat /dev/pool/nix-store <span class="token comment"># same password as work</span>
cryptsetup luksAddKey /dev/pool/nix-store <span class="token comment"># same password as private</span></code></pre></div>
<p>We unlock our partitions to be able to format and mount them. Which
passphrase is used to unlock the nix-store doesn’t matter.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">cryptsetup luksOpen /dev/pool/root-work crypto-work
cryptsetup luksOpen /dev/pool/root-private crypto-private
cryptsetup luksOpen /dev/pool/nix-store nix-store</code></pre></div>
<p>Please note we don’t encrypt the boot partition, which is the default
on most encrypted Linux setup. While this could be achieved, this adds
complexity that I don’t want to cover in this guide.</p>
<p>Note: the nix-store partition isn’t called <code class="language-text">crypto-nix-store</code> because we
want the nix-store partition to be unlocked after the root partition to
reuse the password. The code generating the ramdisk takes the unlocked
partitions’ names in alphabetical order, by removing the prefix <code class="language-text">crypto</code>
the partition will always be after the root partitions.</p>
<h3 id="formatting" style="position:relative;"><a href="#formatting" aria-label="formatting 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>Formatting</h3>
<p>We format each partition using ext4, a performant file-system which
doesn’t require maintenance. You can use other filesystems, like xfs or btrfs,
if you need features specific to them.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">mkfs.ext4 /dev/mapper/crypto-work
mkfs.ext4 /dev/mapper/crypto-private
mkfs.ext4 /dev/mapper/nix-store</code></pre></div>
<h3 id="the-boot-partition" style="position:relative;"><a href="#the-boot-partition" aria-label="the boot partition 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 boot partition</h3>
<p>The boot partition should be formatted using fat32 when using UEFI with
<code class="language-text">mkfs.fat -F 32 /dev/sda1</code>. It can be formatted in ext4 if you are using
legacy boot (MBR).</p>
<h2 id="preparing-the-system" style="position:relative;"><a href="#preparing-the-system" aria-label="preparing the system 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>Preparing the system</h2>
<p>Mount the partitions onto <code class="language-text">/mnt</code> and its subdirectories to prepare for
the installer.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">mount</span> /dev/mapper/crypto-work /mnt
<span class="token function">mkdir</span> <span class="token parameter variable">-p</span> /mnt/etc/nixos /mnt/boot /mnt/nix
<span class="token function">mount</span> /dev/mapper/nix-store /mnt/nix
<span class="token function">mkdir</span> /mnt/nix/config
<span class="token function">mount</span> <span class="token parameter variable">--bind</span> /mnt/nix/config /mnt/etc/nixos
<span class="token function">mount</span> /dev/sda1 /mnt/boot</code></pre></div>
<p>We generate a configuration file:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">nixos-generate-config <span class="token parameter variable">--root</span> /mnt</code></pre></div>
<p>Edit <code class="language-text">/mnt/etc/nixos/hardware-configuration.nix</code> to change the following parts:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">fileSystems<span class="token punctuation">.</span><span class="token string">"/"</span> <span class="token operator">=</span>
  <span class="token punctuation">{</span> device <span class="token operator">=</span> <span class="token string">"/dev/disk/by-uuid/xxxxxxx-something"</span><span class="token punctuation">;</span>
    fsType <span class="token operator">=</span> <span class="token string">"ext4"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>luks<span class="token punctuation">.</span>devices<span class="token punctuation">.</span><span class="token string">"crypto-work"</span><span class="token punctuation">.</span>device <span class="token operator">=</span> <span class="token string">"/dev/disk/by-uuid/xxxxxx-something"</span><span class="token punctuation">;</span></code></pre></div>
<p>by</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">fileSystems<span class="token punctuation">.</span><span class="token string">"/"</span> <span class="token operator">=</span>
  <span class="token punctuation">{</span> device <span class="token operator">=</span> <span class="token string">"/dev/mapper/crypto-work"</span><span class="token punctuation">;</span>
    fsType <span class="token operator">=</span> <span class="token string">"ext4"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>luks<span class="token punctuation">.</span>devices<span class="token punctuation">.</span><span class="token string">"crypto-work"</span><span class="token punctuation">.</span>device <span class="token operator">=</span> <span class="token string">"/dev/pool/root-work"</span><span class="token punctuation">;</span></code></pre></div>
<p>We need two configuration files to describe our two environments, we will
use <code class="language-text">hardware-configuration.nix</code> as a template and apply changes to it.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">sed</span> <span class="token string">'/imports =/,+3d'</span> /mnt/etc/nixos/hardware-configuration.nix <span class="token operator">></span> /mnt/etc/nixos/work.nix
<span class="token function">sed</span> <span class="token string">'/imports =/,+3d ; s/-work/-private/g'</span> /mnt/etc/nixos/hardware-configuration.nix <span class="token operator">></span> /mnt/etc/nixos/private.nix
<span class="token function">rm</span> /mnt/etc/nixos/hardware-configuration.nix</code></pre></div>
<p>Edit <code class="language-text">/mnt/etc/nixos/configuration.nix</code> to make the <code class="language-text">imports</code> code at
the top of the file look like this:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">imports <span class="token operator">=</span>
  <span class="token punctuation">[</span>
    <span class="token url">./work.nix</span>
    <span class="token url">./private.nix</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre></div>
<p>Remember we removed the file <code class="language-text">/mnt/etc/nixos/hardware-configuration.nix</code>
so it shouldn’t be imported anymore.</p>
<p>Now we need to hook each configuration to become a different boot entry,
using the NixOS feature called <a href="https://www.tweag.io/blog/2022-08-18-nixos-specialisations/">specialisation</a>. We will make
the environment you want to be the default in the boot entry as a
non-specialised environment and non-inherited so it’s not picked up by
the other, and a specialisation for the other environment.</p>
<p>For the hardware configuration files, we need to wrap them with some
code to create a specialisation, and the “non-specialisation” case that
won’t propagate to the other specialisations.</p>
<p>Starting from a file looking like this, some code must be added at the
top and bottom of the files depending on if you want it to be the default
context or not.</p>
<p>Content of an example file:</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> config<span class="token punctuation">,</span> pkgs<span class="token punctuation">,</span> modulesPath<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 punctuation">{</span>
  boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>availableKernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"ata_generic"</span> <span class="token string">"uhci_hcd"</span> <span class="token string">"ehci_pci"</span> <span class="token string">"ahci"</span> <span class="token string">"usb_storage"</span> <span class="token string">"sd_mod"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>kernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"dm-snapshot"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  boot<span class="token punctuation">.</span>kernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"kvm-intel"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  boot<span class="token punctuation">.</span>extraModulePackages <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

  fileSystems<span class="token punctuation">.</span><span class="token string">"/"</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
    device <span class="token operator">=</span> <span class="token string">"/dev/mapper/crypto-private"</span><span class="token punctuation">;</span>
    fsType <span class="token operator">=</span> <span class="token string">"ext4"</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token number">8</span><span class="token operator">&lt;</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 punctuation">[</span>more code here<span class="token punctuation">]</span>
  <span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token number">8</span><span class="token operator">&lt;</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>

  swapDevices <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  networking<span class="token punctuation">.</span>useDHCP <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkDefault <span class="token boolean">true</span><span class="token punctuation">;</span>
  hardware<span class="token punctuation">.</span>cpu<span class="token punctuation">.</span>intel<span class="token punctuation">.</span>updateMicrocode <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkDefault config<span class="token punctuation">.</span>hardware<span class="token punctuation">.</span>enableRedistributableFirmware<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div>
<p>Example result of the default context (<a href="https://github.com/tweag/nixos-specialisation-dual-boot/blob/master/configuration/private.nix">See on GitHub</a>):</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">(</span><span class="token punctuation">{</span> lib<span class="token punctuation">,</span> config<span class="token punctuation">,</span> pkgs<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 punctuation">{</span>
  config <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkIf <span class="token punctuation">(</span>config<span class="token punctuation">.</span>specialisation <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>

    boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>availableKernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"ata_generic"</span> <span class="token string">"uhci_hcd"</span> <span class="token string">"ehci_pci"</span> <span class="token string">"ahci"</span> <span class="token string">"usb_storage"</span> <span class="token string">"sd_mod"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>kernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"dm-snapshot"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    boot<span class="token punctuation">.</span>kernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"kvm-intel"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    boot<span class="token punctuation">.</span>extraModulePackages <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    fileSystems<span class="token punctuation">.</span><span class="token string">"/"</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
      device <span class="token operator">=</span> <span class="token string">"/dev/mapper/crypto-private"</span><span class="token punctuation">;</span>
      fsType <span class="token operator">=</span> <span class="token string">"ext4"</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token number">8</span><span class="token operator">&lt;</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 punctuation">[</span>more code here<span class="token punctuation">]</span>
    <span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token number">8</span><span class="token operator">&lt;</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>

    swapDevices <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    networking<span class="token punctuation">.</span>useDHCP <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkDefault <span class="token boolean">true</span><span class="token punctuation">;</span>
    hardware<span class="token punctuation">.</span>cpu<span class="token punctuation">.</span>intel<span class="token punctuation">.</span>updateMicrocode <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkDefault config<span class="token punctuation">.</span>hardware<span class="token punctuation">.</span>enableRedistributableFirmware<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>Note the extra leading <code class="language-text">(</code> character that must also be added at the
very beginning.</p>
<p>Example result for a specialisation named <code class="language-text">work</code> (<a href="https://github.com/tweag/nixos-specialisation-dual-boot/blob/master/configuration/work.nix">See on GitHub</a>):</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix"><span class="token punctuation">{</span> config<span class="token punctuation">,</span> lib<span class="token punctuation">,</span> pkgs<span class="token punctuation">,</span> modulesPath<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 punctuation">{</span>
  specialisation <span class="token operator">=</span> <span class="token punctuation">{</span>
  work<span class="token punctuation">.</span>configuration <span class="token operator">=</span> <span class="token punctuation">{</span>
  system<span class="token punctuation">.</span>nixos<span class="token punctuation">.</span>tags <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token string">"work"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span>

    boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>availableKernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"ata_generic"</span> <span class="token string">"uhci_hcd"</span> <span class="token string">"ehci_pci"</span> <span class="token string">"ahci"</span> <span class="token string">"usb_storage"</span> <span class="token string">"sd_mod"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>kernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"dm-snapshot"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    boot<span class="token punctuation">.</span>kernelModules <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"kvm-intel"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    boot<span class="token punctuation">.</span>extraModulePackages <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    fileSystems<span class="token punctuation">.</span><span class="token string">"/"</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
      device <span class="token operator">=</span> <span class="token string">"/dev/mapper/crypto-work"</span><span class="token punctuation">;</span>
      fsType <span class="token operator">=</span> <span class="token string">"ext4"</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token number">8</span><span class="token operator">&lt;</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 punctuation">[</span>more code here<span class="token punctuation">]</span>
    <span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token number">8</span><span class="token operator">&lt;</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>

    swapDevices <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    networking<span class="token punctuation">.</span>useDHCP <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkDefault <span class="token boolean">true</span><span class="token punctuation">;</span>
    hardware<span class="token punctuation">.</span>cpu<span class="token punctuation">.</span>intel<span class="token punctuation">.</span>updateMicrocode <span class="token operator">=</span> lib<span class="token punctuation">.</span>mkDefault config<span class="token punctuation">.</span>hardware<span class="token punctuation">.</span>enableRedistributableFirmware<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>
<h2 id="system-configuration" style="position:relative;"><a href="#system-configuration" aria-label="system 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>System configuration</h2>
<p>It’s now the time to configure your system as you want. The file
<code class="language-text">/mnt/etc/nixos/configuration.nix</code> contains shared configuration, this
is the right place to define your user, shared packages, network and services.</p>
<p>The files <code class="language-text">/mnt/etc/nixos/private.nix</code> and <code class="language-text">/mnt/etc/nixos/work.nix</code>
can be used to define context specific configuration.</p>
<h3 id="lvm-workaround" style="position:relative;"><a href="#lvm-workaround" aria-label="lvm workaround 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>LVM Workaround</h3>
<p>During the numerous installation tests I’ve made to validate this guide,
on some hardware I noticed an issue with LVM detection, add this line
to your global configuration file to be sure your disks will be detected at boot.</p>
<div class="gatsby-highlight" data-language="nix"><pre class="language-nix"><code class="language-nix">    boot<span class="token punctuation">.</span>initrd<span class="token punctuation">.</span>preLVMCommands <span class="token operator">=</span> <span class="token string">"lvm vgchange -ay"</span><span class="token punctuation">;</span></code></pre></div>
<h2 id="installation" style="position:relative;"><a href="#installation" aria-label="installation 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>Installation</h2>
<h3 id="first-installation" style="position:relative;"><a href="#first-installation" aria-label="first installation 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>First installation</h3>
<p>The partitions are mounted and you configured your system as you want it, we can run the NixOS installer.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">nixos-install</code></pre></div>
<p>Wait for the copy process to complete after which you will be prompted
for the root password of the current crypto-work environment (or the
one you mounted here), you also need to define the password for your
user now by chrooting into your NixOS system.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token comment">## nixos-enter --root /mnt -c "passwd your_user"</span>
New password:
Retape new password:
passwd: password updated successfully
<span class="token comment">## umount -R /mnt</span></code></pre></div>
<p>From now, you have a password set for root and your user for the
crypto-work environment, but no password are defined in the crypto-private
environment.</p>
<h3 id="second-installation" style="position:relative;"><a href="#second-installation" aria-label="second installation 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>Second installation</h3>
<p>We will rerun the installation process with the other environment mounted:</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">mount</span> /dev/mapper/crypto-private  /mnt
<span class="token function">mkdir</span> <span class="token parameter variable">-p</span> /mnt/etc/nixos /mnt/boot /mnt/nix

<span class="token function">mount</span> /dev/mapper/nix-store /mnt/nix
<span class="token function">mount</span> <span class="token parameter variable">--bind</span> /mnt/nix/config /mnt/etc/nixos
<span class="token function">mount</span> /dev/sda1 /mnt/boot</code></pre></div>
<p>As the NixOS configuration is already done and is shared between the two
environments, just run <code class="language-text">nixos-install</code>, wait for the root password to
be prompted, apply the same chroot sequence to set a password to your
user in this environment.</p>
<p>You can reboot, you will have a default boot entry for the default chosen
environment, and the other environment boot entry, both requiring their
own passphrase to be used.</p>
<p>Now, you can apply changes to your NixOS system using <code class="language-text">nixos-rebuild</code>
from both work and private environments.</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>Congratulations for going through this long installation process. You
can now log in to your two contexts and use them independently, and you can
configure them by applying changes to the corresponding files in <code class="language-text">/etc/nixos/</code>.</p>
<h2 id="going-further" style="position:relative;"><a href="#going-further" aria-label="going further 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>Going further</h2>
<h3 id="swap-and-hibernation" style="position:relative;"><a href="#swap-and-hibernation" aria-label="swap and hibernation 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>Swap and hibernation</h3>
<p>With this setup, I chose to not cover swap space because this would allow
to leak secrets between the contexts. If you need some swap, you will
have to create a file on the root partition of your current context,
and add the according code to the context filesystems.</p>
<p>If you want to use hibernation in which the system stops after dumping
its memory into the swap file, your swap size must be larger than the
memory available on the system.</p>
<p>It’s possible to have a single swap for both contexts by using a random
encryption at boot for the swap space, but this breaks hibernation
as you can’t unlock the swap to resume the system.</p>
<h3 id="declare-users-passwords" style="position:relative;"><a href="#declare-users-passwords" aria-label="declare users passwords 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>Declare users’ passwords</h3>
<p>As you noticed, you had to run <code class="language-text">passwd</code> in both contexts to define your
user password and root’s password. It is possible to define their password
declaratively in the configuration file, refers to the documentation
of<code class="language-text">users.mutableUsers</code> and <code class="language-text">users.extraUsers.&lt;name>.initialHashedPassword</code>
for more information.</p>
<h3 id="rescue-the-installation" style="position:relative;"><a href="#rescue-the-installation" aria-label="rescue the installation 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>Rescue the installation</h3>
<p>If something is wrong when you boot the first time, you can reuse the
installer to make changes to your installation: you can run again the
<code class="language-text">cryptsetup luksOpen</code> and <code class="language-text">mount</code> commands to get access to your
filesystems, then you can edit your configuration files and run
<code class="language-text">nixos-install</code> again.</p>]]></description><link>https://tweag.io/blog/2022-11-01-hard-user-separation-with-nixos/</link><guid isPermaLink="false">https://tweag.io/blog/2022-11-01-hard-user-separation-with-nixos/</guid><pubDate>Tue, 01 Nov 2022 00:00:00 GMT</pubDate></item></channel></rss>