<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet href="https://www.chiark.greenend.org.uk/~ryounger/assets/feed.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ross Younger</title>
    <link>https://www.chiark.greenend.org.uk/~ryounger/</link>
    <description>Recent content on Ross Younger</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-UK</language>
    <lastBuildDate>Tue, 14 Jul 2026 16:24:56 +1200</lastBuildDate><atom:link href="https://www.chiark.greenend.org.uk/~ryounger/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Lignum</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2026/07/lignum/</link>
      <pubDate>Tue, 14 Jul 2026 16:24:56 +1200</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2026/07/lignum/</guid>
      <description>&lt;p&gt;A source code repository visualiser with a 1,000&amp;rsquo; view.&lt;/p&gt;
&lt;p&gt;I wasn&amp;rsquo;t satisfied with the state of Git repository visualisers. I found they love to show you every last commit.
When your repo is more than trivial, this is not useful for getting the big-picture.&lt;/p&gt;
&lt;p&gt;Mine is different: it focusses on the macroscopic structure.
Which are the active branches, where are the tags, where are the divergence and merge points?&lt;/p&gt;
&lt;p&gt;In my first software job out of college &amp;ndash; back when dinosaurs roamed the earth and &lt;a href=&#34;https://en.wikipedia.org/wiki/Concurrent_Versions_System&#34;&gt;CVS&lt;/a&gt; was the height of version control &amp;ndash; we often ended up drawing what we termed railway diagrams on the whiteboard to visualise the structure. This is my attempt to automate creating these.&lt;/p&gt;
&lt;h2 id=&#34;features&#34;&gt;Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Output in GraphViz &lt;code&gt;dot&lt;/code&gt; format&lt;/li&gt;
&lt;li&gt;Revision of interest filtering, with wildcards&lt;/li&gt;
&lt;li&gt;Options to include all local branches (default), remotes, tags and root (parentless) commits&lt;/li&gt;
&lt;li&gt;Edges are annotated with their size in commits (broadly the number on the direct line from A to B, though merge commits give different results)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;This is a Rust command-line application.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cargo install --locked lignum&lt;/code&gt;, or get it from the &lt;a href=&#34;https://github.com/crazyscot/lignum&#34;&gt;source repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Run lignum in a directory that contains a git repository, or give it &lt;code&gt;--repo &amp;lt;path&amp;gt;&lt;/code&gt;.
If you have &lt;code&gt;xdot&lt;/code&gt; installed, the &lt;code&gt;-x&lt;/code&gt; option will pipe the output directly to xdot.&lt;/p&gt;
&lt;p&gt;Check the &lt;code&gt;--help&lt;/code&gt; message for more.&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/lignum.png" type="image" /></item>
    
    <item>
      <title>A web toy: Forced ranking assistant</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2025/07/a-web-toy-forced-ranking-assistant/</link>
      <pubDate>Mon, 21 Jul 2025 18:21:31 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2025/07/a-web-toy-forced-ranking-assistant/</guid>
      <description>&lt;p&gt;I wanted to arrange some items in a strongly
ranked order of preference, but found it quite hard.
So I made a little web tool that tries to help.&lt;/p&gt;
&lt;p&gt;The idea? A sort of battle royale.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consider two of the items chosen at random.
Which do you prefer? Give it one point.&lt;/li&gt;
&lt;li&gt;Repeat for every possible pairing of the items, and add up the scores.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tool: &lt;a href=&#34;https://crazyscot.github.io/forced-rank/&#34;&gt;https://crazyscot.github.io/forced-rank/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a Javascript single-page application written in VueJS.
&lt;a href=&#34;https://github.com/crazyscot/forced-rank&#34;&gt;Source code&lt;/a&gt;&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/forced-ranker-response-matrix.png" type="image" /></item>
    
    <item>
      <title>Getting value from CI</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2024/12/getting-value-from-ci/</link>
      <pubDate>Sun, 01 Dec 2024 10:53:36 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2024/12/getting-value-from-ci/</guid>
      <description>&lt;p&gt;Many of my peers within the software world know the value of Continuous Integration and don&amp;rsquo;t need convincing.
This article is for everybody else.&lt;/p&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In my first job out of college we had what you&amp;rsquo;d recognise as CI, though the term wasn&amp;rsquo;t so popular then. It was powerful, very useful, but a source of Byzantine complexity.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also worked for people who didn&amp;rsquo;t think CI was worth doing because it was too expensive to set up and maintain. This is not totally unreasonable; the real question is to figure out where the value for your project might lie.&lt;/p&gt;
&lt;p&gt;Recently, a friend wrote:&lt;/p&gt;
&lt;div class=&#34;callout quote&#34;&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
      &lt;strong&gt;&lt;/strong&gt;
      &lt;p&gt;
&lt;q&gt;I don&#39;t really know very much about CI. I would be interested in knowing
more and might even use some of the quick wins (...)
I do not want to become completely reliant upon GitHub for anything.&lt;/q&gt;
&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  
  
  &lt;style&gt;
     
    .callout {
      position: relative;  
      padding: 20px;
      margin: 20px 0;
      border: 2px solid;  
      border-radius: 8px;  
      font-family: Arial, sans-serif;
      background-color: #f9f9f9;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    }

     
    .callout::before {
      content: &#34;\f00c&#34;;  
      font-family: &#34;FontAwesome&#34;;
      font-weight: normal;  
      position: absolute;
      left: -40px;  
      top: 50%;
      transform: translateY(-50%);
      font-size: 24px;  
      color: inherit;  
    }

     
    .callout.info {
      background-color: #e0f7fa;
      border-color: #00acc1;
    }

    .callout.info::before {
      content: &#34;\f05a&#34;;  
    }

     
    .callout.warning {
      background-color: #fff3e0;
      border-color: #ff6f00;
    }

    .callout.warning::before {
      content: &#34;\f071&#34;;  
    }

     
    .callout.success {
      background-color: #e8f5e9;
      border-color: #43a047;
    }

    .callout.success::before {
      content: &#34;\f058&#34;;  
    }

     
    .callout.error {
      background-color: #ffebee;
      border-color: #e53935;
    }

    .callout.error::before {
      content: &#34;\f057&#34;;  
    }

     
    .callout.quote {
      background-color: #bbb;
      border-color: #666;
    }

    .callout.quote::before {
      content: &#34;\f27a&#34;;  
    }
&lt;/style&gt;

&lt;p&gt;So let&amp;rsquo;s start with a primer.&lt;/p&gt;
&lt;h2 id=&#34;terminology-what-is-ci&#34;&gt;Terminology: What is CI?&lt;/h2&gt;
&lt;p&gt;Unfortunately the term &amp;ldquo;CI&amp;rdquo; is sometimes misused and/or confused.&lt;/p&gt;
&lt;p&gt;The short answer is that it&amp;rsquo;s &lt;strong&gt;automation that regularly (continuously) does something useful with your codebase&lt;/strong&gt;. These actions might take place on every commit, nightly, or be activated by some external trigger.&lt;/p&gt;
&lt;p&gt;CI usually refers to a &lt;em&gt;spectrum&lt;/em&gt; of practices, each step building on the last:&lt;/p&gt;

     &lt;table class=&#34;table table-striped table-bordered&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:right&#34;&gt;Continuous&amp;hellip;&lt;/th&gt;
&lt;th&gt;Typical activities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;&lt;em&gt;Build&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Builds your code, usually to the &lt;em&gt;unit&lt;/em&gt; or &lt;em&gt;module&lt;/em&gt; level. Runs unit tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;&lt;em&gt;Integration&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Assembles modules to a &amp;ldquo;finished application&amp;rdquo;, whatever that means. Runs integration tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;&lt;em&gt;Test&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;A full suite of automated tests. May include regression, performance, deployability and data migration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;&lt;em&gt;Delivery&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;When the test suite passes, the latest version of the system is automatically released to a &lt;em&gt;staging&lt;/em&gt; environment. This might involve building packages and putting them in a download area.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right&#34;&gt;&lt;em&gt;Deployment&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;When the automated tests pass, the software automatically goes live. Hold tight!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;/table&gt;
&lt;p&gt;Exactly what these phases mean for your project, and how far you go with them, depends on your project.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What suits my embedded firmware probably won&amp;rsquo;t suit your cloud app or that other person&amp;rsquo;s desktop app.&lt;/li&gt;
&lt;li&gt;The lines between the phases are blurry. For example, it may or may not make sense to build and integrate everything in one go.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-why-ci&#34;&gt;❔ Why CI&lt;/h2&gt;
&lt;p&gt;If deployed appropriately, CI can save time, reduce costs and improve quality.
Even on a hobby project, there is often value in saving your time.&lt;/p&gt;
&lt;h3 id=&#34;1-automating-stuff-so-the-humans-dont-have-to&#34;&gt;1. Automating stuff, so the humans don&amp;rsquo;t have to&lt;/h3&gt;
&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; use your engineers to do the repetitive drudge work of creating a release across multiple platforms.
You &lt;em&gt;could&lt;/em&gt; have them run a full barrage of tests before committing a code change&amp;hellip; but &lt;em&gt;should&lt;/em&gt; you?
Engineers are expensive and generally dislike boring stuff, so the smart business move is usually to automate away the repetitive parts and have them focus where they can deliver most value.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re not sure, consider this: how much time does your team spend per release cycle on the repetitive parts? Consider your expected frequency of release cycles, that should lead you to the answer.&lt;/p&gt;
&lt;h3 id=&#34;2-automatic-analysis-and-status-reporting&#34;&gt;2. Automatic analysis and status reporting&lt;/h3&gt;
&lt;p&gt;One place I worked had a release process which relied on an engineer reading multiple megabytes of log file to see if things had been successful. Many things could go wrong and leave the final output in a plausible but half-broken state. Worse, it wasn&amp;rsquo;t as simple as running the script in &lt;em&gt;stop-on-error&lt;/em&gt; mode, because some of the steps were prone to false alarms.&lt;/p&gt;
&lt;p&gt;You may be ahead of me here, but I didn&amp;rsquo;t think much of that setup.&lt;/p&gt;
&lt;p&gt;Compilation failed? Show me the compiler output from the file that failed.&lt;/p&gt;
&lt;p&gt;A test failed? I want to see the result of that test (expected &amp;amp; observed results).&lt;/p&gt;
&lt;p&gt;Everything passed? Great, but don&amp;rsquo;t spend megabytes to convey &lt;em&gt;one single bit&lt;/em&gt; of information.&lt;/p&gt;
&lt;p&gt;At its simplest, a small project will have a single &lt;strong&gt;main&lt;/strong&gt; branch, and the operational information you need can be boiled down to a small number of states:&lt;/p&gt;

     &lt;table class=&#34;table table-striped table-bordered no-header cols-3&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/software/stop-light-h_red.svg&#34; alt=&#34;Red traffic light&#34;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/software/stop-light-h_yellow.svg&#34; alt=&#34;Yellow traffic light&#34;&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/software/stop-light-h_green.svg&#34; alt=&#34;Green traffic light&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Something is broken&lt;/td&gt;
&lt;td&gt;Non-critical warning &lt;em&gt;(not all projects use this)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Everything is working&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;/table&gt;
&lt;p&gt;In a non-remote workplace it might make sense to set up some sort of status annunciator.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some people use coloured lava lamps or similar.&lt;/li&gt;
&lt;li&gt;At one place I worked the machinery in the factory had physical traffic light (&lt;em&gt;andon&lt;/em&gt;) lamp sets. We set  one of these up, driven by a Raspberry Pi wired in to the build server.&lt;/li&gt;
&lt;li&gt;Some projects build more elaborate virtual dashboards that suit their needs. Multiple branches, multiple build configurations, whatever makes sense.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-improved-quality&#34;&gt;3. Improved quality&lt;/h3&gt;
&lt;p&gt;This one might be self-evident, but I&amp;rsquo;ll spell it out anyway.&lt;/p&gt;
&lt;p&gt;A good CI system will let you incorporate tests of many different types, with variable pass/fail criteria.
Think beyond unit and integration testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Regression (check that your bugs stay fixed)&lt;/li&gt;
&lt;li&gt;Code quality (code/test coverage analysis; static analysis; dynamic memory leak analysis; automated code style checks)&lt;/li&gt;
&lt;li&gt;Security analysis (are there any known issues in your dependencies?)&lt;/li&gt;
&lt;li&gt;License/SBOM compliance&lt;/li&gt;
&lt;li&gt;Fuzz testing (how does it handle randomised, unexpected inputs?)&lt;/li&gt;
&lt;li&gt;Performance requirements&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Early warning&amp;rdquo; performance canaries&lt;/li&gt;
&lt;li&gt;Standards compliance&lt;/li&gt;
&lt;li&gt;System data migration&lt;/li&gt;
&lt;li&gt;On-device testing (might be real, emulated or simulated hardware)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;callout info&#34;&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
      &lt;strong&gt;Performance canaries&lt;/strong&gt;
      &lt;p&gt;
Particularly where physical devices are involved, you might have a performance margin built in to your hardware spec. As the project evolves, inevitably new features will erode this margin.
When you run out things are going to go wrong, so you want to take action before you get there.
&lt;/p&gt;&lt;p&gt;
An early warning canary is some sort of metric with a threshold. Examples might include free memory, CPU/MCU consumption, or task processing time.
When the threshold is passed, that&#39;s a sign that things are getting tight and it&#39;s time to take pre-emptive action. You might plan to spend some time on algorithmic optimisations, or to kick off a new hardware spin.
&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  
  
  &lt;style&gt;
     
    .callout {
      position: relative;  
      padding: 20px;
      margin: 20px 0;
      border: 2px solid;  
      border-radius: 8px;  
      font-family: Arial, sans-serif;
      background-color: #f9f9f9;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    }

     
    .callout::before {
      content: &#34;\f00c&#34;;  
      font-family: &#34;FontAwesome&#34;;
      font-weight: normal;  
      position: absolute;
      left: -40px;  
      top: 50%;
      transform: translateY(-50%);
      font-size: 24px;  
      color: inherit;  
    }

     
    .callout.info {
      background-color: #e0f7fa;
      border-color: #00acc1;
    }

    .callout.info::before {
      content: &#34;\f05a&#34;;  
    }

     
    .callout.warning {
      background-color: #fff3e0;
      border-color: #ff6f00;
    }

    .callout.warning::before {
      content: &#34;\f071&#34;;  
    }

     
    .callout.success {
      background-color: #e8f5e9;
      border-color: #43a047;
    }

    .callout.success::before {
      content: &#34;\f058&#34;;  
    }

     
    .callout.error {
      background-color: #ffebee;
      border-color: #e53935;
    }

    .callout.error::before {
      content: &#34;\f057&#34;;  
    }

     
    .callout.quote {
      background-color: #bbb;
      border-color: #666;
    }

    .callout.quote::before {
      content: &#34;\f27a&#34;;  
    }
&lt;/style&gt;

&lt;p&gt;If you can automate a really robust set of tests, you can have a lot of confidence in the state of your code at any given time. This gives incredible agility: &lt;strong&gt;you can release at any time, if the tests pass&lt;/strong&gt;. This is the key to moving quickly, and is how a number of tech companies operate.&lt;/p&gt;
&lt;p&gt;For a success story involving physical devices, check out &lt;a href=&#34;https://itrevolution.com/articles/the-amazing-devops-transformation-of-the-hp-laserjet-firmware-team-gary-gruver/&#34;&gt;the HP LaserJet team&amp;rsquo;s DevOps transformation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;4-reduced-time-to-resolve-issues&#34;&gt;4. Reduced time to resolve issues&lt;/h3&gt;
&lt;p&gt;If there&amp;rsquo;s one thing I&amp;rsquo;ve learned in the software business, it&amp;rsquo;s that it&amp;rsquo;s cheaper to find bugs closer to development - &lt;em&gt;by orders of magnitude&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In other words, &lt;em&gt;reduce the feedback cycle to reduce your costs&lt;/em&gt;.
This is where automated tests and checks have great value.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If there is something wrong in code I modified a minute ago, I&amp;rsquo;m still in the right headspace and can usually fix it pretty quickly.&lt;/li&gt;
&lt;li&gt;If it takes a few days to get a test result, I won&amp;rsquo;t remember all the detail and will have to refresh my memory.&lt;/li&gt;
&lt;li&gt;If it takes several months to hear that something&amp;rsquo;s wrong, I may be working on a totally different part of the system and it will take longer to context switch.&lt;/li&gt;
&lt;li&gt;If a bug report comes in from the field a year or two later, I might as well be starting again from scratch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But - as ever - engineering is a trade-off. You can&amp;rsquo;t write a test to catch a bug you haven&amp;rsquo;t foreseen. It may be prohibitively expensive to test all possible combinations before release.&lt;/p&gt;
&lt;h2 id=&#34;-why-not-ci&#34;&gt;❌ Why not CI&lt;/h2&gt;
&lt;p&gt;CI is not suitable for all software projects.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re writing a scratch throw-away project that won&amp;rsquo;t live for very long, even simple CI may not be worth it.&lt;/p&gt;
&lt;p&gt;If you have a legacy codebase that was written without testing in mind, it might be prohibitively expensive to refactor to set these up. Nevertheless, in such projects there is often still some value to be found in a continuous build.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s be pragmatic.&lt;/p&gt;
&lt;h3 id=&#34;tests-arent-everything&#34;&gt;Tests aren&amp;rsquo;t everything&lt;/h3&gt;
&lt;p&gt;On the face of it, more testing means greater quality, right? Well&amp;hellip; &lt;em&gt;maybe&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Keep the end goal in sight.
It&amp;rsquo;s up to you to decide what makes sense for your situation; I recommend taking a &lt;em&gt;whole-of-organisation view&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to balance test runtime against overall feedback cycles. If the tests take too long to run, you&amp;rsquo;re slowing people down.&lt;/li&gt;
&lt;li&gt;Some tests are expensive in terms of time or consuming resources, so you might not want to run them daily.&lt;/li&gt;
&lt;li&gt;Tests involving physical devices can be difficult to automate, and risk creating a process bottleneck. (Consider emulation and/or simulation where appropriate.)&lt;/li&gt;
&lt;li&gt;Beware of over-testing; you may not need to exhaustively check all the combinations. Statistical techniques might help you out here.&lt;/li&gt;
&lt;li&gt;Beware of making your black-box tests too strict; this can lead to brittle tests that are more hassle to maintain than they are worth.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;costs-and-maintenance&#34;&gt;Costs and maintenance&lt;/h3&gt;
&lt;p&gt;It will take time and effort to set up CI. How much time and effort, I can&amp;rsquo;t say.&lt;/p&gt;
&lt;p&gt;In times past, CI was quite the bespoke effort.&lt;/p&gt;
&lt;p&gt;These days there is good tooling support for many environments, so it is usually pretty quick to get something going. From there you can decide how far to go.&lt;/p&gt;
&lt;h3 id=&#34;it-might-be-too-big-for-your-platform&#34;&gt;It might be too big for your platform&lt;/h3&gt;
&lt;p&gt;CI platforms are designed for small, lightweight processes. Think seconds to minutes, not hours.&lt;/p&gt;
&lt;p&gt;If you need to build a large application or a full Yocto firmware image, it&amp;rsquo;s going to be tough to make that fit within the limits of a cloud-hosted CI platform. Don&amp;rsquo;t despair! There are ways out, but you need to be smart. Alternative options include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;self-hosting CI runners that are take part in a cloud source repository;&lt;/li&gt;
&lt;li&gt;self-hosting the CI environment (e.g. Gitlab, Jenkins, CircleCI), noting that most source code hosting platforms have integrations;&lt;/li&gt;
&lt;li&gt;split up the task into multiple smaller CI jobs making good use of &lt;em&gt;artefacts&lt;/em&gt; between stages;&lt;/li&gt;
&lt;li&gt;reconsidering what is truly worth automating anyway.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-steps-you-can-take&#34;&gt;👷 Steps you can take&lt;/h2&gt;
&lt;h3 id=&#34;1-build-your-units&#34;&gt;1. Build your units&lt;/h3&gt;
&lt;p&gt;In most projects you already had to set up a buildsystem. Automating this is usually pretty cheap though you will need to get the tooling right.&lt;/p&gt;
&lt;div class=&#34;callout info&#34;&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
      &lt;strong&gt;Tooling on cloud platforms&lt;/strong&gt;
      &lt;p&gt;
On-cloud CI (as provided by Github, Gitlab, Bitbucket and others) is generally containerised.
What this means is that your project has to know how to install its own tooling, starting with a minimal (usually Linux) container image.
&lt;/p&gt;&lt;p&gt;
&lt;i&gt;This is really good practice!&lt;/i&gt;
Doing so means your required tools are themselves expressed in source code under revision control.
&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  
  
  &lt;style&gt;
     
    .callout {
      position: relative;  
      padding: 20px;
      margin: 20px 0;
      border: 2px solid;  
      border-radius: 8px;  
      font-family: Arial, sans-serif;
      background-color: #f9f9f9;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    }

     
    .callout::before {
      content: &#34;\f00c&#34;;  
      font-family: &#34;FontAwesome&#34;;
      font-weight: normal;  
      position: absolute;
      left: -40px;  
      top: 50%;
      transform: translateY(-50%);
      font-size: 24px;  
      color: inherit;  
    }

     
    .callout.info {
      background-color: #e0f7fa;
      border-color: #00acc1;
    }

    .callout.info::before {
      content: &#34;\f05a&#34;;  
    }

     
    .callout.warning {
      background-color: #fff3e0;
      border-color: #ff6f00;
    }

    .callout.warning::before {
      content: &#34;\f071&#34;;  
    }

     
    .callout.success {
      background-color: #e8f5e9;
      border-color: #43a047;
    }

    .callout.success::before {
      content: &#34;\f058&#34;;  
    }

     
    .callout.error {
      background-color: #ffebee;
      border-color: #e53935;
    }

    .callout.error::before {
      content: &#34;\f057&#34;;  
    }

     
    .callout.quote {
      background-color: #bbb;
      border-color: #666;
    }

    .callout.quote::before {
      content: &#34;\f27a&#34;;  
    }
&lt;/style&gt;

&lt;p&gt;Where this might get tricky is if you have multiple build configurations (platforms or builds with different features).
Don&amp;rsquo;t be surprised if automating reveals shortcomings in your setup.&lt;/p&gt;
&lt;p&gt;If you have autogenerated documentation, consider running that too. (In Rust, for example, it could be as easy as adding a &lt;code&gt;cargo doc&lt;/code&gt; step.)&lt;/p&gt;
&lt;h3 id=&#34;2-test-your-units&#34;&gt;2. Test your units&lt;/h3&gt;
&lt;p&gt;Adding unit tests to CI is usually pretty cheap though it will depend on the language and available test frameworks.&lt;/p&gt;
&lt;p&gt;If you want to include language-intrinsic checks (e.g. code style, static analysis) this is a good time to build them in. Some analyses can be quite expensive so it may not make sense to run all the checks at the same frequency.&lt;/p&gt;
&lt;h3 id=&#34;3-integrate-it&#34;&gt;3. Integrate it&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re pulling multiple component parts (microservices, standalone executables) together to make an end result, that&amp;rsquo;s the next step. Do they play nicely? Do you want to run any isolated tests among them before you move to delivery-level tests?&lt;/p&gt;
&lt;h3 id=&#34;4-add-more-checks&#34;&gt;4. Add more checks&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;#3-improved-quality&#34;&gt;I spoke about these above&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is where things stop being cheap and you have to start thinking about building out supporting infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;5-deliver-it&#34;&gt;5. Deliver it&lt;/h3&gt;
&lt;p&gt;Now we&amp;rsquo;re getting quite situation-specific. Think about what it means to deliver your project.&lt;/p&gt;
&lt;p&gt;Are you building a package for an ecosystem (Rust crate / Python pypi / npm.js / &amp;hellip;) ?
You might be able to automate the packaging steps and that might be pretty cheap.&lt;/p&gt;
&lt;p&gt;Are you building an application? Perhaps you can automate the process of building the installer / container / whatever shape it takes.
If you have multiple build configurations or platforms, it could get very tedious to build them all by hand and there is often a win for automation.&lt;/p&gt;
&lt;div class=&#34;callout warning&#34;&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
      &lt;strong&gt;&lt;/strong&gt;
      &lt;p&gt;
Where there&#39;s code signing involved, you&#39;ll need to decide whether it makes sense to automate that or leave it as a manual release step. Never put private keys or other code signing secrets directly into source!
Some platforms have secrets mechanisms that may be of use, but it pays to be cautious. If your secrets leak, how will you repair the situation?
&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  
  
  &lt;style&gt;
     
    .callout {
      position: relative;  
      padding: 20px;
      margin: 20px 0;
      border: 2px solid;  
      border-radius: 8px;  
      font-family: Arial, sans-serif;
      background-color: #f9f9f9;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    }

     
    .callout::before {
      content: &#34;\f00c&#34;;  
      font-family: &#34;FontAwesome&#34;;
      font-weight: normal;  
      position: absolute;
      left: -40px;  
      top: 50%;
      transform: translateY(-50%);
      font-size: 24px;  
      color: inherit;  
    }

     
    .callout.info {
      background-color: #e0f7fa;
      border-color: #00acc1;
    }

    .callout.info::before {
      content: &#34;\f05a&#34;;  
    }

     
    .callout.warning {
      background-color: #fff3e0;
      border-color: #ff6f00;
    }

    .callout.warning::before {
      content: &#34;\f071&#34;;  
    }

     
    .callout.success {
      background-color: #e8f5e9;
      border-color: #43a047;
    }

    .callout.success::before {
      content: &#34;\f058&#34;;  
    }

     
    .callout.error {
      background-color: #ffebee;
      border-color: #e53935;
    }

    .callout.error::before {
      content: &#34;\f057&#34;;  
    }

     
    .callout.quote {
      background-color: #bbb;
      border-color: #666;
    }

    .callout.quote::before {
      content: &#34;\f27a&#34;;  
    }
&lt;/style&gt;

&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Most projects will benefit from a little CI. You don&amp;rsquo;t need to have unit tests, though they are a good idea.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re going to have to maintain your CI, so build it for maintainability like you do your software.&lt;/li&gt;
&lt;li&gt;Apply agile to your CI as you do to your deliverables. Perfect is the enemy of good enough.
Build something, get feedback, iterate!&lt;/li&gt;
&lt;li&gt;CI vendors want to lock you in to their platform. Keep your eyes open.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t let CI become an all-consuming monster that prevents you from delivering in the first place!&lt;/li&gt;
&lt;/ul&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/code-crane.jpg" type="image" /></item>
    
    <item>
      <title>Announcing qcp</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2024/11/announcing-qcp/</link>
      <pubDate>Sun, 03 Nov 2024 10:23:43 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2024/11/announcing-qcp/</guid>
      <description>&lt;p&gt;The QUIC Copier (&lt;code&gt;qcp&lt;/code&gt;) is an experimental high-performance remote file copy utility for long-distance internet connections.&lt;/p&gt;
&lt;p&gt;Source repository: &lt;a href=&#34;https://github.com/crazyscot/qcp&#34;&gt;https://github.com/crazyscot/qcp&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;-features&#34;&gt;📋 Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;🔧 Drop-in replacement for &lt;code&gt;scp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;🛡️ Similar security to &lt;code&gt;scp&lt;/code&gt;, using existing, well-known mechanisms&lt;/li&gt;
&lt;li&gt;🚀 Better throughput on congested networks&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-about-qcp&#34;&gt;📖 About qcp&lt;/h2&gt;
&lt;p&gt;qcp is a &lt;em&gt;hybrid protocol&lt;/em&gt; combining ssh and &lt;a href=&#34;https://quicwg.github.io/&#34;&gt;QUIC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We use ssh to establish a control channel to the target machine, then spin up the QUIC protocol to transfer data.&lt;/p&gt;
&lt;p&gt;This has the following useful properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User authentication is handled entirely by ssh&lt;/li&gt;
&lt;li&gt;Data is transmitted over UDP, avoiding known issues with TCP over &amp;ldquo;long, fat pipe&amp;rdquo; connections&lt;/li&gt;
&lt;li&gt;Data in transit is protected by TLS using ephemeral keys&lt;/li&gt;
&lt;li&gt;The security mechanisms all use existing, well-known cryptographic algorithms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For full documentation refer to &lt;a href=&#34;https://docs.rs/qcp/latest/qcp/&#34;&gt;qcp on docs.rs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;motivation&#34;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;I needed to copy multiple large (3+ GB) files from a server in Europe to my home in New Zealand.&lt;/p&gt;
&lt;p&gt;I’ve got nothing against ssh or scp. They’re brilliant. I’ve been using them since the 1990s. However they run on top of TCP, which does not perform very well when the network is congested. With a fast fibre internet connection, a long round-trip time and noticeable packet loss, I was right in the sour spot. TCP did its thing and slowed down, but when the congestion cleared it was very slow to get back up to speed.&lt;/p&gt;
&lt;p&gt;If you’ve ever been frustrated by download performance from distant websites, you might have been experiencing this same issue. Friends with satellite (pre-Starlink) internet connections seem to be particularly badly affected.&lt;/p&gt;
&lt;h2 id=&#34;-getting-qcp&#34;&gt;💻 Getting qcp&lt;/h2&gt;
&lt;p&gt;The project is a Rust binary &lt;a href=&#34;https://crates.io/crates/qcp&#34;&gt;crate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can install it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;as a Debian package or pre-compiled binary from the &lt;a href=&#34;https://github.com/crazyscot/qcp/releases/latest&#34;&gt;latest qcp release&lt;/a&gt; page (N.B. the Linux builds are static musl binaries);&lt;/li&gt;
&lt;li&gt;with &lt;code&gt;cargo install qcp&lt;/code&gt; (you will need to have a rust toolchain &lt;del&gt;and capnpc&lt;/del&gt; installed) &lt;em&gt;(the capnp dependency was removed later)&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;by cloning and building the &lt;a href=&#34;https://github.com/crazyscot/qcp&#34;&gt;source repository&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will need to install qcp on both machines. Please refer to the &lt;a href=&#34;https://github.com/crazyscot/qcp/blob/main/README.md&#34;&gt;README&lt;/a&gt; for more.&lt;/p&gt;
&lt;h3 id=&#34;see-also&#34;&gt;See also&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc9000.html&#34;&gt;RFC9000 &amp;ldquo;QUIC: A UDP-Based Multiplexed and Secure Transport&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/qcp-in-progress.png" type="image" /></item>
    
    <item>
      <title>Broadcast graphics for fencing</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2024/08/broadcast-graphics-for-fencing/</link>
      <pubDate>Thu, 15 Aug 2024 21:16:22 +1200</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2024/08/broadcast-graphics-for-fencing/</guid>
      <description>&lt;p&gt;I created a TV graphics package for fencing tournaments.&lt;/p&gt;
&lt;p&gt;Earlier this year, Christchurch played host to the Commonwealth Junior &amp;amp; Cadet fencing tournament.&lt;/p&gt;
&lt;p&gt;Selected parts of the tournament were livestreamed, with a package broadcast on Sky TV (NZ).
The broadcast and finals streams had a live graphics package fed from
the scoreboard.&lt;/p&gt;
&lt;p&gt;The programmes were produced using a broadcast-spec OB truck supplied by &lt;a href=&#34;https://kiwiob.tv/&#34;&gt;Kiwi Outside Broadcast&lt;/a&gt;.
The truck graphics PC used &lt;em&gt;Captivate&lt;/em&gt; to generate its graphics, which output as key+fill SDI signals. These were fed to the vision mixer and keyed onto the picture in the usual way.&lt;/p&gt;
&lt;p&gt;The package can be seen in action on the &lt;a href=&#34;https://youtu.be/n4kEKuf7Mf4?si=Nh3CHiz_-WjDBoPB&amp;amp;t=356&#34;&gt;Commonwealth Junior &amp;amp; Cadet 2024 programmes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The details read from the scoreboard cover the &amp;ldquo;hit&amp;rdquo; lamps, scores, clock (including fractional seconds in the last 10s), period, red/yellow cards, and the priority indicator. On top of that, the package provides a place to enter the fencer names and nationalities, and set colours for them.&lt;/p&gt;
&lt;p&gt;This is all made possible by the scoreboard, a Favero FA-07, offering a data feed over an RS-422 interface.
I wrote a Python script to parse the data feed, turn it into a JSON dictionary and pass on to Captivate.&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/fencing-fa07.nbtlproj.jpg" type="image" /></item>
    
    <item>
      <title>Fault-finding at the ends of the earth</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2024/04/fault-finding-at-the-ends-of-the-earth/</link>
      <pubDate>Mon, 29 Apr 2024 13:29:43 +1200</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2024/04/fault-finding-at-the-ends-of-the-earth/</guid>
      <description>&lt;p&gt;This is a tale from many months ago, working on an embedded ARM target.&lt;/p&gt;
&lt;p&gt;In my private journal I wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Today I feel like I saddled up and rode my horse to the literal ends of the earth. I was fault-finding in the setting-up-of-the-universe that happens before your program starts up, and in the tearing-it-down-again that happens after you declare you&amp;rsquo;re finished.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you know C++, you might guess that this was a story about static object allocation and deallocation. You&amp;rsquo;d be right. So, destructors belonging to static-allocated objects. You&amp;rsquo;d never think they&amp;rsquo;d run on a bare-metal embedded target.&lt;/p&gt;
&lt;p&gt;Well, they can. If your target supports &lt;code&gt;exit()&lt;/code&gt; - e.g. if you are running with newlib - then an &lt;code&gt;atexit&lt;/code&gt; handler is set up for you, and that will be set up to run the static destructors. If your program then calls &lt;code&gt;exit()&lt;/code&gt; (as, say, your on-silicon unit tests might, at the end of a test run) then things are at risk of turning to custard.&lt;/p&gt;
&lt;p&gt;You might have enabled an interrupt for some peripheral on the silicon. In order to do anything really useful, the ISR might reference a static object. If you do this, you&amp;rsquo;d damn well better make sure the object has a static destructor that disables the interrupt, or hilarity is one day going to ensue. You know, the sort of hilarity that involves being savaged by a horde of angry rampaging badgers, or your socks catching fire.&lt;/p&gt;
&lt;p&gt;But wait, I hear you say, it called exit! The program no longer exists! Well, sure it doesn&amp;rsquo;t; but &lt;em&gt;what happens on exit&lt;/em&gt;? On this particular ARM target, running tests via a debugger as part of a CI chain, when the atexit handlers have run the process signals final completion with a semihosting call, which is a special flavour of debug breakpoint. It is&amp;hellip; &lt;em&gt;not fast&lt;/em&gt;. If your interrupt happens regularly, the goblins are going to get you before the pseudo-system-call completes. &lt;strong&gt;Your test framework will fail the test executable for hanging, despite somehow passing all of its tests.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There was an actual bug in there, and it was mine. Class X, which contained an RTOS queue and enabled an interrupt, only had a default destructor. On exit, somewhen between static destructors and completion of the semihosting exit call, the ISR fired. It duly failed to insert an item into the now-destroyed queue, so jumped to the internal panic routine. That routine contained a breakpoint and then went nowhere fast, waiting for a debugger command that was never going to arrive &amp;mdash; hence the time-out. Maybe it would have been useful to have a library option to skip the static destructors, but I probably wouldn&amp;rsquo;t have been aware of it ahead of time anyway.&lt;/p&gt;
&lt;p&gt;The static destructor ordering fiasco can also be yours for the taking, but thankfully that hadn&amp;rsquo;t bitten me. Nevertheless, it was a rough day.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cover image: &lt;a href=&#34;https://www.freepik.com/free-vector/cyber-bug-search_81409647.htm&#34;&gt;Cyber Bug Search, by juicy_fish on Freepik&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/bug-search-by-juicy_fish-on-freepik.jpg" type="image" /></item>
    
    <item>
      <title>The Road Less Travelled</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2024/01/the-road-less-travelled/</link>
      <pubDate>Thu, 04 Jan 2024 20:03:08 +1200</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2024/01/the-road-less-travelled/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve started an occasional YouTube series about quirky, off-the-beaten-track places.&lt;/p&gt;
&lt;p&gt;At the time of writing there are 5 episodes online. I&amp;rsquo;m still experimenting with style and techniques.
My inspiration is a combination of &lt;em&gt;The Tim Traveller&lt;/em&gt; and &lt;em&gt;Tom Scott&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So far it consists of a few random places around South Island.
There&amp;rsquo;s no particular timescale for publishing new episodes; I have many demands on my time, and this project isn&amp;rsquo;t yielding an income at the moment.
(It may never; I&amp;rsquo;m doing it for fun.) Presenting is taking some getting used to!&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the playlist:&lt;/p&gt;
&lt;iframe width=&#34;640&#34; height=&#34;360&#34; src=&#34;https://www.youtube.com/embed/videoseries?si=k6sLeGsP0BSontxs&amp;amp;list=PLkN3Y2Ip2FvCaSVDRnyWdhlQIvNNzZ1ND&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen&gt;&lt;/iframe&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/the-road-less-travelled-title.jpg" type="image" /></item>
    
    <item>
      <title>About Me</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/about/</link>
      <pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/about/</guid>
      <description>&lt;p&gt;I grew up in Scotland, lived for some years in Cambridge in England, and now live with my wife in Christchurch, New Zealand.&lt;/p&gt;
&lt;p&gt;A child of the 1980s, I&amp;rsquo;ve been a heavy computer (and later Internet) user since my first home micro at the age of 8.
I think of myself as a generalist, capable of deep understanding of systems and subjects, yet also capable of turning my hand to new challenges, new workflows, new environments. I&amp;rsquo;m not too proud to be told when I&amp;rsquo;m wrong, and I believe in finding the right solution.&lt;/p&gt;
&lt;p&gt;My first career was as a software engineer, teaching machines to communicate.
From 2000 to 2011 – after my first degree – I lived in Cambridge, working mostly as a software
engineer with a focus on embedded and embedded-adjacent systems.&lt;/p&gt;
&lt;p&gt;In software, my professional interests include cryptography, reliability,
and leading teams to greater performance through careful application of
DevOps principles.&lt;/p&gt;
&lt;p&gt;My wife and I moved to Christchurch in 2011, several months after the &lt;a href=&#34;https://en.wikipedia.org/wiki/February_2011_Christchurch_earthquake&#34;&gt;devastating earthquake that struck the region&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In 2013 I realised that it was time for a new challenge and enrolled at the New Zealand Broadcasting School, to study Digital Film and TV Production for the Bachelor of Broadcasting Communications degree. I completed the course in 2016.
I particularly enjoy camera operation, vision switching and editing and I love the buzz of the team environment, both on the set and in the studio.&lt;/p&gt;
&lt;p&gt;For a few years I was a freelance videographer and video technician,
but as of 2019 my primary focus is back on software. (I keep my hand
in with the occasional freelance job. Since 2023 I&amp;rsquo;ve been part of a
team covering NZ domestic basketball, broadcast live-to-air in NZ and
available across the world via ESPN.)&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a &lt;a href=&#34;https://en.wikipedia.org/wiki/Fencing&#34;&gt;fencer&lt;/a&gt;;
I&amp;rsquo;ve been fencing on and off for most of my life.  As of 2022 I am the
president of &lt;a href=&#34;https://unitedfencingclub.weebly.com/&#34;&gt;United
Fencing Club&lt;/a&gt;. I have served as both secretary and treasurer of
the regional body &lt;a href=&#34;http://fencingmidsouth.org.nz&#34;&gt;Fencing
Mid South&lt;/a&gt;, and I advise them and the national body &lt;a href=&#34;https://www.fencing.org.nz&#34;&gt;Fencing
NZ&lt;/a&gt; about video replay systems.  I used to
say that the épée was my preferred weapon but in 2023 I
was tempted to try the dark side that is sabre &amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;contact-information--gpg-key&#34;&gt;Contact information &amp;amp; GPG key&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re not already in contact with me, &lt;code&gt;webcontact at crazyscot.com&lt;/code&gt; is a good place to email in the first instance. &lt;em&gt;(Mar 2026: Apologies if you&amp;rsquo;ve tried to email that mailbox within the past year or so and found it bouncing. Now fixed.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I have a &lt;a href=&#34;https://www.chiark.greenend.org.uk/~ryounger/assets/002254bb.asc&#34;&gt;GPG key&lt;/a&gt;. (You might find a more up-to-date version on the standard keyservers.) If you know what to do with one of those, you&amp;rsquo;ll find other email addresses in it. The address which starts with the digit zero does not have any spam filtering, so is a good one to try if you have problems reaching me on other addresses.&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/ry%20glacier%202014.jpg" type="image" /></item>
    
    <item>
      <title>2023 in pictures</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2023/12/2023-in-pictures/</link>
      <pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2023/12/2023-in-pictures/</guid>
      <description>&lt;p&gt;It has been a somewhat lumpy year of photography, so I don&amp;rsquo;t have a favourite for each month. But I do have some favourites, more than 12 in fact.&lt;/p&gt;
&lt;p&gt;Willowbank Wildlife Reserve. These ducks look like they&amp;rsquo;re about to drop a sick album.&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53346457240/in/album-72177720313738844/&#34; title=&#34;20231121_103456&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53346457240_4317432603_z.jpg&#34; width=&#34;480&#34; height=&#34;640&#34; alt=&#34;20231121_103456&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/52763942209/in/album-72177720313738844/&#34; title=&#34;Kaikōura sunrise&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/52763942209_75297eb38c_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;Kaikōura sunrise&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;A cheeky selfie before the Davis Cup tennis tie. It was 34C in the shade, and I had no shade.&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/52672126511/in/album-72177720313738844/&#34; title=&#34;Cheeky tennis selfie&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/52672126511_4e10b1b908_z.jpg&#34; width=&#34;640&#34; height=&#34;480&#34; alt=&#34;Cheeky tennis selfie&#34;/&gt;&lt;/a&gt;&lt;script async src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53112238347/in/album-72177720313738844/&#34; title=&#34;a kotare (kingfisher) eating a crab&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53112238347_ff559393eb_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;a kotare (kingfisher) eating a crab&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/52763725851/in/album-72177720313738844/&#34; title=&#34;Seal family drama&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/52763725851_7359ff552b_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;Seal family drama&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Kakariki with malice aforethought&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53413641116/in/album-72177720313738844/&#34; title=&#34;Kakariki&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53413641116_5318c9d891_z.jpg&#34; width=&#34;640&#34; height=&#34;360&#34; alt=&#34;Kakariki&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Aurora (airglow?) and Magellanic Clouds&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/52843954454/in/album-72177720313738844/&#34; title=&#34;Aurora Australis and Magellanic Clouds&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/52843954454_68c00d48b8_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;Aurora Australis and Magellanic Clouds&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Belatedly celebrating C&amp;rsquo;s PhD atop the Sky Tower&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53432973904/in/album-72177720313738844/&#34; title=&#34;sky tower&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53432973904_a4f5c6f736_z.jpg&#34; width=&#34;640&#34; height=&#34;480&#34; alt=&#34;sky tower&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Blue-and-gold macaw in flight, at Willowbank&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53413810788/in/album-72177720313738844/&#34; title=&#34;Blue-and-gold macaw&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53413810788_d3238a9fab_z.jpg&#34; width=&#34;640&#34; height=&#34;360&#34; alt=&#34;Blue-and-gold macaw&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Rose macro&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53314198867/in/album-72177720313738844/&#34; title=&#34;IMG_7351&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53314198867_98e93ac3e1_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;IMG_7351&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Roto Kohatu Reserve. The Pūteketeke (Australasian crested grebe)&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53421682444/in/album-72177720313738844/&#34; title=&#34;IMG_8862-Enhanced-NR&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53421682444_af4e93e60c_z.jpg&#34; width=&#34;640&#34; height=&#34;360&#34; alt=&#34;IMG_8862-Enhanced-NR&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Behind the scenes at basketball&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53432800573/in/album-72177720313738844/&#34; title=&#34;basketball&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53432800573_e5a58638ba_z.jpg&#34; width=&#34;640&#34; height=&#34;480&#34; alt=&#34;basketball&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Busy bee&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53356636434/in/album-72177720313738844/&#34; title=&#34;IMG_8185&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53356636434_a3bb9abf8d_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;IMG_8185&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Waiuta. Two hats and a carful of sandflies.&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53187692612/in/album-72177720313738844/&#34; title=&#34;20230914_101203 waiuta two hats and a carful of sandflies&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53187692612_42f95f8ba5_z.jpg&#34; width=&#34;480&#34; height=&#34;640&#34; alt=&#34;20230914_101203 waiuta two hats and a carful of sandflies&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Willowbank. Scarlet macaw in flight.&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53431629312/in/album-72177720313738844/&#34; title=&#34;Scarlet macaw&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53431629312_5b4bbfa547_z.jpg&#34; width=&#34;640&#34; height=&#34;640&#34; alt=&#34;Scarlet macaw&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Willowbank. A pair of love birds (more blue-and-gold macaws)&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53432984260/in/album-72177720313738844/&#34; title=&#34;IMG_9170-Enhanced-NR&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53432984260_0a0152b438_z.jpg&#34; width=&#34;640&#34; height=&#34;360&#34; alt=&#34;IMG_9170-Enhanced-NR&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Rose macro in our garden&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/53356530908/in/album-72177720313738844/&#34; title=&#34;IMG_8221&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/53356530908_df6eaf569b_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;IMG_8221&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;Willowbank. Kea in flight.&lt;/p&gt;
&lt;p&gt;&lt;a data-flickr-embed=&#34;true&#34; href=&#34;https://www.flickr.com/photos/crazyscot/52300593674/in/album-72157717538273943/&#34; title=&#34;Kea in flight&#34;&gt;&lt;img src=&#34;https://live.staticflickr.com/65535/52300593674_fda30c9555_z.jpg&#34; width=&#34;640&#34; height=&#34;427&#34; alt=&#34;Kea in flight&#34; /&gt;&lt;/a&gt;&lt;script async=&#34;async&#34; src=&#34;//embedr.flickr.com/assets/client-code.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/rose-macro.jpg" type="image" /></item>
    
    <item>
      <title>Faster C&#43;&#43; code style checks: detail</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2023/08/faster-c-code-style-checks-detail/</link>
      <pubDate>Thu, 17 Aug 2023 22:04:01 +1200</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2023/08/faster-c-code-style-checks-detail/</guid>
      <description>&lt;p&gt;&lt;code&gt;xargs&lt;/code&gt;, &lt;code&gt;nproc&lt;/code&gt; and shell arrays, oh my!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is a companion article to &lt;a href=&#34;../developer-tooling-and-boiled-frogs/&#34;&gt;Developer tooling and boiled frogs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;before-47-seconds-&#34;&gt;Before: 4.7 seconds 💩&lt;/h2&gt;
&lt;p&gt;In this codebase, style checks were performed by clang-format with an in-house style file.
They ran in a pre-commit hook (as well as in CI). However, &lt;strong&gt;the pre-commit hook is critical for a good DX&lt;/strong&gt;; 5 seconds really sucks.&lt;/p&gt;
&lt;p&gt;The entry point to the check was a shell script which invoked clang-format, amongst other things. Like all such scripts, it was not truly original, being assembled from snippets and ideas found around the internet. Here&amp;rsquo;s the gist of it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_files&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# arguments are filenames. if any failed, returns 1; if all passed, returns 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    err&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; file in $@; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ext&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;file##*.&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$ext&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            c|cpp|h|hpp&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ! clang-format -Werror -style&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;file -n &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$file&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; ; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    err&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;code style check failed for &lt;/span&gt;$file&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            *&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;# unsupported file type; ignore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; $err
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_files &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;git ls-files&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Do you see the problems?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;It&amp;rsquo;s single-threaded!&lt;/em&gt; Almost all desktop and laptop CPUs have had multiple cores for 15 or more years, so this is truly Dark Ages style processing.&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re invoking &lt;code&gt;clang-format&lt;/code&gt; far more times than we need to. Every invocation has to parse the config file, which is wasteful.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;after-021-seconds-&#34;&gt;After: 0.21 seconds 🚀&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_files&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# arguments are filenames. if any failed, returns 1; if all passed, returns 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FILES&lt;span style=&#34;color:#f92672&#34;&gt;=()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; file in $@; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ext&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;file##*.&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$ext&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        c|cpp|h|hpp&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          FILES&lt;span style=&#34;color:#f92672&#34;&gt;+=(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$file&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          ;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        *&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;# unsupported file type; ignore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          ;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FILES_COUNT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${#&lt;/span&gt;FILES[@]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CORES&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;nproc&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# CORES+3 gave best performance for me. YMMV.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    JOB_SIZE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$((&lt;/span&gt;FILES_COUNT &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;CORES+3&lt;span style=&#34;color:#66d9ef&#34;&gt;))&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;JOB_SIZE&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; JOB_SIZE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;1; &lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;FILES[@]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; | xargs -P &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; -n &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;JOB_SIZE&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt; clang-format -Werror -style&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;file -n &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a bit to unpack here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We&amp;rsquo;ve moved from &lt;code&gt;sh&lt;/code&gt; to &lt;code&gt;bash&lt;/code&gt; to take advantage of &lt;a href=&#34;https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays&#34;&gt;array variables&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Rather than immediately running &lt;code&gt;clang-format&lt;/code&gt; on every file that passes the filter, add them to the &lt;code&gt;FILES&lt;/code&gt; array so we can use xargs. (Yes, I know this could be better structured. This was but a single step of evolution; &lt;em&gt;first, make it work&lt;/em&gt;.)&lt;/li&gt;
&lt;li&gt;We use &lt;code&gt;xargs&lt;/code&gt; in &lt;em&gt;parallel mode&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&amp;rsquo;ve naively assumed that checking each file takes roughly the same amount of time. This isn&amp;rsquo;t quite true (I found about a factor of 3 variation amongst them) but the performance gains were good enough that it doesn&amp;rsquo;t seem worth worrying about at the present time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key here is combining &lt;code&gt;-n $JOB_SIZE&lt;/code&gt; with &lt;code&gt;-P 0&lt;/code&gt; (unlimited parallelisation) and doing some shell arithmetic to figure out what that job size should be.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xargs&lt;/code&gt; with neither option runs a single &lt;code&gt;clang-format&lt;/code&gt; with all the input files in a single invocation.
&lt;ul&gt;
&lt;li&gt;This is &lt;em&gt;good&lt;/em&gt; (about a 6x speedup) but we can do better! It is still single-threaded, iterating through the files in order.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xargs -P $(nproc)&lt;/code&gt; sounds interesting but is no better on its own. The man page says it all:
&lt;blockquote&gt;
&lt;p&gt;Use the  -n  option  or  the -L option with -P; otherwise chances are that only one exec will be done.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve counted the files, divided by (slightly more than) the number of processor cores available, and told xargs to parcel the files out in jobs of that size. Of course, if you&amp;rsquo;re doing this yourself, you&amp;rsquo;ll want to repeat the experiment to figure out the sweet spot for your hardware and your codebase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;🚅 Final speedup for this activity: 22.3x 🚀&lt;/strong&gt;
(timings as measured on my laptop for that codebase on that day; your mileage may vary, may contain nuts, etc.)&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/code.jpg" type="image" /></item>
    
    <item>
      <title>Developer tooling and boiled frogs</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2023/08/developer-tooling-and-boiled-frogs/</link>
      <pubDate>Thu, 17 Aug 2023 22:04:00 +1200</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2023/08/developer-tooling-and-boiled-frogs/</guid>
      <description>&lt;p&gt;This is an article about software tooling and the developer experience.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Right now I am spending most of my work time on an embedded project.
It&amp;rsquo;s C++ running on bare-metal target hardware. I&amp;rsquo;ve got all of 768K of RAM to play with, but that&amp;rsquo;s still a tad more than my first home micro had.&lt;/p&gt;
&lt;p&gt;Our tooling and infrastructure grew mostly organically.
I was the one who initialised the git repo.&lt;/p&gt;
&lt;h3 id=&#34;ci-chain-&#34;&gt;CI chain 🔗&lt;/h3&gt;
&lt;p&gt;We have a CI chain, of course. It has grown over time and does a few tricks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build for the target hardware (various targets; RAM and flash builds)&lt;/li&gt;
&lt;li&gt;build on Linux native port and run unit tests&lt;/li&gt;
&lt;li&gt;perform various analyses (clang-tidy, sonarcloud, code coverage, valgrind)&lt;/li&gt;
&lt;li&gt;code style enforcement (clang-format) 👮&lt;/li&gt;
&lt;li&gt;prepare a signed upgrade image with the development signing key&lt;/li&gt;
&lt;li&gt;assemble a whole-of-flash image for the factory 🏭&lt;/li&gt;
&lt;li&gt;build and run some unit tests on a development board&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The dev board is on-premises but the rest happens in the cloud, taking a matter of minutes on every push.
It&amp;rsquo;s reasonably slick.&lt;/p&gt;
&lt;p&gt;You can run all the build and analysis steps locally.
We have a script that spins up a container emulating our CI provider so you don&amp;rsquo;t even need to have the dev tools installed, you only need to pull our in-house container image.&lt;/p&gt;
&lt;h2 id=&#34;how-much-effort-to-spend-on-tooling-&#34;&gt;How much effort to spend on tooling? 💸&lt;/h2&gt;
&lt;p&gt;Many a savvy manager or product owner will tell you to not spend too much time on tooling.
They have a point; time spent working on tooling and infra is not delivering features, after all.
Many managers have been bitten by infrastructure blow-outs and would rather we focus on building the stuff they want us to build.&lt;/p&gt;
&lt;p&gt;But here&amp;rsquo;s the thing.
When they say that, they don&amp;rsquo;t mean &amp;ldquo;forget about tooling and never speak of it again&amp;rdquo;.
You are allowed to bring the subject up again in future, and you are allowed to try to educate them as to the value.&lt;/p&gt;
&lt;h3 id=&#34;the-frog-boils-&#34;&gt;The frog boils 📉&lt;/h3&gt;
&lt;p&gt;We now have over 300 source files that get compiled into the image, which includes some vendor and autogenerated code.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s the rub. We didn&amp;rsquo;t start out with anywhere near that many. The CI chain used to be really fast, under one minute.
Local builds were fast, and even running the clang-tidy script wasn&amp;rsquo;t that awful (though I did build a cache for it, which helped).&lt;/p&gt;
&lt;p&gt;The size and complexity grew over time, and here we are today.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s like the old tale about boiling frogs.
&lt;em&gt;(Disclaimer: I don&amp;rsquo;t even know if this is true, and I definitely don&amp;rsquo;t suggest you try it, but it&amp;rsquo;s an interesting thought experiment.)&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What happens if you drop a frog into a pot of boiling water?&lt;/p&gt;
&lt;p&gt;The frog doesn&amp;rsquo;t like it and jumps out.&lt;/p&gt;
&lt;p&gt;But what happens if you drop a frog into a pot of cold water, then light the stove?
The frog doesn&amp;rsquo;t realise anything is wrong. It boils to death before it thinks to jump out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Builds with a cold cache were taking a couple of minutes locally, and about 8 minutes in the cloud. (This happens depressingly often when you change a low-level header that is included all over the place&amp;hellip; there&amp;rsquo;s another observation, that maybe our architecture needs work, but that&amp;rsquo;s for another day.)&lt;/p&gt;
&lt;p&gt;A full static analysis with cold cache was taking over 5 minutes locally, and 22 in the cloud. (The difference is because our company-provided laptops have 16 cores, while our CI provider runs our container with 4 vCPU cores and tightly limited RAM.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Goodness me, 300 compile units now?&lt;/em&gt;
&lt;em&gt;This water sure is getting warm!&lt;/em&gt; 😰&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s a &lt;strong&gt;poor developer experience&lt;/strong&gt;. Even running the code style checker script takes five whole seconds. That&amp;rsquo;s 5 seconds in which you can get distracted, pop over to Slack, and whoops! ten minutes later, you&amp;rsquo;re trying to remember what it was you were doing.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been in this business long enough to know that keeping myself and my team focussed is vitally important to getting things done and keeping the stakeholders happy.
An efficient DX is part of that puzzle.&lt;/p&gt;
&lt;p&gt;Like the mythical frog, I don&amp;rsquo;t remember noticing these tasks growing longer.&lt;/p&gt;
&lt;h2 id=&#34;a-concrete-walk-through-&#34;&gt;A concrete walk-through 🔧&lt;/h2&gt;
&lt;p&gt;OK, so you&amp;rsquo;ve decided you need to jump out of the pan and spend some time sharpening your tools. Great.&lt;/p&gt;
&lt;p&gt;The most important advice I can offer is to &lt;em&gt;understand where the pain points are&lt;/em&gt;. 🔍&lt;/p&gt;
&lt;p&gt;If this is stuff you work with every day, you maybe already know where the pain points are but are blind to them because you haven&amp;rsquo;t noticed how poor the DX has become. Open your eyes, and you may find it is in plain sight. Don&amp;rsquo;t optimise for its own sake; &lt;em&gt;optimise where it will help&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I noted about our tooling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The compilation time itself is already pretty well optimised and parallelised thanks to &lt;code&gt;meson&lt;/code&gt;, &lt;code&gt;ninja&lt;/code&gt; and &lt;code&gt;ccache&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The code style checker is something we run very frequently as part of our workflow (it&amp;rsquo;s in our pre-commit script). At 5 seconds, that&amp;rsquo;s like sand in your gears.&lt;/li&gt;
&lt;li&gt;The static analyser is a different type of pain point. We run it interactively but we don&amp;rsquo;t like to because it&amp;rsquo;s a slow ol&amp;rsquo; beast. On the cloud, it is the slowest part of the build; it is probably the reason why we&amp;rsquo;re regularly incurring overage on our CI build minutes. (Now, that&amp;rsquo;s not a big cost compared to an engineer&amp;rsquo;s salary, but it&amp;rsquo;s not zero and it has been noticed, so maybe worth spending a little time on.)&lt;/li&gt;
&lt;li&gt;The unit test run on a dev board is a bottleneck, practically by definition, as it uses a self-hosted CI runner plugged into our pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;code-style-checks-parallelise&#34;&gt;Code style checks: Parallelise!&lt;/h3&gt;
&lt;p&gt;We use &lt;code&gt;clang-format&lt;/code&gt; with an in-house style file.&lt;/p&gt;
&lt;p&gt;The heart of our checker script is a shell function.&lt;/p&gt;
&lt;p&gt;The tl;dr version is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We were invoking clang-format in a tight loop, once per input file. 🤦&lt;/li&gt;
&lt;li&gt;Now, we count the files, divide by (slightly more than) the number of CPU cores available, and have xargs parcel out the work into that many chunks. 🚅&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;static-analysis-reconsider-the-load-cache-and-parallelise-where-it-will-help&#34;&gt;Static analysis: Reconsider the load, cache, and parallelise where it will help!&lt;/h3&gt;
&lt;p&gt;The learnings from this one are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do the heavy lifting in parallel (we were already)&lt;/li&gt;
&lt;li&gt;Cacheing positive analysis results helps (we were doing that too)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t analyse unit tests or test harness/mocks/fakes&lt;/strong&gt; unless you have a good reason&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t analyse target-specific files for obsolete hardware targets&lt;/strong&gt; unless you have a good reason&lt;/li&gt;
&lt;li&gt;Where you have a string-and-glue shell phase to prepare the analysis run, it can be worth parallelising that as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;results-&#34;&gt;Results 🚀&lt;/h2&gt;
&lt;p&gt;(Timed on my work laptop, which has 16 cores. It&amp;rsquo;s running Ubuntu 22.04 on WSL on Windows 11.)&lt;/p&gt;

     &lt;table class=&#34;table table-striped table-bordered&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action/Phase&lt;/th&gt;
&lt;th&gt;Time before&lt;/th&gt;
&lt;th&gt;Time after&lt;/th&gt;
&lt;th&gt;Speed-up&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code style check&lt;/td&gt;
&lt;td&gt;4.7 seconds&lt;/td&gt;
&lt;td&gt;210 ms&lt;/td&gt;
&lt;td&gt;22.3x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static analysis setup&lt;/td&gt;
&lt;td&gt;14 seconds&lt;/td&gt;
&lt;td&gt;3.5s&lt;/td&gt;
&lt;td&gt;4x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;/table&gt;
&lt;h3 id=&#34;my-next-steps&#34;&gt;My next steps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The on-target testing phase is a bottleneck, so we&amp;rsquo;re going to add more hardware.&lt;/li&gt;
&lt;li&gt;There might be some mileage in using using &lt;a href=&#34;https://mesonbuild.com/Precompiled-headers.html&#34;&gt;pre-compiled headers&lt;/a&gt; to speed up cold-cache compilation.
&lt;ul&gt;
&lt;li&gt;This isn&amp;rsquo;t something the places I have worked have traditionally done, but I know it&amp;rsquo;s popular when you&amp;rsquo;re dealing with the Win32 C++ api.&lt;/li&gt;
&lt;li&gt;Our codebase has a &amp;ldquo;platform&amp;rdquo; layer; this smells like a good starting point for precompilation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Coverage analysis. We already do this, but there&amp;rsquo;s another pain point while &lt;code&gt;genhtml&lt;/code&gt; crunches the data and generates the HTML report. This one could be trickier; it&amp;rsquo;s a single-threaded perl script, invoked by meson.&lt;/li&gt;
&lt;li&gt;Stack usage analysis. We&amp;rsquo;ve got some code paths that can be provoked to overflow on debug builds. This is a different sort of pain point; while we&amp;rsquo;ve configured the silicon to detect and hard-fault on overflow, I&amp;rsquo;d like to set up some analysis to automatically find these situations ahead of time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;How long to spend on infrastructure?&lt;/em&gt; 💸&lt;/p&gt;
&lt;p&gt;Circling back to a question I asked earlier, how long is a piece of string?&lt;/p&gt;
&lt;p&gt;It depends where you are and what your current DX is like.&lt;/p&gt;
&lt;p&gt;10-20% of your developer time seems a reasonable benchmark; it&amp;rsquo;s enough that you can get a handful of DX tickets or story points through every iteration without becoming too much of a drag on progress.
After all, if you&amp;rsquo;re working agile (and who isn&amp;rsquo;t, these days?) constant improvements are the key.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;How&amp;rsquo;s your developer experience looking?&lt;/em&gt;&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/software/code.jpg" type="image" /></item>
    
    <item>
      <title>Time, projects and the sparking of joy</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2022/11/time-projects-and-the-sparking-of-joy/</link>
      <pubDate>Mon, 28 Nov 2022 06:27:00 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2022/11/time-projects-and-the-sparking-of-joy/</guid>
      <description>&lt;p&gt;I had a big realisation the other day.
The most important currency in my life, right now, is &lt;em&gt;time&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-value-of-time&#34;&gt;The value of time&lt;/h2&gt;
&lt;p&gt;We had already reached the point in this household where it was worthwhile for us to pay a cleaner, because we both &lt;em&gt;really&lt;/em&gt; hate cleaning. Now we are thinking about a gardener. It is worth paying $6 worth of courier fee to mail order something even when it is 10 or 15 minutes drive away, because the &lt;em&gt;time cost&lt;/em&gt; of getting in the car and going there is worth more than $6 (before you consider petrol or parking). Provided, of course, the item can be readily found and confirmed on the supplier&amp;rsquo;s website (insert rant about crap online shopping sites here).&lt;/p&gt;
&lt;p&gt;Chatting with a colleague the other day he mentioned feeling similarly, but there are chores he gets value from doing. He really loves mowing his lawn&amp;hellip; OK, his wife said, then we won&amp;rsquo;t pay someone to do it, but that&amp;rsquo;s your project to keep on top of.&lt;/p&gt;
&lt;p&gt;There are not enough hours in the day (week) (month) (year) to achieve everything I want to. Now I must qualify that statement; I do lose a noticeable chunk of time by not having the energy to achieve more, and some more by procrastination. Sometimes after work I have zero oomph to do, well, very much at all. Maybe there&amp;rsquo;s some neurodivergence effect in there that confounds.&lt;/p&gt;
&lt;p&gt;I have too many personal projects I want to do (let alone spending money on tools or toys I&amp;rsquo;ll hardly use, which is a side grumble in of itself). So it becomes imperative to choose which projects live, and which must suffocate. But &lt;em&gt;is&lt;/em&gt; it a death of a project, or merely parking it indefinitely? Practically speaking there isn&amp;rsquo;t much difference from the project&amp;rsquo;s point of view. From my POV it is better to not feel overwhelmed by my &lt;em&gt;to-don&amp;rsquo;t&lt;/em&gt; list, lest it become a source of doom and make-work, so I prefer to park things sufficiently far out of sight &amp;amp; mind that they might as well be dead.&lt;/p&gt;
&lt;p&gt;I used to try to prioritise things in terms of an Eisenhower matrix. Maybe I should again, but it somehow seems lacking. The urgent and important stuff gets done; the hard part is figuring out how to rank the importance of the non-urgent projects. The value, perhaps; but it&amp;rsquo;s tough to quantify.&lt;/p&gt;
&lt;h2 id=&#34;the-value-points-scale&#34;&gt;The value points scale&lt;/h2&gt;
&lt;p&gt;Somewhat idly, I propose the &lt;em&gt;value points&lt;/em&gt; scale. These are akin to &lt;em&gt;story points&lt;/em&gt;, for those who practice various flavours of agile. These aren&amp;rsquo;t absolute dollar values; they are relative. Start with Marie Kondo-style thinking of whether completing this project will bring joy, or some other emotion that you find valuable. If you find the term &lt;em&gt;value points&lt;/em&gt; confusing, maybe call them &lt;em&gt;Kondo points&lt;/em&gt;? There is no &lt;em&gt;direct&lt;/em&gt; correlation between value and the effort required, though there will likely be some.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixing a light switch or tidying the garden is mostly a chore but brings me a little joy at the feeling of a job well done, so perhaps call that one kondo.&lt;/li&gt;
&lt;li&gt;Making something out of wood - say, a table - is a big project that will give me some pride, perhaps quite a lot of pride if it involved new techniques or tools. I could maybe see that being 8 or 13 kondos.&lt;/li&gt;
&lt;li&gt;Doing something worthwhile for a good cause you really care about might fill you with warm fuzzies. How many?&lt;/li&gt;
&lt;li&gt;For those of us who love to write software, a personal passion project might come with a raft of kondos.&lt;/li&gt;
&lt;li&gt;Art? Writing? Videography? These won&amp;rsquo;t all spark joy for everyone, so see if you can calibrate your own relative scale.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like story points, use a modified Fibonacci scale for the allowable numbers of points. 0, 1, 2, 3, 5, 8, 13, 20, 40, 100. Maybe a ½ point for the really small stuff. But don&amp;rsquo;t overthink it. This is supposed to be a &lt;em&gt;lightweight&lt;/em&gt; gut-feel metric. As you complete projects you may find your calibration improves.&lt;/p&gt;
&lt;p&gt;Agile practitioners talk of a definition of done; with these projects you will have your own idea of what done means, how good a job you need to do to satisfy yourself and cash in the joy. But beware of creeping perfectionism&amp;hellip; don&amp;rsquo;t let that 40 kondo project expand to take infinite time.&lt;/p&gt;
&lt;p&gt;Then, you have to decide how to spend your available time. If you have only 1 spare hour in a week that may not be enough for big projects, so consider the quick wins: projects that won&amp;rsquo;t take long but which will bring some joy. If you are fortunate to have lots of time and energy in a day or a week, you might carve up your time to balance a reward from completing short projects, and progressing longer ones.&lt;/p&gt;
&lt;p&gt;With larger projects the joy may not all come at once. You may find some joy at the milestone points. Only you can figure out whether this is part of your own calculus; you are your own project-joy-owner.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cover image by vectorjuice on &lt;a href=&#34;https://www.freepik.com/&#34;&gt;Freepik&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/unemployment-abstract-illustration-by-vectorjuice-on-freepik.jpg" type="image" /></item>
    
    <item>
      <title>Tensegrity table</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2020/11/tensegrity-table/</link>
      <pubDate>Sun, 15 Nov 2020 17:07:00 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2020/11/tensegrity-table/</guid>
      <description>&lt;p&gt;&lt;em&gt;Tensegri-what?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;These intriguing creations appear to defy gravify.&lt;/p&gt;
&lt;p&gt;On first encountering the concept, most people&amp;rsquo;s response is either &amp;ldquo;hey, that&amp;rsquo;s cool!&amp;rdquo; or &amp;ldquo;burn the witch!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Most of the weight is taken by the short central chain while the others serve to balance the top and keep it (mostly) flat. There&amp;rsquo;s a little play in the system, just enough to be intriguing.&lt;/p&gt;
&lt;p&gt;Mine is made from some kwila and white oak off-cuts, coated in Danish oil.
It&amp;rsquo;s reversible, and the table top is acrylic.&lt;/p&gt;
&lt;div class=&#34;videowrap&#34;&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/OwAelqAXEr8&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/things/tensegrity-table.jpg" type="image" /></item>
    
    <item>
      <title>Patio table</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2020/10/patio-table/</link>
      <pubDate>Tue, 06 Oct 2020 16:34:00 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2020/10/patio-table/</guid>
      <description>&lt;p&gt;I wanted a decent patio table.
I know how to work wood.
There are plenty of plans for projects like this online.
What could possibly go wrong&amp;hellip;?&lt;/p&gt;
&lt;p&gt;Well, in short: &lt;em&gt;a lot&lt;/em&gt;. This project is truly a testament to the old adage that there are few truly fatal mistakes in woodworking. No, I won&amp;rsquo;t tell you where the issues are; you can probably find them in person if you care to look closely.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s made from kwila. This is commonly used for decking here in NZ. However, as timbers go it exudes some very staining run-off (tannins) if you get it wet, so you have to be careful what you do with it. In my case, I treated it with a chemical wash that draws out the surface tannins, then a decking product that effectively restores some of its lovely rich colour.&lt;/p&gt;
&lt;p&gt;Now, if anybody asks me if I can make one for them too, my honest answer
will be that it would be quicker and cheaper for them to go down to
Mitre 10. Seriously.
My project cost about $700 on timber alone. By the time you factor in
several days&amp;rsquo; work, consumables and wear on my tools &amp;ndash; and that you can get a very similar
(albeit mass-produced) table for $1000-1500 &amp;ndash; who&amp;rsquo;d ever buy artisan
woodwork? Nobody, unless they wanted a unique piece or wanted the
&lt;em&gt;journey&lt;/em&gt; of making it themselves.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.flickr.com/photos/crazyscot/albums/72157716279389127&#34;&gt;The full photo story&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&#34;inline100&#34;&gt;&lt;a href=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-1.jpg&#34;&gt;&lt;img src=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-1.jpg&#34; /&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;h4&gt;Precisely mitred edge pieces&lt;/h4&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure class=&#34;inline100&#34;&gt;&lt;a href=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-2.jpg&#34;&gt;&lt;img src=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-2.jpg&#34; /&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;h4&gt;The top takes shape&lt;/h4&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure class=&#34;inline100&#34;&gt;&lt;a href=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-3.jpg&#34;&gt;&lt;img src=&#34;https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-3.jpg&#34; /&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;h4&gt;Leg assembly&lt;/h4&gt;&lt;/figcaption&gt;&lt;/figure&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/things/patio-table-finished.jpg" type="image" /></item>
    
    <item>
      <title>Lightroom plugin for Tangent control surfaces</title>
      <link>https://www.chiark.greenend.org.uk/~ryounger/2019/12/lightroom-plugin-for-tangent-control-surfaces/</link>
      <pubDate>Sun, 15 Dec 2019 21:31:00 +1300</pubDate>
      
      <guid>https://www.chiark.greenend.org.uk/~ryounger/2019/12/lightroom-plugin-for-tangent-control-surfaces/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s all about speed. If you have a lot of interactive colour correction
to do, it&amp;rsquo;s so much faster to use a hardware panel
than it is to use the mouse.&lt;/p&gt;
&lt;p&gt;Professional film and video colour graders use hardware control surfaces
all the time. Broadcast (video) engineers do something very similar with
their CCUs.&lt;/p&gt;
&lt;p&gt;This is a plugin for Adobe Lightroom Classic, which draws heavily on a
similar plugin called MIDI2LR but for MIDI controllers.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;You can get it from &lt;a href=&#34;https://github.com/crazyscot/tangent-lightroom&#34;&gt;tangent-lightroom on
Github&lt;/a&gt;.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update: This project is obsolete!&lt;/strong&gt; On 1 Apr 2021, Tangent released their own official plugin for Lightroom. It&amp;rsquo;s included in version 1.7.0 of the Tangent bundle, whish you can get from &lt;a href=&#34;https://www.tangentwave.co.uk/tangent-support/&#34;&gt;Tangent support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The repository lives on, as a place for me to keep my mapping for the Wave:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/crazyscot/tangent-lightroom/tree/tangent-map&#34;&gt;tangent-map branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://raw.githubusercontent.com/crazyscot/tangent-lightroom/tangent-map/RY-Lightroom.xml&#34;&gt;Direct download&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <enclosure url="https://www.chiark.greenend.org.uk/~ryounger/images/tangent-lightroom-panel.jpg" type="image" /></item>
    
  </channel>
</rss>