<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lawngno.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lawngno.me/" rel="alternate" type="text/html" /><updated>2025-04-07T23:41:15+00:00</updated><id>https://lawngno.me/feed.xml</id><title type="html">Gnome home</title><subtitle>Various musings of Adam Harvey.</subtitle><entry><title type="html">What licences do Rust crates choose?</title><link href="https://lawngno.me/blog/2025/03/06/crate-licences.html" rel="alternate" type="text/html" title="What licences do Rust crates choose?" /><published>2025-03-06T19:00:00+00:00</published><updated>2025-03-06T19:00:00+00:00</updated><id>https://lawngno.me/blog/2025/03/06/crate-licences</id><content type="html" xml:base="https://lawngno.me/blog/2025/03/06/crate-licences.html"><![CDATA[<p>A few months ago, the question was posed: what licences do crates on
<a href="https://crates.io/">crates.io</a> use?</p>

<p>Obviously I couldn’t resist the opportunity to dive into this, so I gathered
the data, and generated this chart:</p>

<div style="margin-bottom: 1em"><canvas id="individual"></canvas></div>

<p>Or, in table format, the top 10:</p>

<table>
  <thead>
    <tr>
      <th>Licence</th>
      <th>Count</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>MIT</td>
      <td>110204</td>
    </tr>
    <tr>
      <td>Apache-2.0</td>
      <td>61993</td>
    </tr>
    <tr>
      <td>GPL-3.0</td>
      <td>4970</td>
    </tr>
    <tr>
      <td>MPL-2.0</td>
      <td>3676</td>
    </tr>
    <tr>
      <td>BSD-3-Clause</td>
      <td>2951</td>
    </tr>
    <tr>
      <td>GPL-3.0-or-later</td>
      <td>2818</td>
    </tr>
    <tr>
      <td>AGPL-3.0</td>
      <td>1549</td>
    </tr>
    <tr>
      <td>Unlicense</td>
      <td>1426</td>
    </tr>
    <tr>
      <td>AGPL-3.0-or-later</td>
      <td>1116</td>
    </tr>
    <tr>
      <td>ISC</td>
      <td>1072</td>
    </tr>
  </tbody>
</table>

<p>The keen-eyed observer will note that the numbers add up quite obviously to
more than the actual number of crates published on crates.io. This is because
many crates are dual (or even more than dual) licensed: in fact, over 40,000
crates are dual licensed under MIT and Apache 2.0. (This is about a quarter of
all crates on crates.io!)</p>

<p>I don’t think <code class="language-plaintext highlighter-rouge">cargo</code> has ever prefilled a licence for new projects, so I
suspect this is purely the social norm arising from Rust itself being dual MIT
and Apache 2.0 licensed: that led to many early and influential crates being
dual licensed the same way, and then it snowballed from there.</p>

<blockquote>
  <p>Update: After publishing this post, <a href="https://hachyderm.io/@epage">Ed Page</a>
pointed me to <a href="https://rust-lang.github.io/api-guidelines/necessities.html#crate-and-its-dependencies-have-a-permissive-license-c-permissive">this page on Rust API
guidelines</a>
which does explicitly recommend the MIT OR Apache-2.0 combination, so that
may explain at least some of its dominance.</p>
</blockquote>

<p>I tried to visualise the most common combinations of licences, but I don’t
think it ended up being all that useful, since I had to take the logarithm of
the edge weights due to how dominant the MIT and Apache 2.0 pair was.
Nevertheless, here it is:</p>

<div id="graph" style="width: 100%; aspect-ratio: 1 / 1"></div>

<p>The processed data (as of November 21, 2024)<sup id="fnref:late" role="doc-noteref"><a href="#fn:late" class="footnote" rel="footnote">1</a></sup> is available: both <a href="/assets/crate-licences/individual.json">the
individual licence counts after splitting multi-licensed
crates</a>, along with <a href="/assets/crate-licences/graph.json">the graph data
showing all the pairs found in multi-licensed
projects</a>. I’ve also included <a href="/assets/crate-licences/licences.csv">the raw CSV
generated from the crates.io database</a>,
which is simply the count of each licence seen in the default version for each
crate.</p>

<p>I’ve also published <a href="https://github.com/LawnGnome/licence-vis">the analysis code to
GitHub</a>. It’s a little fiddly, but if
anyone wants to replicate or build on this, that’s where to start.</p>

<p>May your crates be forever licensed appropriately! (Or at all.)</p>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" integrity="sha512-CQBWl4fJHWbryGE+Pc7UAxWMUMNMWzWxF4SQo9CgkJIN1kx6djDQZjh3Y8SZ1d+6I+1zze6Z7kHXO7q3UyZAWw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script>
document.addEventListener("DOMContentLoaded", async () => {
  const raw = await fetch("/assets/crate-licences/individual.json");
  let individual = await raw.json();
  individual = individual.slice(0, 15);

  const chart = new Chart(
    document.getElementById("individual"),
    {
      type: "doughnut",
      data: {
        labels: [...individual.map(r => r.name)],
        datasets: [
          {
            data: [...individual.map(r => r.count)],
          }
        ]
      },
    },
  );
});
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.30.3/cytoscape.min.js" integrity="sha512-oHBLiJyO02B4XgizsMos74uf/p8b1fUqLV5Pc8HaFF6rR0123STTQN3qOh29hYvdId5g24Wfno5gpfsP8enzfQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script src="https://cdn.jsdelivr.net/npm/cytoscape-qtip@2.8.0/cytoscape-qtip.min.js"></script>

<script>
document.addEventListener("DOMContentLoaded", async () => {
  const raw = await fetch("/assets/crate-licences/graph.json");
  let graph = await raw.json();
  graph = [...graph.filter(({count}) => count > 9)];

  const nodes = {};
  const edges = [];

  let total = 0;
  for (const {count} of graph) {
    total += Math.log2(count);
  }

  for (const {a, b, count} of graph) {
    nodes[a] = {data: {id: a}, selectable: false};
    nodes[b] = {data: {id: b}, selectable: false};

    let width = 80 * Math.log2(count) / total;
    if (width < 0.5) {
      width = 0.5;
    }
    edges.push({
      data: {
        id: `${a}::${b}`,
        source: a,
        target: b,
      },
      style: {width},
      selectable: false,
    });
  }

  const cy = cytoscape({
    container: document.getElementById("graph"),
    elements: {
      nodes: Object.values(nodes),
      edges,
    },
    layout: {
      name: "circle",
    },
    style: [
      {
        selector: "node",
        style: {
          label: "data(id)",
        },
      },
      {
        selector: "edge",
        style: {
          width: 3,
          "curve-style": "unbundled-bezier",
          "line-color": "black",
        },
      },
    ],
    autoungrabify: true,
    autounselectify: true,
    boxSelectionEnabled: false,
    userPanningEnabled: false,
    userZoomingEnabled: false,
  });

  cy.on("mouseover", "edge", event => {
    event.cyTarget.qtip({
      content: "hello",
      show: {
        event: event.type,
        ready: true,
      },
      hide: {
        event: "mouseout unfocus",
      },
    })
  });
});
</script>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:late" role="doc-endnote">

      <p>Yes, I’ve been sitting on this for a while. I did the visualisations and
then completely forgot I had to actually write a blog post around them. <a href="#fnref:late" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[A few months ago, the question was posed: what licences do crates on crates.io use?]]></summary></entry><entry><title type="html">Niri, Waybar, and Rust</title><link href="https://lawngno.me/blog/2025/03/06/niri.html" rel="alternate" type="text/html" title="Niri, Waybar, and Rust" /><published>2025-03-06T16:59:00+00:00</published><updated>2025-03-06T16:59:00+00:00</updated><id>https://lawngno.me/blog/2025/03/06/niri</id><content type="html" xml:base="https://lawngno.me/blog/2025/03/06/niri.html"><![CDATA[<p>As a long term believer in it being the year of Linux on the desktop, I’ve been
using <a href="https://swaywm.org/">Sway</a> for several years as my main compositor and, by extension,
desktop. Like a lot of people, I combine that with <a href="https://github.com/Alexays/Waybar">Waybar</a> to provide
some basic desktop environment functionality like a system tray, clock, etc.</p>

<p>For the last few weeks, I’ve been trying out <a href="https://github.com/YaLTeR/niri">Niri</a> instead. Niri is
another Wayland compositor that takes inspiration from a project I haven’t
tried called <a href="https://github.com/paperwm/PaperWM">PaperWM</a> to implement a “scrollable tiling” environment.
I’ve been enjoying it: my Sway setup is generally to have one app per
workspace, with one “chat” workspace in tabbed mode with whatever communication
apps I have running at any given moment.<sup id="fnref:comms" role="doc-noteref"><a href="#fn:comms" class="footnote" rel="footnote">1</a></sup></p>

<p>I haven’t 100% decided if I’m going to stick with Niri or go back to Sway
(truthfully, I’ll probably switch back and forth for a while), but one gap I
felt pretty quickly in Niri is that the only option for a running app list in
Waybar is the <a href="https://github.com/Alexays/Waybar/wiki/Module:-Taskbar"><code class="language-plaintext highlighter-rouge">wlr/taskbar</code></a> module. Happily, Niri does implement
the required Wayland protocols for this to work, but less happily, apps
essentially show up in random order.<sup id="fnref:order" role="doc-noteref"><a href="#fn:order" class="footnote" rel="footnote">2</a></sup> It’s possible to drag to reorder
the apps, but that’s obviously a manual process, and because <del>I’m crazy</del> I
like things being ordered, I would like everything to be in the same order it’s
on screen: first workspace first, then leftmost window first.</p>

<blockquote>
  <p>An aside: why wasn’t this an issue for me in Sway, you might ask? That’s
because I hacked some terrible code that I’m embarrassed of and hence have
never released to rename workspaces based on their active window
title<sup id="fnref:chat" role="doc-noteref"><a href="#fn:chat" class="footnote" rel="footnote">3</a></sup>, which then meant that the <a href="https://github.com/Alexays/Waybar/wiki/Module:-Sway#workspaces"><code class="language-plaintext highlighter-rouge">sway/workspaces</code> Waybar
module</a> got me close enough to what I wanted.</p>
</blockquote>

<p>So, I figured, could I create a Waybar module to do what I wanted?</p>

<hr />

<p>Waybar has long supported <a href="https://github.com/Alexays/Waybar/wiki/Module:-Custom">custom modules</a>, which are useful, but
limited in what UI they can create: it’s basically just a single label with a
single tooltip. That isn’t sufficient here.</p>

<p>About a year ago, I became aware of <a href="https://github.com/Alexays/Waybar/wiki/Module:-CFFI">Waybar’s <code class="language-plaintext highlighter-rouge">cffi</code> module</a>, which
allows you to implement Waybar modules in anything that can build a shared
library with a C API. You get a Gtk 3<sup id="fnref:gtk3" role="doc-noteref"><a href="#fn:gtk3" class="footnote" rel="footnote">4</a></sup> container, and can then do
whatever you want within that. I threw together a rough Rust binding, couldn’t
get one of my demos to work<sup id="fnref:not-working" role="doc-noteref"><a href="#fn:not-working" class="footnote" rel="footnote">5</a></sup>, and then left the code to rot in a
directory.<sup id="fnref:rot" role="doc-noteref"><a href="#fn:rot" class="footnote" rel="footnote">6</a></sup></p>

<p>Now, though, I had real motivation to get something useful together, so I blew
the dust off the bindings, fixed the bugs, brought them up to date with 2024
edition Rust, and have now published them as the <a href="https://crates.io/crates/waybar-cffi"><code class="language-plaintext highlighter-rouge">waybar-cffi</code>
crate</a>. You implement <a href="https://docs.rs/waybar-cffi/0.1.0/waybar_cffi/trait.Module.html">a <code class="language-plaintext highlighter-rouge">Module</code> trait</a>, <a href="https://docs.rs/waybar-cffi/0.1.0/waybar_cffi/macro.waybar_module.html">call a
macro</a>, and you have a Waybar module! Obviously I’m biased, but I think
<a href="https://docs.rs/waybar-cffi/0.1.0/waybar_cffi/index.html#example">the Hello World example is pretty nifty</a>.</p>

<p>Could I implement the taskbar of my dreams using that?</p>

<hr />

<p>No suspense here: the answer is obviously yes, otherwise I probably wouldn’t be
writing a blog post.</p>

<p><img src="/images/niri/screenshot.png" alt="A screenshot of Niri and Waybar with a fetching taskbar" /></p>

<p>This looks pretty similar to <code class="language-plaintext highlighter-rouge">wlr/taskbar</code> out of the box: I like that module
in general, so there wasn’t much need to change anything. The main differences
are that windows and window events are synced via <a href="https://docs.rs/niri-ipc/">Niri’s IPC
protocol</a>, and apps are sorted by workspace index, so they’re mostly
in the order they appear on my desktop.</p>

<p>The “mostly” above is because Niri <a href="https://github.com/YaLTeR/niri/discussions/721">doesn’t yet provide window geometry in its
IPC</a>, which means the taskbar can’t order apps within a workspace
based on their position.<sup id="fnref:geometry" role="doc-noteref"><a href="#fn:geometry" class="footnote" rel="footnote">7</a></sup> In practice, this isn’t a huge issue for me
because the window ID ordering basically works as a fallback, but I’d love to
add that at some point.</p>

<p>The other main thing I implemented in the initial version is app highlighting,
which you can kind of see on the Signal icon there. Basically, I’ve written the
module configuration to allow this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"cffi/niri-taskbar"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"apps"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"signal"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"match"</span><span class="p">:</span><span class="w"> </span><span class="s2">"</span><span class="se">\\</span><span class="s2">([0-9]+</span><span class="se">\\</span><span class="s2">)$"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"class"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unread"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Which translates to:</p>

<ol>
  <li>If the app ID is <code class="language-plaintext highlighter-rouge">signal</code>, <em>and</em></li>
  <li>The window title matches the given regex<sup id="fnref:signal" role="doc-noteref"><a href="#fn:signal" class="footnote" rel="footnote">8</a></sup>, <em>then</em></li>
  <li>Add the <code class="language-plaintext highlighter-rouge">unread</code> CSS class to the window button.</li>
</ol>

<p>At which point I can use Waybar’s <a href="https://github.com/Alexays/Waybar/wiki/Styling">normal styling support</a>, built on
top of Gtk’s CSS support, to apply a highlighted border.</p>

<p>This is particularly useful in Niri because, unlike Sway, Niri doesn’t
currently implement a Wayland protocol to indicate that a toplevel window is
“urgent” (or, put another way, has a notification pending). But, even on Sway,
I actually prefer to have a middle ground quiet way of being notified that
something might be pending.</p>

<p>Anyway, this is the dictionary definition of “works for me” right now. I don’t
know if it’ll be useful to anyone else.</p>

<hr />

<p>I do have some rough plans for the taskbar module. In no particular order:</p>

<ol>
  <li>I haven’t even considered multiple outputs yet. That’ll probably need
something.</li>
  <li>I don’t know if <em>I</em> want this, but some way of putting a border or number
between workspaces for additional visual grouping would probably be nice.</li>
  <li>I wonder if it’s possible to fake urgency by peeking<sup id="fnref:peeking" role="doc-noteref"><a href="#fn:peeking" class="footnote" rel="footnote">9</a></sup> on
notifications on D-Bus and highlighting those apps until they become
focused.</li>
</ol>

<p>But we’ll see how far I get on this. Like I said, I’m not even totally sure
I’ll keep using Niri in the longer term, in which case the taskbar module would
no longer be useful to me. Regardless, though, I’m glad to finally get the Rust
bindings out for Waybar CFFI, and I hope those are more generally useful to
others no matter what happens with the taskbar.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:comms" role="doc-endnote">

      <p>Signal and IRCCloud are permanent fixtures, Slack and Zulip are <em>usually</em>
running unless I’m actively trying to disconnect, and then it’s usually
whatever combination of WhatsApp, Messenger, and Discord I need on a given
day. <a href="#fnref:comms" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:order" role="doc-endnote">

      <p>OK, so that’s not <em>strictly</em> true: they show up in window ID order,
which right now basically matches startup order. (Although Niri <a href="https://docs.rs/niri-ipc/25.2.0/niri_ipc/struct.Window.html#structfield.id">explicitly
documents that this is not to be relied upon</a>, so who knows how
long that will be true.) <a href="#fnref:order" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:chat" role="doc-endnote">

      <p>It’s actually worse than that: I <em>also</em> included some functionality to
parse the window titles for chat applications and set the workspace title
based on which apps needed attention. <a href="#fnref:chat" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:gtk3" role="doc-endnote">

      <p>Waybar only supports Gtk 3 at present; the hold up on [Gtk 4 support][gtk4]
is, as I understand it, system tray support. This was mildly annoying when
I had to use fractional scaling, since Gtk 3 doesn’t support it natively,
but honestly isn’t an issue for me now that I put a 2x screen in my
Framework. <a href="#fnref:gtk3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:not-working" role="doc-endnote">

      <p>This turned out to be because I’d forgotten literally everything I once
knew about how Gtk and the Glib event loop work. <a href="#fnref:not-working" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:rot" role="doc-endnote">
      <p>I do this way too often. <a href="#fnref:rot" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:geometry" role="doc-endnote">

      <p>I spent quite a bit of time trying to figure out if there was a way to get
that information out of a Wayland protocol instead of Niri, and I think the
answer right now is “not really”. I may be mistaken! <a href="#fnref:geometry" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:signal" role="doc-endnote">

      <p>Which matches how Signal indicates unread messages — it appends <code class="language-plaintext highlighter-rouge">(N)</code> to
the window title, where <code class="language-plaintext highlighter-rouge">N</code> is the number of unreads. <a href="#fnref:signal" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:peeking" role="doc-endnote">

      <p>I’ve played around with some of this before, and I have a horrible feeling
that the actual way of doing this is implementing the entire notification
interface and then delegating to whatever actual notification daemon you
want, but I’d like to be wrong here. <a href="#fnref:peeking" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[As a long term believer in it being the year of Linux on the desktop, I’ve been using Sway for several years as my main compositor and, by extension, desktop. Like a lot of people, I combine that with Waybar to provide some basic desktop environment functionality like a system tray, clock, etc.]]></summary></entry><entry><title type="html">999 crates of Rust on the wall</title><link href="https://lawngno.me/blog/2024/06/10/divine-provenance.html" rel="alternate" type="text/html" title="999 crates of Rust on the wall" /><published>2024-06-10T17:22:00+00:00</published><updated>2024-06-10T17:22:00+00:00</updated><id>https://lawngno.me/blog/2024/06/10/divine-provenance</id><content type="html" xml:base="https://lawngno.me/blog/2024/06/10/divine-provenance.html"><![CDATA[<h2 id="tldr">tl;dr</h2>

<p>I’ve been comparing crates on crates.io against their upstream repositories in
an effect to detect (and, ultimately, help prevent) supply chain attacks like
the <code class="language-plaintext highlighter-rouge">xz</code> backdoor<sup id="fnref:xz" role="doc-noteref"><a href="#fn:xz" class="footnote" rel="footnote">1</a></sup>, where the code published in a package doesn’t match the
code in its repository.</p>

<p>The results of these comparisons for the most popular 999<sup id="fnref:999" role="doc-noteref"><a href="#fn:999" class="footnote" rel="footnote">2</a></sup> crates by download
count <a href="https://lawngnome.github.io/divine-provenance/">are now available</a>. These come with a bunch of
caveats that I’ll get into below, but I hope it’s a useful starting point for
discussing code provenance in the Rust ecosystem.</p>

<p>No evidence of malicious activity was detected as part of this work, and
approximately 83% of the current versions of these popular crates match their
upstream repositories exactly.</p>

<h2 id="background">Background</h2>

<p>I’ve been employed by the <a href="https://foundation.rust-lang.org/">Rust Foundation</a><sup id="fnref:thanks" role="doc-noteref"><a href="#fn:thanks" class="footnote" rel="footnote">3</a></sup> to work
on security matters for a bit over a year now. My focus has mostly been on the
crate ecosystem thus far, and especially around supply chain security.</p>

<p>After the <code class="language-plaintext highlighter-rouge">xz</code> backdoor<sup id="fnref:xz:1" role="doc-noteref"><a href="#fn:xz" class="footnote" rel="footnote">1</a></sup>, one question that immediately came up was “could
a crate be compromised in the same way?”. Perhaps an even more urgent question
was “<em>is</em> a crate compromised in the same way?”. We need tooling to be able to
answer those questions in an ongoing way.</p>

<h2 id="analysis">Analysis</h2>

<p>I’ve built a standalone tool to analyse crate versions, which I intend to
eventually turn into something that performs ongoing analysis of newly
published crate files. (Once that’s ready, this will be open sourced — the
current tool relies on personal tooling I’ve built to mirror crates.io, which
is (a) ugly as hell, and (b) not useful to anyone except me in its current
form. The <a href="#methodology">methodology</a> is described below, though.)</p>

<p>For now, I’ve analysed the top 999<sup id="fnref:999:1" role="doc-noteref"><a href="#fn:999" class="footnote" rel="footnote">2</a></sup> crates on crates.io.</p>

<p>As part of that, I also built a rough and ready tool to visualise the results
for spot checking purposes, which I’ve now turned into a static site generator
and have used to publish <a href="https://lawngnome.github.io/divine-provenance/">the results of that
analysis</a>.<sup id="fnref:frontend" role="doc-noteref"><a href="#fn:frontend" class="footnote" rel="footnote">4</a></sup></p>

<h2 id="methodology">Methodology</h2>

<p>The short version here is:</p>

<ol>
  <li>Take every version of each crate.</li>
  <li>See if the manifest <a href="https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field">defines a <code class="language-plaintext highlighter-rouge">repository</code></a>.</li>
  <li>See if the crate file <a href="https://doc.rust-lang.org/cargo/commands/cargo-package.html#cargo_vcs_infojson-format">includes VCS metadata</a>.</li>
  <li>Clone the repository at the given revision.</li>
  <li>Run <code class="language-plaintext highlighter-rouge">cargo package</code> to rebuild the crate file.<sup id="fnref:crate-package" role="doc-noteref"><a href="#fn:crate-package" class="footnote" rel="footnote">5</a></sup></li>
  <li>See if it matches!</li>
</ol>

<p>Simple, right? Right?</p>

<p>Well, as it turns out, there are a few issues.</p>

<h2 id="issues">Issues</h2>

<p>There are, in fact, a bunch of ways the above can fail.</p>

<h3 id="symlinks">Symlinks</h3>

<p>This isn’t an issue for the vast majority of crates, but I spent a fair bit of
time tracking this down.</p>

<p>Basically, if you have a symlink in your repo, and you clone it on Windows
<em>without</em> symlink support enabled<sup id="fnref:windows-symlinks" role="doc-noteref"><a href="#fn:windows-symlinks" class="footnote" rel="footnote">6</a></sup>, Git will helpfully turn
each symlink into a regular file. That file’s contents will be the target of
the symlink.</p>

<p>Now, if it’s a source file, you’re probably going to notice right away (since
your builds will fail), but for things like README files and licences, you
probably won’t. And, to make matters worse, they’re pretty much the most
commonly symlinked files, particularly in multi-crate workspaces.</p>

<p>For now, I’ve elected to give crate versions that otherwise match their repos a
special yellow sort-of-OK state. Still, it’s not lost on me that this might be
a potential vector of attack in the future. Realistically, the fix here is
probably to encourage crate developers to publish their crates out of
(non-Windows) CI. (More on that in a bit.)</p>

<h3 id="stuff-straight-up-not-existing">Stuff straight up not existing</h3>

<p>Just because you declare a <code class="language-plaintext highlighter-rouge">repository</code> in a Cargo manifest, doesn’t mean that
the repo still exists. (Or, indeed, ever existed.) And that’s before even
getting to submodules. Or revisions — just because Cargo saw a revision in a
local repository doesn’t mean it ever got pushed to a public code host. It just
has to be committed locally to avoid needing to use <code class="language-plaintext highlighter-rouge">--allow-dirty</code>.</p>

<p>Speaking of…</p>

<h3 id="dirty-crates">Dirty crates</h3>

<p>If you publish with something like <code class="language-plaintext highlighter-rouge">cargo publish --allow-dirty</code>, then that
lovely <a href="https://doc.rust-lang.org/cargo/commands/cargo-package.html#cargo_vcs_infojson-format">VCS info file</a> doesn’t get included in the crate file.</p>

<p>This is probably the right choice on the Cargo side, but I do feel that we’ll
lose the ability to otherwise verify repos in some cases where someone just has
an extra set of test cases in their working directory and used <code class="language-plaintext highlighter-rouge">--allow-dirty</code>
to get around the requirement that the Git tree is clean.</p>

<h3 id="build-failures">Build failures</h3>

<p>This isn’t really an issue in the top crates, but in testing the deeper reaches
of the crate ecosystem, some crate versions just straight up don’t build any
more — presumably because they relied on submodules that no longer populate
correctly, or because they relied on nightly features that no longer exist.</p>

<p>It’s hard to generate a crate file to test when the crate doesn’t build.</p>

<h3 id="workspaces-again">Workspaces, again</h3>

<p>I mentioned workspaces earlier, but another problematic case is users of
workspaces on older versions of Cargo. Before Cargo 1.57 (December 2021), crate
files built from subdirectories of repositories didn’t have that fact annotated
in their <a href="https://doc.rust-lang.org/cargo/commands/cargo-package.html#cargo_vcs_infojson-format">VCS info</a>.</p>

<p>In theory, it would be possible to search the repository to try to discover
where a member crate is built from. Alas, that exercise has been left for
another day, so those crates will currently show up as not being found in the
repository.</p>

<h3 id="very-old-things">Very old things</h3>

<p>And, finally, Cargo added support for generating the <a href="https://doc.rust-lang.org/cargo/commands/cargo-package.html#cargo_vcs_infojson-format">VCS info file</a>
in version 1.30, which was released in October 2018. Very old crate versions
simply won’t have this file, and hence can never be verified.</p>

<h2 id="results">Results</h2>

<p>Given the above, here’s what I found. Of the most recent versions of the top 999 crates:</p>

<ul>
  <li>826 crates match their upstream repositories at the revision they were built at.</li>
  <li>74 crates have revisions that cannot be found in their repositories, whether
due to later squash merges, rebases or revisions simply not being pushed.</li>
  <li>73 crates do not have <a href="https://doc.rust-lang.org/cargo/commands/cargo-package.html#cargo_vcs_infojson-format">VCS info</a>, either because they were built
with old Cargo versions, built with <code class="language-plaintext highlighter-rouge">--allow-dirty</code>, or not built from a repo
clone at all.<sup id="fnref:heuristics" role="doc-noteref"><a href="#fn:heuristics" class="footnote" rel="footnote">7</a></sup></li>
  <li>7 crates do not declare a <code class="language-plaintext highlighter-rouge">repository</code> in their Cargo manifest.</li>
  <li>7 crates <em>would</em> match their upstream repository but for one or more symlinks
being incorrectly handled.</li>
  <li>3 crates declare repositories that do not exist.</li>
  <li>3 crates have submodules that do not exist.</li>
  <li>3 crates cannot be found within their repositories.</li>
  <li>3 crates cannot be built due to <code class="language-plaintext highlighter-rouge">cargo package</code> errors.</li>
</ul>

<p>Going back further, those 999 crates have published 33,085 versions in total.
The major trends looking back further into history are that fewer crates have
repository metadata, and there are more errors related to not being able to
find a crate in a workspace and more missing repositories. Both of these feel
intuitively correct: the further back into history we go with these crates, the
more likely it is that they have were packaged with older versions of Cargo,
and the more likely it is that their repository history has shifted in ways
that we can’t unpick in 2024.</p>

<p>Only 8 crate versions straight up don’t match their upstream repositories. None
of these were malicious: seven were updates from vendored upstreams (such as
wrapped C libraries) that weren’t represented in their repository at the point
the crate version was published, and the last was the inadvertent inclusion of
<code class="language-plaintext highlighter-rouge">.github</code> files that hadn’t yet been pushed to the GitHub repository.</p>

<h2 id="future-work">Future work</h2>

<p>An obvious next step here is to extend this to the entire crates.io corpus. I
intend to perform this analysis in the next couple of weeks.</p>

<p>Rather than further extending the static site that I’ve published today, I
would also like to integrating this into crates.io for every crate, and running
this check each time a new crate version is published. Doing this will, of
course, require the consensus of the crates.io team, and work to design the UI
and UX for this in a way that is immediately useful to the casual Rust user.</p>

<p>I think it’s also critical that we start providing off-the-shelf GitHub actions
(and equivalents for other popular code hosts) that make it easier to publish
directly out of repositories on the host, rather than crates being published
from developer desktops. This is also a critical step on the road towards
supporting a full trusted publishing pipeline.</p>

<p>And, of course, there’s plenty that can be done to improve the analysis.</p>

<p>The handling of broken symlinks is a late-added heuristic that I’m still not
100% sure I like.</p>

<p>Discovering crates within workspaces published from old Cargo versions would
improve the accuracy of the checks.</p>

<p>Finally, getting a better idea of what <em>types</em> of changes exist is also
important: analysing the top 999 crates didn’t really result in enough crate
versions that didn’t match their upstreams to perform any real analysis, but a
larger dataset will likely give us a lot to dig into. This is important because
it will allow us to develop tailored best practice advice for different real
world scenarios.</p>

<h2 id="in-summary">In summary</h2>

<p>If there’s a backdoor attack lurking in the crates ecosystem, then it’s lurking
pretty deep at present. The popular crates that we all rely on day to day
generally appear to be what they say they are.</p>

<p>Of course, just because a package is the same as its upstream repository, that
doesn’t mean that the repository itself is safe. This just mitigates one
potential area of supply chain interest. (Alas, there are no silver bullets.)</p>

<p>I’m looking forward to developing this work further in conjunction with the
Rust project, Rust Foundation, crates.io team, and others, but also ensuring
that we broaden the analysis and scanning work that we do as we go.</p>

<p>Lots to be done!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:xz" role="doc-endnote">

      <p>I doubt this needs any introduction, but <a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor">Wikipedia has a decent
summary</a>. <a href="#fnref:xz" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:xz:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:999" role="doc-endnote">

      <p>Why 999? There are just under 150k crates on crates.io, but as with
most package registries, their popularity follows a pretty standard log
curve where the most popular crates are <em>really</em> popular, and there’s a
very long tail of crates that are rarely or never used by other projects.</p>

      <p>As to why it’s 999 and not 1000, you can decide if it’s because 999 is a
more memorable number, because it allowed for the joke in the title, or
because I had an off-by-one error in the shell pipeline that generated the
candidate list.</p>

      <p>Finally, “top” is being defined by download count in the last 90 days for
now. <a href="#fnref:999" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:999:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:thanks" role="doc-endnote">

      <p>My thanks to the <a href="https://foundation.rust-lang.org/">Rust Foundation</a> for supporting this
work in conjunction with the <a href="https://openssf.org/">OpenSSF</a>’s <a href="https://alpha-omega.dev/">Alpha Omega
project</a>. My thanks also to the <a href="https://www.rust-lang.org/governance/teams/dev-tools#team-crates-io">crates.io
team</a>, who continue to support and encourage security-related
experimentation. <a href="#fnref:thanks" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:frontend" role="doc-endnote">
      <p>Two notes on this:</p>

      <ol>
        <li>
          <p>I am not a frontend developer. I used to be many years ago, but anyone
who’s worked with me in the last decade will agree that I shouldn’t be
allowed near any frontend technology. So go easy!</p>
        </li>
        <li>
          <p>The overall bundle and data size is kind of large — it all weighs in at
just under 1 MB to render the index, and then there’s another ~180 MB of
fine-grained data that gets loaded on a per-crate basis. Don’t load this
on a heavily metered mobile connection.</p>
        </li>
      </ol>
      <p><a href="#fnref:frontend" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:crate-package" role="doc-endnote">

      <p><code class="language-plaintext highlighter-rouge">cargo package --list</code> isn’t usable because it’s currently ambiguous where
files come from in workspace scenarios. <a href="https://github.com/rust-lang/cargo/issues/11666">cargo issue
#11666</a> would add a JSON
output mode that would make <em>actually</em> packaging unnecessary, so I’m crossing
my fingers that that can be landed at some point.</p>

      <p>(And no, I’m not ruling out going and implementing it myself at some point.) <a href="#fnref:crate-package" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:windows-symlinks" role="doc-endnote">

      <p>In recent versions of Windows, you can enable developer mode to get symlink
support. And probably other useful stuff. <a href="#fnref:windows-symlinks" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:heuristics" role="doc-endnote">

      <p>It would probably be possible to figure out a heuristic to split these, but
I don’t think the answer is particularly interesting, honestly: the only
one of these cases that is really actionable is the <code class="language-plaintext highlighter-rouge">--allow-dirty</code> case,
and we can address that through better tooling to publish crates from CI. <a href="#fnref:heuristics" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[tl;dr]]></summary></entry><entry><title type="html">A namespace system, if you can keep it</title><link href="https://lawngno.me/blog/2024/03/11/namespaces.html" rel="alternate" type="text/html" title="A namespace system, if you can keep it" /><published>2024-03-11T23:43:32+00:00</published><updated>2024-03-11T23:43:32+00:00</updated><id>https://lawngno.me/blog/2024/03/11/namespaces</id><content type="html" xml:base="https://lawngno.me/blog/2024/03/11/namespaces.html"><![CDATA[<p><a href="https://rust-lang.github.io/rfcs/3243-packages-as-optional-namespaces.html">Rust RFC #3243</a>, which implements optional namespace support for Rust, has
been approved<sup id="fnref:fcp" role="doc-noteref"><a href="#fn:fcp" class="footnote" rel="footnote">1</a></sup> and merged. It is now official Rust project policy, and I’m
looking forward to using it.</p>

<p>From the perspective of <em>actually</em> getting to use it, though, it’s maybe more
the end of the beginning.</p>

<p>Implementing this on crates.io<sup id="fnref:crates-io" role="doc-noteref"><a href="#fn:crates-io" class="footnote" rel="footnote">2</a></sup> will require code changes, so isn’t
going to be immediate. There’s nothing there that strikes me as <em>particularly</em>
problematic from an implementation perspective: crate search will presumably
need some tweaking, we’ll have to figure out if this changes how we rate limit
crate publication, and the inherited ownership semantics in the RFC — while
likely sufficient for the initial, single level implementations — will probably
eventually need more refinement to handle less common scenarios.<sup id="fnref:ownership" role="doc-noteref"><a href="#fn:ownership" class="footnote" rel="footnote">3</a></sup></p>

<h3 id="policy">Policy</h3>

<p>crates.io policy, however, is an area that will need more work.</p>

<p>Owners of existing crates will likely be largely unaffected here. To use
<a href="https://crates.io/crates/serde"><code class="language-plaintext highlighter-rouge">serde</code></a> as an example, there are already <a href="https://crates.io/teams/github:serde-rs:publish">a handful of existing
<code class="language-plaintext highlighter-rouge">serde_*</code> crates</a>. This RFC will provide the option to migrate to
<code class="language-plaintext highlighter-rouge">serde::*</code>, and will (at the very least) prevent others from publishing crates
within that namespace.</p>

<p>I fear, though, that there will be a landrush of users who want to grab good
namespaces for their own use.</p>

<p>In some cases, this will simply be spam, and can be dealt with accordingly <a href="https://crates.io/policies">per
crates.io policy</a>.</p>

<p>Some may be attempts at typosquatting, although top level crate owners
controlling their namespaces means that there aren’t any new opportunities here.
(For example, were someone to want to namespace a hypothetical <code class="language-plaintext highlighter-rouge">serde::json</code>
crate, they could try <code class="language-plaintext highlighter-rouge">serd::json</code><sup id="fnref:serd" role="doc-noteref"><a href="#fn:serd" class="footnote" rel="footnote">4</a></sup>, but <code class="language-plaintext highlighter-rouge">serde::json2</code> wouldn’t be an
option.) Also, we’re pretty vigilant on this now, and I won’t be comfortable
with crates.io accepting namespaced crates until we know the existing
typosquatting checks<sup id="fnref:checks" role="doc-noteref"><a href="#fn:checks" class="footnote" rel="footnote">5</a></sup> are extended to handle them appropriately.</p>

<p>Finally, I suspect the crates.io team will have to field more requests based on
trademark and other intellectual property rights. These are going to be
time-consuming and potentially expensive<sup id="fnref:policy" role="doc-noteref"><a href="#fn:policy" class="footnote" rel="footnote">6</a></sup> to deal with, not to mention
the issues that will be caused if a commonly used crate has to be removed or
renamed, since much of the Rust ecosystem assumes an immutable crates.io index.</p>

<h3 id="verification">Verification</h3>

<p>I also think we’re going to have to figure out a story around namespace
verification over time. Put simply, this would give us a way of saying that the
<code class="language-plaintext highlighter-rouge">northwind-traders</code> namespace belongs to the actual Northwind Traders company,
and indicating that somehow in the crates.io UI.</p>

<p>This already exists in Java’s Maven Central<sup id="fnref:java" role="doc-noteref"><a href="#fn:java" class="footnote" rel="footnote">7</a></sup> and .NET’s NuGet<sup id="fnref:dotnet" role="doc-noteref"><a href="#fn:dotnet" class="footnote" rel="footnote">8</a></sup>,
and the research<sup id="fnref:research" role="doc-noteref"><a href="#fn:research" class="footnote" rel="footnote">9</a></sup> I did for my <a href="https://www.youtube.com/watch?v=1IINSW7IG-k"><em>What’s in a Namespace</em></a> talk
last year at PackagingCon suggests that good top level namespace verification
policies discourage typosquatting attacks, presumably because users have been
trained to expect some sort of signal in their registry that the package is
authentic<sup id="fnref:signing" role="doc-noteref"><a href="#fn:signing" class="footnote" rel="footnote">10</a></sup>.</p>

<p>To me, it makes sense that we should provide a way of saying that a namespace
prefix is reserved (or owned, or verified, or whatever word you’d like to use)
by the entity that you’d expect. I think this need would increase significantly
if multi-level namespaces<sup id="fnref:multi" role="doc-noteref"><a href="#fn:multi" class="footnote" rel="footnote">11</a></sup> are ultimately provided, but I think it will
still exist even today.</p>

<p>Maybe there’s a world in which signing completely solves this (BigCorp signs a
key for ProductTeam, who uses that key to sign their crates, and then crates.io
has some way of displaying that chain of trust), but that feels a long way off.</p>

<h3 id="doom-and-gloom">Doom and gloom?</h3>

<p>All that said, though, I really don’t think this is a bad thing. (No matter what
the last few paragraphs suggest.)</p>

<p>Namespacing is a <em>new</em> thing for Rust, and it’s going to bring new challenges
(for other teams as well, I’m sure), but we’re not treading completely new
ground here, and none of these issues are critical, day zero things. They’re
just going to require careful thinking and planning.</p>

<p>I’m looking forward to us figuring them out together.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:fcp" role="doc-endnote">
      <p>The observant reader might note that I didn’t approve the RFC as part of
the crates.io FCP review. I’m generally supportive of the aims of the RFC,
but wasn’t 100% sold on some of the detail — not enough to derail a process
that had taken literal years of effort from multiple very smart people to
get to that point, but enough that I didn’t feel totally comfortable
approving it.</p>

      <p>I may eventually write up what would have been my objections, but they’re
irrelevant to this blog post. <a href="#fnref:fcp" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:crates-io" role="doc-endnote">
      <p>Author disclosure statement: I’m a member of the crates.io team,
although probably not <em>the</em> member of the crates.io team that will implement
most of this. I’m also employed by the Rust Foundation to work on ecosystem
security, which (unsurprisingly) overlaps significantly with crates.io. <a href="#fnref:crates-io" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ownership" role="doc-endnote">
      <p>One initial thought I have here is that I’m not sure conflating
the permission to <em>curate</em> a namespace with the ability to <em>publish</em> to a
crate within a namespace is the right thing to do in all cases, but this is
something that can be iterated on later. <a href="#fnref:ownership" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:serd" role="doc-endnote">
      <p>I mean, they actually couldn’t, because I picked that example
carefully, but you get the general idea. <a href="#fnref:serd" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:checks" role="doc-endnote">
      <p>Inside Rust blog post on this coming to a blog near you Real Soon
Now™. (I should probably be finishing that now instead of writing this.) <a href="#fnref:checks" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:policy" role="doc-endnote">
      <p>The crates.io team <a href="https://crates.io/policies">has policies that nominally address requests for
crates based on IP</a>, but ultimately these may have to go through the
Rust Foundation’s legal counsel, which isn’t free, and the Foundation’s
budget isn’t limitless. <a href="#fnref:policy" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:java" role="doc-endnote">
      <p>Maven Central’s <a href="https://central.sonatype.org/faq/verify-ownership/#answer">verification system</a> is based on
domain names, which isn’t something we can adopt wholesale for multiple
reasons — the lack of multi-level namespaces in the RFC that was accepted,
along with there being existing crates that are also TLDs — but it might
make sense as part of a larger verification process. (Maven Central also
<em>requires</em> verification for new namespaces, which is unlikely to be
something we’d ever want in Rust.) <a href="#fnref:java" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:dotnet" role="doc-endnote">
      <p>The <a href="https://learn.microsoft.com/en-us/nuget/nuget-org/id-prefix-reservation">exact criteria used by NuGet</a> are opaque,
but that’s obviously intentional. Unfortunately, we’d probably have to do
something similar for crates.io, since we can’t just use DNS as a source of
identity proof.<sup id="fnref:java:1" role="doc-noteref"><a href="#fn:java" class="footnote" rel="footnote">7</a></sup> <a href="#fnref:dotnet" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:research" role="doc-endnote">
      <p>I don’t exactly have p-values for this stuff, because it was
research for a 25 minute conference talk and we don’t exactly have a large
sample size for major language ecosystems. I’m comfortable saying this, but
I’m also happy to admit that this is at about a “trust me, bro” level of
reproducibility. <a href="#fnref:research" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:signing" role="doc-endnote">
      <p>This is obviously separate from giving crate authors the ability to
sign their crates, which is another way of attesting provenance. While
<a href="https://foundation.rust-lang.org/news/2023-12-21-improving-supply-chain-security/#crate-author-signing">we’re not planning on addressing this at the Rust Foundation until after
infrastructure-level concerns are dealt with</a>, there are others
already thinking about this, both <a href="https://rust-lang.zulipchat.com/#narrow/stream/417663-tbd-signing">on Zulip</a> and elsewhere. <a href="#fnref:signing" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:multi" role="doc-endnote">
      <p>My assumption here is that the single level namespaces approved in
this RFC will tend to be used at a business unit level in larger companies:
since <code class="language-plaintext highlighter-rouge">company::product::client</code> isn’t an option, I’d expect we’ll see
<code class="language-plaintext highlighter-rouge">product::client</code>, for the most part. Should namespaces be opened up to
multiple levels later, this may no longer hold. <a href="#fnref:multi" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[Rust RFC #3243, which implements optional namespace support for Rust, has been approved1 and merged. It is now official Rust project policy, and I’m looking forward to using it. The observant reader might note that I didn’t approve the RFC as part of &#8617;]]></summary></entry><entry><title type="html">Musings on Rust RFC #3537</title><link href="https://lawngno.me/blog/2024/02/22/rust-version.html" rel="alternate" type="text/html" title="Musings on Rust RFC #3537" /><published>2024-02-22T23:43:32+00:00</published><updated>2024-02-22T23:43:32+00:00</updated><id>https://lawngno.me/blog/2024/02/22/rust-version</id><content type="html" xml:base="https://lawngno.me/blog/2024/02/22/rust-version.html"><![CDATA[<p>I wrote about 600 words on <a href="https://github.com/rust-lang/rfcs/pull/3537">Rust RFC #3537</a>, but before I could do that, I wrote almost 2000 words working through my vague worries in different scenarios. This blog is a lightly edited version of my initial braindump.</p>

<hr />

<p>I am, in principle, supportive of adding a new resolver version that is Rust version aware, although I’m unsure if <code class="language-plaintext highlighter-rouge">resolver.precedence = "rust-version"</code> is the right default. (I won’t rehash this in any detail; others have covered the same ground. I basically agree with @mitsuhiko here that shipping just the new resolver as an opt-in — maybe even requiring an explicit <code class="language-plaintext highlighter-rouge">resolver.precedence</code> initially — would be a good way of gathering more data.)</p>

<p>I also strongly support the changes to <code class="language-plaintext highlighter-rouge">cargo update</code> and friends that will result in more obvious notices to users when they have old versions of dependencies, and the idea of making a MSRV violation an overridable lint.</p>

<p>From where I sit, I see three distinct problems with the current RFC, all around <code class="language-plaintext highlighter-rouge">rust-version</code>.</p>

<h3 id="rust-version-becomes-semantically-overloaded"><code class="language-plaintext highlighter-rouge">rust-version</code> becomes semantically overloaded</h3>

<p><code class="language-plaintext highlighter-rouge">rust-version</code> is currently <a href="https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field">clearly defined as the minimum version supported by a crate</a>.</p>

<p>This clarity is removed in <a href="https://github.com/epage/rfcs/blob/dce5d7c7a0dba7c644ba0bcc115fca3838ecd01d/text/3537-msrv-resolver.md#the-rust-version-field">the proposed documentation update</a>, by weakening the definition of the field to “what version of the Rust language and compiler your package can be compiled with”.</p>

<p>It feels like <code class="language-plaintext highlighter-rouge">rust-version</code> now has three different meanings, depending on which part of the RFC you’re reading:</p>

<ol>
  <li>The existing meaning: the minimum supported Rust version.</li>
  <li>A version that just happens to work with the given package.</li>
  <li>With <code class="language-plaintext highlighter-rouge">auto</code>, possibly even a statement of policy (latest only)?</li>
</ol>

<p>These are different concerns, and shouldn’t be overloaded into a single field.</p>

<p>The last point could be dealt with by introducing (in a later RFC, presumably) the hinted at <a href="https://github.com/epage/rfcs/blob/dce5d7c7a0dba7c644ba0bcc115fca3838ecd01d/text/3537-msrv-resolver.md#ensuring-the-registry-index-has-rust-version-without-affecting-quality"><code class="language-plaintext highlighter-rouge">rust-version-policy</code> field</a>, which would be a good piece of policy documentation that could be surfaced on crates.io alongside the minimum version, since it tells the reader both what the <em>current</em> MSRV is <em>and</em> what to expect on the next release of a crate.</p>

<p>For the other meanings, I would argue that the minimum version is still the definition we should use for <code class="language-plaintext highlighter-rouge">rust-version</code>, and that this RFC shouldn’t change that.</p>

<h3 id="rust-version--auto-isnt-an-expression-of-a-minimum-version-policy"><code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> isn’t an expression of a minimum version policy</h3>

<p>OK, so if you accept the previous section’s contention that <code class="language-plaintext highlighter-rouge">rust-version</code> should continue to only express the minimum supported Rust version<sup id="fnref:acceptance" role="doc-noteref"><a href="#fn:acceptance" class="footnote" rel="footnote">1</a></sup>, then something else falls out: <code class="language-plaintext highlighter-rouge">auto</code> isn’t a valid expression of a minimum version.</p>

<p>The persona that develops a quick and dirty crate, starting with <code class="language-plaintext highlighter-rouge">cargo new</code>, and throws it over the wall at crates.io probably isn’t thinking about minimum supported Rust versions. If <code class="language-plaintext highlighter-rouge">cargo new</code> adds <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code><sup id="fnref:default" role="doc-noteref"><a href="#fn:default" class="footnote" rel="footnote">2</a></sup>, some users may take that as a hint to figure out their MSRV, but I suspect many would either miss it, or they may see <code class="language-plaintext highlighter-rouge">auto</code> as something that’s probably super safe and not worth investigating further.<sup id="fnref:auto" role="doc-noteref"><a href="#fn:auto" class="footnote" rel="footnote">3</a></sup></p>

<p>I <em>would</em> like people to think about their MSRV. I’d support changing the template used by <code class="language-plaintext highlighter-rouge">cargo new</code> to include a commented out <code class="language-plaintext highlighter-rouge">rust-version</code> field along with a link to best practice documentation.<sup id="fnref:best-practice" role="doc-noteref"><a href="#fn:best-practice" class="footnote" rel="footnote">4</a></sup></p>

<p>But the problem I have with <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> is how its behaviour shifts based on its environment, and environments are implicit, not explicit. With <code class="language-plaintext highlighter-rouge">auto</code>, and assuming I run <code class="language-plaintext highlighter-rouge">rustup update</code> reasonably often, if I publish <code class="language-plaintext highlighter-rouge">my-shiny-new-crate 0.1.0</code> today, I’m saying that the MSRV is 1.76.0[^rustup]. If I fix a bug in six months and publish <code class="language-plaintext highlighter-rouge">my-shiny-new-crate 0.1.1</code>, now my MSRV is going to be something like 1.80.0<sup id="fnref:maths" role="doc-noteref"><a href="#fn:maths" class="footnote" rel="footnote">5</a></sup>, even if I changed one inconsequential line.</p>

<p>If <code class="language-plaintext highlighter-rouge">auto</code> doesn’t exist, one of two things happens:</p>

<ol>
  <li>I may have taken the hint from the template, or read some documentation, and declared <code class="language-plaintext highlighter-rouge">rust-version = "1.76.0"</code> when I published the first version, This is probably still true, and it’s right in front of me in the manifest to change if it’s not. (And, if I’m wrong, it’s a really straightforward bug report from a user down the line, and an obvious fix for me as the maintainer: I can either make it compatible, or bump <code class="language-plaintext highlighter-rouge">rust-version</code> and republish.)</li>
  <li>If I didn’t specify <code class="language-plaintext highlighter-rouge">rust-version</code> at all, then that’s <em>definitely</em> still true. Caveat emptor for my users. And that’s OK: as a crate consumer, I’d much rather know that the maintainer hasn’t thought about MSRV at all (through the omission of <code class="language-plaintext highlighter-rouge">rust-version</code>) than get a fake value that doesn’t really express their opinion.</li>
</ol>

<p>In practice, I also think that the motivation for <code class="language-plaintext highlighter-rouge">auto</code> (which, as I perceive it, is basically a lack of <code class="language-plaintext highlighter-rouge">rust-version</code> usage thus far) will eventually correct itself.</p>

<p>Going back to PHP, <code class="language-plaintext highlighter-rouge">composer init</code><sup id="fnref:composer-init" role="doc-noteref"><a href="#fn:composer-init" class="footnote" rel="footnote">6</a></sup> doesn’t include a PHP requirement at all in its default output, and there’s no equivalent to <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> in Composer. Yet ~78%<sup id="fnref:source" role="doc-noteref"><a href="#fn:source" class="footnote" rel="footnote">7</a></sup> of PHP packages on Packagist do include minimum PHP requirements, expressed as a semver constraint, likely because it’s <a href="https://getcomposer.org/doc/01-basic-usage.md#platform-packages">prominently documented on the first real page of Composer documentation</a>. The situation is different in some ways — Composer always (to my recollection) supported PHP requirements, so users learned to write Composer manifests with PHP requirements from early on — but I think over time crate authors would do the same thing. We just need to bring the documentation to the users, provide tooling to help, and be patient.</p>

<h3 id="rust-version--auto-can-cause-security-problems-for-crate-consumers"><code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> can cause security problems for crate consumers</h3>

<p>To shorten this section, let’s just assume I agree with basically everything @jonhoo said.</p>

<p>But, at the risk of repeating others, the specific scenario — related to the above — that worries me is this:</p>

<ol>
  <li>Maintainer Alex publishes <code class="language-plaintext highlighter-rouge">buffer-thing 1.0.0</code> with <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code>. They use Rust 1.76, so that’s what the crate MSRV is set to.</li>
  <li>Consumer Bobbie adds <code class="language-plaintext highlighter-rouge">buffer-thing = "1.0.0"</code> as a dependency in their project that uses Rust 1.77. No problem.</li>
  <li>Alex fixes a security bug and publishes <code class="language-plaintext highlighter-rouge">buffer-thing = "1.0.1"</code>, but publishes from an environment (whether their workstation or CI) that uses Rust 1.78. <code class="language-plaintext highlighter-rouge">cargo publish</code> sets the MSRV to 1.78.</li>
  <li>Bobbie hasn’t updated their Rust version for whatever reason, rebuilds their project on Rust 1.77, and still gets the vulnerable version even if they run <code class="language-plaintext highlighter-rouge">cargo update</code>.</li>
</ol>

<p>Yes, Bobbie will get a warning here from <code class="language-plaintext highlighter-rouge">cargo update</code>, and that’s good! But now they’re in a difficult position:</p>

<ol>
  <li>Bobbie could use <code class="language-plaintext highlighter-rouge">--ignore-rust-version</code>, but they don’t have any way of knowing if <code class="language-plaintext highlighter-rouge">buffer-thing 1.0.1</code> actually works on Rust 1.77, and they have what seems like an authoritative statement from Alex on crates.io that it actually requires Rust 1.78.<sup id="fnref:verisimilitude" role="doc-noteref"><a href="#fn:verisimilitude" class="footnote" rel="footnote">8</a></sup> They’re probably not going to do this.</li>
  <li>Bobbie could upgrade their local toolchain, but they may not be able to — it may be a certified fork like Ferrocene, or something else might not work with a newer version, or they just might need to conform with Big Company policies that they don’t necessarily even agree with but have no control over.<sup id="fnref:bigco" role="doc-noteref"><a href="#fn:bigco" class="footnote" rel="footnote">9</a></sup></li>
  <li>If there are more changes than just the security fix, Bobbie could try to fork the last version that supported Rust 1.77, cherry pick the security fix in, and vendor that. That is both time-consuming and error-prone.</li>
</ol>

<p>Again, all of this goes away if <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> doesn’t exist. Alex either published <code class="language-plaintext highlighter-rouge">buffer-thing 1.0.0</code> with an explicit <code class="language-plaintext highlighter-rouge">rust-version</code>, which probably only gets incremented when it actually needs to be or per a clear policy that Bobbie hopefully already knew about, or they published it without a <code class="language-plaintext highlighter-rouge">rust-version</code> at all, in which case Bobbie gets to try it and can then decide to either report or fix a build failure. In some respects, this is essentially the same decision tree as above, but without the uncertainty of having a piece of seemingly authoritative information that purports to state Alex’s intentions around the minimum version. Whatever they choose, their path is clearer.</p>

<hr />

<p>OK, that was a lot of words. I’m sorry. Really.</p>

<p><strong>To put this into something actionable</strong>: my suggestion is that <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> should be dropped from the RFC. I also agree with @mitsuhiko that it probably makes sense to slow roll these changes more generally: it makes sense to me to ship the v3 resolver as soon as it’s ready, but I think it’s premature to commit to making it the default in the next edition without some real world experience reports, particularly around whether the default <code class="language-plaintext highlighter-rouge">resolver.precedence</code> is appropriate. (Could we ship it and <em>require</em> <code class="language-plaintext highlighter-rouge">resolver.precedence</code> initially, and ask for feedback?)</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:acceptance" role="doc-endnote">
      <p>I mean, I don’t actually expect many (most?) people reading this to agree with everything I just said. I’m just trying to follow it through to what I see as the logical conclusions of that train of thought. <a href="#fnref:acceptance" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:default" role="doc-endnote">
      <p>It’s unclear to me whether <code class="language-plaintext highlighter-rouge">cargo new</code> setting <code class="language-plaintext highlighter-rouge">rust-version = "auto"</code> is actually being proposed or not. The <a href="https://github.com/epage/rfcs/blob/dce5d7c7a0dba7c644ba0bcc115fca3838ecd01d/text/3537-msrv-resolver.md#guide-level-explanation">guide level explanation</a> says that it is, but some recent comments suggest that it’s not. If it’s not… well, I’d still argue against it in a future RFC, but you can probably ignore most of this section. <a href="#fnref:default" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:auto" role="doc-endnote">
      <p>I don’t really want to bikeshed the name, since I disagree with the whole concept, but <code class="language-plaintext highlighter-rouge">auto</code> specifically could imply “Cargo will figure out what the minimum version actually is”, which is even more misleading than what it actually does. If the concept did stay, I agree with <a href="https://github.com/epage/rfcs/blob/dce5d7c7a0dba7c644ba0bcc115fca3838ecd01d/text/3537-msrv-resolver.md#unresolved-questions">the unresolved question</a> around naming, and would probably vote for <code class="language-plaintext highlighter-rouge">toolchain</code> or <code class="language-plaintext highlighter-rouge">current</code> of the listed alternatives. <a href="#fnref:auto" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:best-practice" role="doc-endnote">
      <p>Some of which already exists, in fact, in the <a href="https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field"><code class="language-plaintext highlighter-rouge">rust-version</code> documentation</a>. <a href="#fnref:best-practice" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:maths" role="doc-endnote">
      <p>The specific version number is a rough guess. I didn’t actually look at a calendar, so if I’m off by one I apologise. <a href="#fnref:maths" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:composer-init" role="doc-endnote">
      <p>Roughly equivalent to <code class="language-plaintext highlighter-rouge">cargo init</code>, but with more interactive questions, like <code class="language-plaintext highlighter-rouge">poetry new</code> from the Python world. <a href="#fnref:composer-init" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:source" role="doc-endnote">
      <p>Analysis performed on the full Packagist index as of today, Feburary 22nd, 2024. <a href="#fnref:source" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:verisimilitude" role="doc-endnote">
      <p>I studied film at university, and one of the things that you spend a <em>lot</em> of time on in your documentary units is the concept of “verisimilitude”: how real or truthful something appears. The moment we have <code class="language-plaintext highlighter-rouge">cargo publish</code> start inserting version numbers that get displayed on crates.io, users are — rightly — going to assume that they’re truthful, because crates.io is the authoritative source of crate information. <a href="#fnref:verisimilitude" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:bigco" role="doc-endnote">
      <p>Ask me about my stories from New Relic. <a href="#fnref:bigco" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[I wrote about 600 words on Rust RFC #3537, but before I could do that, I wrote almost 2000 words working through my vague worries in different scenarios. This blog is a lightly edited version of my initial braindump.]]></summary></entry><entry><title type="html">Couch to 5K on the Pebble</title><link href="https://lawngno.me/blog/2020/05/27/intervals-pebble.html" rel="alternate" type="text/html" title="Couch to 5K on the Pebble" /><published>2020-05-27T04:43:05+00:00</published><updated>2020-05-27T04:43:05+00:00</updated><id>https://lawngno.me/blog/2020/05/27/intervals-pebble</id><content type="html" xml:base="https://lawngno.me/blog/2020/05/27/intervals-pebble.html"><![CDATA[<p><em>…or, how Adam decided that the C in C25K stood for… well, C.</em></p>

<p>I decided a while back to start running again. It’s been a few years since I
broke my ankle, I’m not getting any younger, and there’s some vague glimmer of
hope that we might get a bit of a cricket season in here in
Vancouver<sup id="fnref:host-city" role="doc-noteref"><a href="#fn:host-city" class="footnote" rel="footnote">1</a></sup>, so it felt like it was time.</p>

<p>Unfortunately, a combination of being old, lazy, and beer mean that I probably
can’t just go run several kilometres any more, so I’ve been doing something I’ve
always hated: intervals. Specifically, the <a href="https://www.nhs.uk/live-well/exercise/couch-to-5k-week-by-week/">Couch to 5K programme</a>. Which
means that I need to time things, and remember things like how many reps I’ve
done.</p>

<p>I am, in related news, <em>really</em> bad at remembering things like that when
exercising.</p>

<p>I also don’t really want to be holding a phone when I exercise, so I dusted off
my old Pebble Time<sup id="fnref:pebble" role="doc-noteref"><a href="#fn:pebble" class="footnote" rel="footnote">2</a></sup> and went hunting for apps. There are <a href="https://apps.rebble.io/en_US/search/watchapps/1?query=couch">two apps on
the Rebble store</a>. They’re both functional, but have a
significant flaw for me: you don’t get much of a sense of how you’re doing
overall. Since I’ve never had an endorphin from exercise ever, in order to
convince my body that idle exercise is worth it I badly need to know that there
is a finish line after which the pain will subside.<sup id="fnref:quads" role="doc-noteref"><a href="#fn:quads" class="footnote" rel="footnote">3</a></sup></p>

<p>Therefore, I decided to <em>also</em> dust off my Pebble app making skills<sup id="fnref:cricket" role="doc-noteref"><a href="#fn:cricket" class="footnote" rel="footnote">4</a></sup>
and see where that led. Specifically, it led here:</p>

<p><img src="/images/intervals-pebble/activity.png" alt="A watch app showing a running activity represented using a clock-style ring" /></p>

<p>I built <a href="https://github.com/LawnGnome/c25k-pebble">a thing!</a>. It is, naturally, open source. It has not been
submitted to the Rebble store as yet, mostly because I don’t really like Discord
or bugging people and the <a href="https://rebble.io/submit/">submission process involves both</a>.</p>

<p>I hadn’t used the Pebble SDK since the version 2 days. By the time Pebble
folded, they were up to version 4.5, and there was a pre-release 4.6 out
there.<sup id="fnref:arch" role="doc-noteref"><a href="#fn:arch" class="footnote" rel="footnote">5</a></sup> This was a huge improvement on the early days of Pebble
development: there were emulators! The build process only involved <a href="https://waf.io/book/">one weird
Python thing</a>! The API had lost most of its rough edges and allowed you to
pass userdata to <em>almost</em> all the callbacks!<sup id="fnref:timers" role="doc-noteref"><a href="#fn:timers" class="footnote" rel="footnote">6</a></sup></p>

<p>And, as it turns out, writing basic graphics code is still fun. The Pebble SDK
provides a bunch of drawing primitives for things like <a href="https://developer.rebble.io/developer.pebble.com/docs/c/Graphics/Drawing_Primitives/index.html">arcs, circles, and
rectangles</a>, and you can also just grab the framebuffer and
bitbang that if you really want to. Of course, it’s running on a Cortex-M3 or
Cortex-M4 with a few hundred kilobytes of RAM, so you can’t go <em>too</em> crazy, but
it’s nice to just have the possibility.</p>

<p>In the end, I really only hit two problems:</p>

<ol>
  <li>
    <p>I wanted to use Rust, because obviously. A couple of people have tried and
apparently succeeded with trivial apps, but I wasn’t able to join their
ranks: with a lot of swearing and excursions into corners of the Rust
toolchain I’d never been to before<sup id="fnref:rustc" role="doc-noteref"><a href="#fn:rustc" class="footnote" rel="footnote">7</a></sup>, I could get something to link and
be apparently valid, but not something that would do anything other than (a)
crash on startup, or (b) lock up the watch.<sup id="fnref:watchdog" role="doc-noteref"><a href="#fn:watchdog" class="footnote" rel="footnote">8</a></sup></p>

    <p>I wrote it off as a bad job after a couple of evenings, but it’s bugging me.
It’s a bog standard THUMB chipset running a hacked up version of FreeRTOS,
and the interesting bits of generating code for that are all in LLVM once you
disable the Rust standard library and accept that <code class="language-plaintext highlighter-rouge">unsafe</code> will be your best
friend. I’m annoyed I couldn’t get it to work, even given some of the
weirdness in the Pebble app build process.<sup id="fnref:metadata" role="doc-noteref"><a href="#fn:metadata" class="footnote" rel="footnote">9</a></sup></p>
  </li>
  <li>
    <p>I got sad about Pebble. After they failed, they were bought by Fitbit, who
were pretty generous with the grace period but eventually shut all the online
services and support down.</p>

    <p>But the Pebble’s actually still a really handy device. The battery life is
great, and it’s in a weird spot where it’s not quite a true smartwatch, but
it nailed the things I <em>actually</em> want a device to do on my wrist: my phone
can provide the smarts; I just need something that’s in a nice form factor
and can perform a small number of actions.</p>

    <p>Plus, it’s actually pretty fun to develop for. The SDK really was quite good
by the end, and it’s a full featured enough environment to not feel like
you’re constantly running into arbitrary limitations, unless you need more
than 64 kB of heap. (Nobody needs more than 64 kB, right?)</p>

    <p>In retrospect, Pebble’s biggest failing seemed to be that they didn’t quite
know what they wanted to be. Pebbles were absolutely niche devices, but there
was a period where Pebble (the company) was trying to take them mainstream —
into brick and mortar stores, and into the consciousness. And maybe that’s
what they had to do if they were going to make the economics work.</p>

    <p>But it feels like there’s a hole in the market for a modern Pebble-like
device, and it almost makes me want to look into it. (Until I remember all
the blog posts Pebble posted at the start of the process about how hard it
was to get stuff manufactured, anyway.) It wouldn’t be a huge seller, but I’d
buy one. You wouldn’t sell millions, but you’d sell thousands. Probably tens
of thousands.</p>

    <p>I wish there was room for that in the modern tech world.</p>
  </li>
</ol>

<p>So, when people ask me what I did during COVID-19, I can say — hand on heart —
that I did something productive<sup id="fnref:productivity" role="doc-noteref"><a href="#fn:productivity" class="footnote" rel="footnote">10</a></sup> and didn’t spend the <em>whole</em> time
drinking beer.</p>

<p>Except for the parts where I was drinking beer while working on this.</p>

<p>Which was the whole development process.</p>

<p>Better get back to running to offset that, I guess.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:host-city" role="doc-endnote">
      <p>Oddly, unlike the NHL, cities do not seem to be falling over themselves to host the British Columbia Mainland Cricket League in quarantine. <a href="#fnref:host-city" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:pebble" role="doc-endnote">
      <p>Pebble was a relatively short lived company that made <a href="https://en.wikipedia.org/wiki/Pebble_(watch)">low power watches</a> with e-ink displays, battery life measured in days (if not weeks), and enough Bluetooth smarts to at least show you notifications. <a href="#fnref:pebble" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:quads" role="doc-endnote">
      <p>At least until the next day, at which point I will almost fall down the stairs because my quads stopped working sometime during the night. (That only happened once, in fairness.) <a href="#fnref:quads" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cricket" role="doc-endnote">
      <p>Last seen several years ago to build a <a href="https://github.com/LawnGnome/cricket-counter-for-pebble">ball counter for use when umpiring a cricket match</a>, because it was easier than ordering a <a href="https://www.gray-nicolls.co.uk/products/umpires-counters">real one</a> to Canada. <a href="#fnref:cricket" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:arch" role="doc-endnote">
      <p>Which, oddly enough, is the actual version that’s in the Arch User Repository, even though <a href="https://aur.archlinux.org/packages/pebble-sdk">it claims to be 4.5</a>. <a href="#fnref:arch" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:timers" role="doc-endnote">
      <p>The one thing I hit that you still need to use global state for is the <a href="https://developer.rebble.io/developer.pebble.com/docs/c/Foundation/Event_Service/TickTimerService/index.html"><code class="language-plaintext highlighter-rouge">TickTimerService</code></a>, which… uh, feels kinda fundamental for a watch. I hacked around it by <a href="https://github.com/LawnGnome/c25k-pebble/blob/3be4af1a82893cfdc6879bb7aaf13a3ea13b71e0/src/c/activity.c#L183-L186">repeatedly setting 500 ms timeouts</a>, but I doubt that’s good for battery life. <a href="#fnref:timers" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:rustc" role="doc-endnote">
      <p>Hi, <a href="https://doc.rust-lang.org/cargo/commands/cargo-rustc.html"><code class="language-plaintext highlighter-rouge">cargo rustc</code></a> and your collection of fun arguments you can pass directly to <code class="language-plaintext highlighter-rouge">rustc</code> and LLVM. <a href="#fnref:rustc" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:watchdog" role="doc-endnote">
      <p>On the bright side, Pebbles have a watchdog that reboots the watch if the UI is unresponsive for a few seconds. On the darker side, if you trigger that too often, you get punted into recovery mode and have to restore. As I found out. Twice. <a href="#fnref:watchdog" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:metadata" role="doc-endnote">
      <p>For example, you build a perfectly good ELF binary for each model, and then there’s a weird action that pulls it apart, looks for symbols, and then somehow injects metadata into the ELF binary to turn it into a binary blob that is actually uploaded to the watch. I’m sure there’s a good reason for all this, but it does feel a touch overwrought. <a href="#fnref:metadata" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:productivity" role="doc-endnote">
      <p>Lots of other people have said this, but I’ll reiterate it: it’s also OK to have done nothing during COVID-19. It hasn’t been a fun time. <a href="#fnref:productivity" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[…or, how Adam decided that the C in C25K stood for… well, C.]]></summary></entry></feed>