<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>CodeBlueprint · Cloud/Edge, robotics, distributed platforms</title><description>CodeBlueprint&apos;s notes on cloud and edge systems, robotics and IoT, distributed platforms and security — and the C++, Rust, Go and build tooling underneath.</description><link>https://sysdev.me/</link><language>en</language><atom:link href="https://sysdev.me/rss.xml" rel="self" type="application/rss+xml"/><item><title>Cross-Compiling C++ with Bazel: Platforms, Toolchains and the Exec/Target Split</title><link>https://sysdev.me/2026/10/19/bazel-cross-compilation/</link><guid isPermaLink="true">https://sysdev.me/2026/10/19/bazel-cross-compilation/</guid><description>As far as I&apos;m concerned, Bazel is the best build tool you can pick today, as long as it supports your language.</description><pubDate>Mon, 19 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/bazel-cross-compilation/alamedin-camp.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/bazel-cross-compilation/alamedin-camp.jpg&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Alamedin gorge. The tent goes where the ground is flat, not where the view is. Nothing required those to be the same place.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As far as I’m concerned, Bazel is the best build tool you can pick today, as long as it supports your language. A real dependency graph, hermetic toolchains, a remote cache that actually works, one command to build and test everything. And yet every time I bring it up with embedded people, the first question is the same: fine, but what about cross-compilation?&lt;/p&gt;
&lt;p&gt;Fair question. Most of us grew up with a vendor SDK that hijacks the shell through an &lt;code&gt;environment-setup&lt;/code&gt; script, or with the “just build it on the board” approach because it looks simpler. It isn’t simpler. A Raspberry Pi 5 is a decent target and a lousy build machine, and nothing about an ARM CPU requires the compiler to run on it.&lt;/p&gt;
&lt;p&gt;So here’s a small and fairly well-known example, done the way I think it should be done: C++23 built on an x86_64 Linux host for an aarch64 Linux device. The device is a Pi 5 because that’s the board sitting on my desk. Swap in any ARM64 Linux board and the mechanics stay the same. The code lives in pi-bazel&lt;sup&gt;&lt;a href=&quot;#user-content-fn-repo&quot; id=&quot;user-content-fnref-repo&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, and the whole cross setup is a 137-line &lt;code&gt;MODULE.bazel&lt;/code&gt;, a 23-line &lt;code&gt;.bazelrc&lt;/code&gt; and two &lt;code&gt;platform()&lt;/code&gt; declarations. No hand-written &lt;code&gt;cc_toolchain_config&lt;/code&gt;, no &lt;code&gt;select()&lt;/code&gt; anywhere in the tree, no &lt;code&gt;#ifdef&lt;/code&gt; on architecture in the sources.&lt;/p&gt;
&lt;h2 id=&quot;platforms-instead-of---cpu&quot;&gt;Platforms Instead of &lt;code&gt;--cpu&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If you touched Bazel C++ cross builds a few years ago, you remember &lt;code&gt;--cpu=aarch64&lt;/code&gt; plus &lt;code&gt;--crosstool_top&lt;/code&gt;. The CPU name was a lookup key into a crosstool file, the host compiler was a second lookup through &lt;code&gt;--host_cpu&lt;/code&gt;, and anything that wasn’t a C++ compiler had no mechanism at all. Forget it. Bazel 9 still accepts &lt;code&gt;--cpu&lt;/code&gt;, but nothing in this repo reads it.&lt;/p&gt;
&lt;p&gt;The current model is constraint matching&lt;sup&gt;&lt;a href=&quot;#user-content-fn-platforms&quot; id=&quot;user-content-fnref-platforms&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. A &lt;em&gt;platform&lt;/em&gt; is a set of constraint values (CPU, OS, whatever axis you define). A &lt;em&gt;toolchain&lt;/em&gt; declares two sets of constraints: &lt;code&gt;exec_compatible_with&lt;/code&gt; says where the tool runs, &lt;code&gt;target_compatible_with&lt;/code&gt; says what it produces output for. Every build has one target platform, set with &lt;code&gt;--platforms&lt;/code&gt;, and a list of execution platforms where actions may run. For each toolchain type a rule needs, Bazel walks the registered toolchains and takes the first one whose target side fits the target platform and whose exec side fits some available execution platform&lt;sup&gt;&lt;a href=&quot;#user-content-fn-toolchains&quot; id=&quot;user-content-fnref-toolchains&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And that’s the whole trick. Cross-compilation stops being a special mode: it’s simply the case where the matched execution platform and the target platform differ.&lt;/p&gt;
&lt;p&gt;Here’s every platform the project defines:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/bazel/platforms/BUILD.bazel#L1-L17&quot;&gt;&lt;code&gt;bazel/platforms/BUILD.bazel:1-17&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;package(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default_visibility&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;//visibility:public&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;platform(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    constraint_values&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@platforms//cpu:x86_64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@platforms//os:linux&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;platform(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;pi5&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    constraint_values&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@platforms//cpu:aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@platforms//os:linux&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two constraints each, all from &lt;code&gt;@platforms&lt;/code&gt;. You might expect a Cortex-A76 constraint on &lt;code&gt;pi5&lt;/code&gt;, but &lt;code&gt;@platforms//cpu&lt;/code&gt; only knows &lt;code&gt;cortex-r52&lt;/code&gt; and &lt;code&gt;cortex-r82&lt;/code&gt; among the &lt;code&gt;cortex-*&lt;/code&gt; values, so A76 tuning ends up as a compiler flag, attached to the Pi’s toolchain in &lt;code&gt;MODULE.bazel&lt;/code&gt; (next section). A project-local &lt;code&gt;constraint_setting&lt;/code&gt; for the microarchitecture would earn its place the day two aarch64 toolchains have to be told apart during resolution. With one target board, it’s ceremony.&lt;/p&gt;
&lt;p&gt;Strictly speaking, &lt;code&gt;:host&lt;/code&gt; is optional, since Bazel’s default host platform already carries &lt;code&gt;x86_64&lt;/code&gt; and &lt;code&gt;linux&lt;/code&gt;. I keep it anyway because it gives CI a label to pin and gives the execution platform list a first entry I control (more on that later).&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/bazel-cross-compilation/toolchain-resolution.svg&quot; width=&quot;640&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Note the bottom box. Both builds run every action on the same x86_64 machine; only the target side of the match moves.&lt;/p&gt;
&lt;h2 id=&quot;one-config-per-target&quot;&gt;One Config per Target&lt;/h2&gt;
&lt;p&gt;Picking the target is a named config in &lt;code&gt;.bazelrc&lt;/code&gt;. &lt;code&gt;bazel build --config=host //...&lt;/code&gt; is the host build, for the workstation; &lt;code&gt;bazel build --config=pi //...&lt;/code&gt; is the Pi build. Each config is nothing but a platform choice, and this is every per-target line in the file:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/.bazelrc#L14-L16&quot;&gt;&lt;code&gt;.bazelrc:14-16&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;build:host --platforms=//bazel/platforms:host&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;build:pi --platforms=//bazel/platforms:pi5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole &lt;code&gt;.bazelrc&lt;/code&gt; is 23 lines, and the rest is lockfile strictness, three output-directory flags covered near the end, a CI config, test output settings and a &lt;code&gt;user.bazelrc&lt;/code&gt; import. The A76 tuning used to live here too, as two &lt;code&gt;build:pi --copt&lt;/code&gt; lines. That had a real gap: the tuning was tied to a config name, not to the target, so a plain &lt;code&gt;bazel build --platforms=//bazel/platforms:pi5&lt;/code&gt; produced an untuned binary without a word of complaint. The flags now sit on the toolchain itself, keyed to the Pi’s target pair:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/MODULE.bazel#L77-L84&quot;&gt;&lt;code&gt;MODULE.bazel:77-84&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # @platforms has no cortex-a76 constraint value, so A76 tuning can only be a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # compiler flag rather than a platform property. Keyed to the target pair&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # rather than set under build:pi, so the tuning follows the toolchain: a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # plain --platforms=//bazel/platforms:pi5 build gets it too.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    extra_compile_flags &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;linux-aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-mcpu=cortex-a76&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-mtune=cortex-a76&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]},&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;aquery&lt;/code&gt; on that bare &lt;code&gt;--platforms&lt;/code&gt; build found zero tuning flags before the move and two after. Keying by pair also keeps the flags out of everything else. The host compile action carries no &lt;code&gt;cortex-a76&lt;/code&gt; flag, and neither will a tool that runs during the build (a code generator, say), because those get built for the exec platform, which is &lt;code&gt;x86_64-linux&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There’s no &lt;code&gt;--extra_toolchains&lt;/code&gt; line, and that’s worth a detour, because an earlier version of this repo had one. The host toolchain was registered globally with &lt;code&gt;register_toolchains(&quot;@llvm_host//:all&quot;)&lt;/code&gt; and the cross one was selected by label, on the theory that registering both would leave resolution order to arbitrate between them. The theory was right. The premise was wrong.&lt;/p&gt;
&lt;p&gt;Ask &lt;code&gt;toolchains_llvm&lt;/code&gt; what &lt;code&gt;@llvm_pi&lt;/code&gt; actually generates and you get five:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ bazel query &apos;@llvm_pi//:all&apos; | grep cc-toolchain-&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@llvm_pi//:cc-toolchain-aarch64-linux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@llvm_pi//:cc-toolchain-riscv32-none&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@llvm_pi//:cc-toolchain-riscv64-none&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@llvm_pi//:cc-toolchain-x86_64-linux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@llvm_pi//:cc-toolchain-x86_64-none&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s the &lt;code&gt;aarch64-linux&lt;/code&gt; pair, the native &lt;code&gt;x86_64-linux&lt;/code&gt; pair, and three bare-metal pairs that need no sysroot. Now run the same query against the host-only toolchain the repo used to have (&lt;code&gt;git checkout f550049&lt;/code&gt; first, since it no longer exists on &lt;code&gt;main&lt;/code&gt;) and you get exactly those five minus &lt;code&gt;aarch64-linux&lt;/code&gt;. &lt;code&gt;toolchains_llvm&lt;/code&gt; emits a cross pair only when it has a sysroot for it, and the host toolchain had none, so it could never have shadowed the Pi. &lt;code&gt;@llvm_pi&lt;/code&gt; is a strict superset: diff the two generated &lt;code&gt;BUILD&lt;/code&gt; files at that commit and you get 144 added lines, nothing removed and nothing changed.&lt;/p&gt;
&lt;p&gt;Which inverts the fix. Registering both would put two &lt;code&gt;x86_64-linux&lt;/code&gt; candidates in front of every host build, with order picking the winner, so I registered &lt;code&gt;@llvm_pi//:all&lt;/code&gt; alone and deleted the host toolchain entirely:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/MODULE.bazel#L134-L137&quot;&gt;&lt;code&gt;MODULE.bazel:134-137&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Registered globally rather than selected by label under build:pi. Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# arbitrates: aarch64-linux is the only candidate for //bazel/platforms:pi5,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# x86_64-linux the only one for :host.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;register_toolchains(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@llvm_pi//:all&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s the whole mechanism, and it’s why &lt;code&gt;.bazelrc&lt;/code&gt; above has nothing to say about toolchains any more: it picks a platform and the registration does the rest. One registration serves both configs, and the &lt;code&gt;--extra_toolchains&lt;/code&gt; line goes away along with nineteen lines of &lt;code&gt;MODULE.bazel&lt;/code&gt;. I compared the link command lines before and after under both configs: flag for flag identical, down to the sysroot path and the order of the archives. The only thing that changes is which repo’s &lt;code&gt;cc_wrapper.sh&lt;/code&gt; runs, and the two are byte-identical.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;:all&lt;/code&gt; in that line isn’t special syntax. It’s the ordinary “everything in this package” pattern, and &lt;code&gt;@llvm_pi&lt;/code&gt; holds 120 targets: filegroups, the &lt;code&gt;cc_toolchain&lt;/code&gt; rules, wrappers for &lt;code&gt;clangd&lt;/code&gt; and &lt;code&gt;clang-format&lt;/code&gt;, and so on. &lt;code&gt;register_toolchains&lt;/code&gt; silently skips everything that isn’t a &lt;code&gt;toolchain()&lt;/code&gt; and keeps the five that are. I’d rather have that than a hand-written list of labels. A &lt;code&gt;toolchains_llvm&lt;/code&gt; upgrade that adds or renames a target would leave an explicit list quietly incomplete, while &lt;code&gt;:all&lt;/code&gt; just picks up whatever got generated.&lt;/p&gt;
&lt;p&gt;Registering a toolchain doesn’t mean it gets used, though. All five land in the candidate list, and resolution throws out the ones whose constraints don’t fit. &lt;code&gt;--toolchain_resolution_debug&lt;/code&gt; shows it happening (repo prefixes shortened, most rejections cut):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ bazel cquery --config=pi \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    --toolchain_resolution_debug=&apos;@bazel_tools//tools/cpp:toolchain_type&apos; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //src/hello:hello&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ToolchainResolution:   Toolchain @@…llvm_pi//:cc-toolchain-aarch64-linux (resolves to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @@…llvm_pi//:cc-clang-aarch64-linux) is compatible with target platform, searching&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for execution platforms:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ToolchainResolution:   Rejected toolchain @@…llvm_pi//:cc-toolchain-x86_64-linux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  (resolves to @@…llvm_pi//:cc-clang-x86_64-linux) ; mismatching values: x86_64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ToolchainResolution:   Rejected toolchain @@…local_config_cc_toolchains//:cc-toolchain-k8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  (resolves to @@…local_config_cc//:cc-compiler-k8) ; mismatching values: x86_64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every line names two targets because &lt;code&gt;cc-toolchain-*&lt;/code&gt; is the &lt;code&gt;toolchain()&lt;/code&gt; wrapper that gets registered, and &lt;code&gt;cc-clang-*&lt;/code&gt; is the actual &lt;code&gt;cc_toolchain&lt;/code&gt; behind it. The last line isn’t mine at all: &lt;code&gt;rules_cc&lt;/code&gt; (and &lt;code&gt;apple_support&lt;/code&gt;) register their own autodetected toolchains, and resolution walks past those too. If a rerun prints nothing, that’s the analysis cache. The debug output only appears when resolution actually runs.&lt;/p&gt;
&lt;p&gt;There’s one cost. With &lt;code&gt;@llvm_host&lt;/code&gt; gone, host builds come out of &lt;code&gt;@llvm_pi&lt;/code&gt; too, from its &lt;code&gt;x86_64-linux&lt;/code&gt; toolchain. So every setting in the &lt;code&gt;llvm.toolchain(name = &quot;llvm_pi&quot;)&lt;/code&gt; block also applies to host builds, unless it’s keyed to a specific target pair. &lt;code&gt;stdlib&lt;/code&gt; is the one that matters here. It’s keyed to &lt;code&gt;linux-aarch64&lt;/code&gt;, so only Pi builds get &lt;code&gt;libc++&lt;/code&gt;. Change the key to &lt;code&gt;&quot;&quot;&lt;/code&gt; (an easy mistake when the block is literally named &lt;code&gt;llvm_pi&lt;/code&gt;) and host builds quietly switch away from &lt;code&gt;builtin-libc++&lt;/code&gt; as well. A separate host toolchain made that mistake impossible. Now the only guard is a comment above that line in &lt;code&gt;MODULE.bazel&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;where-the-toolchain-comes-from&quot;&gt;Where the Toolchain Comes From&lt;/h2&gt;
&lt;p&gt;Writing a &lt;code&gt;cc_toolchain_config&lt;/code&gt; by hand is a waste of a weekend. &lt;code&gt;toolchains_llvm&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-tllvm&quot; id=&quot;user-content-fnref-tllvm&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; generates the toolchain, its features and its flag sets from a few tags in &lt;code&gt;MODULE.bazel&lt;/code&gt;, so the interesting work is in the parts it can’t decide for you.&lt;/p&gt;
&lt;p&gt;The first one surprised me. An LLVM release tarball is two different things glued together. &lt;code&gt;LLVM-23.1.0-Linux-X64&lt;/code&gt; carries &lt;code&gt;clang&lt;/code&gt;, &lt;code&gt;clang++&lt;/code&gt; and &lt;code&gt;ld.lld&lt;/code&gt; as x86_64 binaries, which every build needs to &lt;em&gt;run&lt;/em&gt;, plus x86_64 headers and &lt;code&gt;lib/x86_64-unknown-linux-gnu/libc++.a&lt;/code&gt;, which the host build links and the Pi build can’t use. The aarch64 archives only exist in &lt;code&gt;LLVM-23.1.0-Linux-ARM64&lt;/code&gt;, whose binaries can’t execute on my workstation at all. So the Pi toolchain, &lt;code&gt;@llvm_pi&lt;/code&gt;’s &lt;code&gt;aarch64-linux&lt;/code&gt; entry, is assembled from two distributions:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/MODULE.bazel#L74-L115&quot;&gt;&lt;code&gt;MODULE.bazel:74-115&lt;/code&gt;&lt;/a&gt;, with the two flag attributes elided:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;llvm.toolchain(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;llvm_pi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    cxx_standard&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;c++23&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # extra_compile_flags = {...}, extra_link_flags = {...}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    llvm_version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; _LLVM_VERSION&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Keyed to the aarch64 target rather than &quot;&quot; because this toolchain also&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # serves host builds: an empty key would move them off builtin-libc++ too,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # with nothing in a --config=host command line pointing at the cause.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    stdlib&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;linux-aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;libc++&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;llvm.toolchain_root(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;llvm_pi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;@llvm_x64//:BUILD&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;llvm.target_toolchain_root(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;llvm_pi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;@llvm_arm64//:BUILD&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    targets&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;linux-aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;llvm.sysroot(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;llvm_pi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;@pi_sysroot//sysroot&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    targets&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;linux-aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;toolchain_root&lt;/code&gt; is the distribution whose binaries run. &lt;code&gt;target_toolchain_root&lt;/code&gt; is the one you compile against and link with. Both tags read only the package path out of the label, which explains the odd-looking &lt;code&gt;@llvm_x64//:BUILD&lt;/code&gt;. (Both trees are fetched by sha256 through a small repository rule of mine. It exists for a linker problem with my workstation’s system libraries that has nothing to do with cross-compilation, so I’ll skip it here.)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@llvm_arm64&lt;/code&gt; supplies more than the aarch64 runtime archives (&lt;code&gt;libc++.a&lt;/code&gt;, &lt;code&gt;libc++abi.a&lt;/code&gt;, &lt;code&gt;libunwind.a&lt;/code&gt;, compiler-rt). Run &lt;code&gt;aquery&lt;/code&gt; on a Pi compile action and you’ll see &lt;code&gt;-resource-dir&lt;/code&gt; pointing at &lt;code&gt;@llvm_arm64/lib/clang/23&lt;/code&gt;, &lt;code&gt;-nostdinc++&lt;/code&gt;, and two &lt;code&gt;-cxx-isystem&lt;/code&gt; paths into &lt;code&gt;@llvm_arm64/include&lt;/code&gt;. So the target-side C++ headers and the clang resource directory come from the ARM64 tree too. For the Pi build, the X64 tree contributes executables and nothing else. The host build gets everything from the X64 tree, because &lt;code&gt;x86_64-linux&lt;/code&gt; has no &lt;code&gt;target_toolchain_root&lt;/code&gt; and falls back to &lt;code&gt;toolchain_root&lt;/code&gt;.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/bazel-cross-compilation/toolchain-sources.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;One thing I didn’t expect: pointing &lt;code&gt;toolchain_root&lt;/code&gt; at a tree you’ve already fetched also skips &lt;code&gt;toolchains_llvm&lt;/code&gt;’s own LLVM download. The generated &lt;code&gt;@llvm_pi&lt;/code&gt; repo is 39 KB of BUILD files and a &lt;code&gt;cc_wrapper.sh&lt;/code&gt;, not a third copy of a 12 GB distribution.&lt;/p&gt;
&lt;h2 id=&quot;the-sysroot&quot;&gt;The Sysroot&lt;/h2&gt;
&lt;p&gt;The C side (glibc headers, &lt;code&gt;libc.so.6&lt;/code&gt;, &lt;code&gt;libm&lt;/code&gt;, &lt;code&gt;libpthread&lt;/code&gt;) comes from a sysroot. The obvious way to bring one in is &lt;code&gt;http_archive&lt;/code&gt; plus a &lt;code&gt;filegroup&lt;/code&gt; with a &lt;code&gt;glob()&lt;/code&gt;. Don’t. On the sysroot used here, that glob produces a warning naming 6,749 files, and every one of them becomes a separate input symlinked into every compile and link sandbox.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;toolchains_llvm&lt;/code&gt; ships its own &lt;code&gt;sysroot&lt;/code&gt; repository rule, and it declares the extracted tree as a single source directory (&lt;code&gt;srcs = [&quot;.&quot;]&lt;/code&gt;). That’s one input in the action graph and one symlink per sandbox instead of thousands. It also extracts with a hermetic &lt;code&gt;bsdtar&lt;/code&gt; instead of whatever &lt;code&gt;tar&lt;/code&gt; the build machine has.&lt;/p&gt;
&lt;p&gt;For now the sysroot itself is borrowed. Chromium builds Debian sysroots for its own cross-compiles and publishes them as immutable tarballs, and &lt;code&gt;toolchains_llvm&lt;/code&gt;’s own tests use the same ones, so it was the quickest way to get a working one. Mine is their Debian stretch arm64 image, with glibc 2.24. The Pi runs 2.41 and glibc is forward-compatible in that direction, so the artifacts carry a &lt;em&gt;lower&lt;/em&gt; symbol floor than intended rather than a broken one. A Debian trixie snapshot replaces it next, and the label &lt;code&gt;@pi_sysroot//sysroot&lt;/code&gt; won’t move when that happens.&lt;/p&gt;
&lt;p&gt;This is what reaches the linker. I pulled it with &lt;code&gt;bazel aquery --config=pi &apos;mnemonic(&quot;CppLink&quot;, //src/hello:hello)&apos;&lt;/code&gt; and trimmed the hardening flags:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exec external/toolchains_llvm++llvm+llvm_pi/bin/cc_wrapper.sh \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -o bazel-out/pi5-fastbuild/bin/src/hello/hello \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --target=aarch64-unknown-linux-gnu -fuse-ld=lld \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -resource-dir external/+llvm_distribution+llvm_arm64/lib/clang/23 \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -rtlib=compiler-rt -l:libunwind.a -lpthread -ldl \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  bazel-out/pi5-fastbuild/bin/src/hello/_objs/hello/main.pic.o \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -Wl,--start-lib bazel-out/.../hello_lib/hello.pic.o -Wl,--end-lib \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  --sysroot=external/+sysroot+pi_sysroot/sysroot/ \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -Lexternal/+llvm_distribution+llvm_arm64/lib \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -Lexternal/+llvm_distribution+llvm_arm64/lib/aarch64-unknown-linux-gnu \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -l:libc++.a -l:libc++abi.a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The detail I care about most is that &lt;code&gt;--sysroot&lt;/code&gt; is workspace-relative. Nothing in the command line knows where my checkout lives, so the action key doesn’t either. There’s no shared cache here yet, but once there is, a CI runner and my workstation will compute the same key for this link even though the checkouts live at different paths. An absolute path would have made every machine’s keys unique. (Why that output path says &lt;code&gt;pi5-fastbuild&lt;/code&gt; gets a section near the end.)&lt;/p&gt;
&lt;p&gt;If I had to recommend one Bazel habit for toolchain work, it’s this one: when something looks wrong, read the action with &lt;code&gt;aquery&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-aquery&quot; id=&quot;user-content-fnref-aquery&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt; instead of guessing. Every finding in the next section came out of a command line or an input list.&lt;/p&gt;
&lt;h2 id=&quot;defaults-that-fail-quietly&quot;&gt;Defaults That Fail Quietly&lt;/h2&gt;
&lt;h3 id=&quot;libstdc-through-the-back-door&quot;&gt;libstdc++ through the back door&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;toolchains_llvm&lt;/code&gt; controls the C++ runtime through a &lt;code&gt;stdlib&lt;/code&gt; attribute, and the default is &lt;code&gt;builtin-libc++&lt;/code&gt;: static libc++ and libc++abi, plus compiler-rt and libunwind. Sounds exactly like what I want. But when exec and target architectures differ, the toolchain config rewrites that default:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bazel-contrib/toolchains_llvm/blob/5370e47ae9d668a3a13a2da8818a2b72c907f8e1/toolchain/cc_toolchain_config.bzl#L437-L438&quot;&gt;&lt;code&gt;toolchain/cc_toolchain_config.bzl:437-438&lt;/code&gt;&lt;/a&gt; (toolchains_llvm 1.9.0)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stdlib &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;builtin-libc++&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; is_xcompile &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; is_darwin_exec_and_target:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        stdlib &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;stdc++&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Leave &lt;code&gt;stdlib&lt;/code&gt; alone and the Pi build takes its C++ standard library from the sysroot. With the stretch sysroot this project uses today, you find out immediately, because nothing compiles:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;./src/hello/hello.h:3:10: fatal error: &apos;expected&apos; file not found&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    3 | #include &amp;lt;expected&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      |          ^~~~~~~~~~&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;aquery&lt;/code&gt; shows why. The compile action gets &lt;code&gt;-stdlib=libstdc++&lt;/code&gt; and no &lt;code&gt;-nostdinc++&lt;/code&gt;, so &lt;code&gt;&amp;lt;expected&amp;gt;&lt;/code&gt; is looked up in &lt;code&gt;external/+sysroot+pi_sysroot/sysroot/usr/include/c++/6.3.0&lt;/code&gt;, the libstdc++ headers that came inside the sysroot tarball. The build machine’s own &lt;code&gt;/usr/include&lt;/code&gt; plays no part. Debian stretch ships GCC 6.3, and its libstdc++ has no &lt;code&gt;&amp;lt;expected&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;flat_map&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;print&amp;gt;&lt;/code&gt;. With the &lt;code&gt;libc++&lt;/code&gt; key set, the same action gets &lt;code&gt;-nostdinc++&lt;/code&gt; and two &lt;code&gt;-cxx-isystem&lt;/code&gt; paths, and the headers come from &lt;code&gt;@llvm_arm64/include/c++/v1&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;A failing compile is the lucky outcome. With a sysroot whose libstdc++ is new enough, the build goes green and nothing tells you that the binary now loads &lt;code&gt;libstdc++.so.6&lt;/code&gt; from the Pi at runtime, or that &lt;code&gt;hello_test&lt;/code&gt; on the workstation exercises libc++ while the Pi runs libstdc++. The neighbouring branch, an empty &lt;code&gt;stdlib&lt;/code&gt; on a cross build, at least prints a warning. This one prints nothing.&lt;/p&gt;
&lt;p&gt;The fix is &lt;code&gt;stdlib = {&quot;linux-aarch64&quot;: &quot;libc++&quot;}&lt;/code&gt;, keyed by the target pair. An empty key would apply to every pair, including &lt;code&gt;x86_64-linux&lt;/code&gt;, and drag the host build off &lt;code&gt;builtin-libc++&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;Except the &lt;code&gt;libc++&lt;/code&gt; value isn’t &lt;code&gt;builtin-libc++&lt;/code&gt; minus the cross rewrite. It emits &lt;code&gt;-l:libc++.a -l:libc++abi.a&lt;/code&gt; and nothing more, so the first link died like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ld.lld: error: undefined symbol: __aarch64_ldadd8_acq_rel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ld.lld: error: undefined symbol: __aarch64_ldadd8_relax&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ld.lld: error: undefined symbol: __aarch64_cas8_acq_rel&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those are AArch64’s outline-atomics helpers, small functions the compiler calls for atomic operations. &lt;code&gt;libc++.a&lt;/code&gt; and &lt;code&gt;libc++abi.a&lt;/code&gt; both use them, and they live in compiler-rt, which the &lt;code&gt;libc++&lt;/code&gt; mode never links. It skips the unwinder, pthread and dl as well, all of which &lt;code&gt;builtin-libc++&lt;/code&gt; brings along for free. Four flags on the toolchain, keyed to the Pi’s pair again, put all of that back:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/MODULE.bazel#L85-L95&quot;&gt;&lt;code&gt;MODULE.bazel:85-95&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # The libc++ stdlib branch emits only -l:libc++.a -l:libc++abi.a, unlike&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # builtin-libc++: AArch64 outline-atomics live in compiler-rt, and libunwind,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # pthread and dl are otherwise supplied for free. extra_link_flags appends to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # the toolchain&apos;s own link_flags, so these land before the object files,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # where builtin-libc++ puts them on the host.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    extra_link_flags &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;linux-aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-rtlib=compiler-rt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-l:libunwind.a&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-lpthread&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-ldl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]},&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-rtlib=compiler-rt&lt;/code&gt; brings in the atomics. &lt;code&gt;-l:libunwind.a&lt;/code&gt; names the static archive on purpose: &lt;code&gt;-unwindlib=libunwind&lt;/code&gt; looks equivalent, but it links the shared &lt;code&gt;libunwind.so&lt;/code&gt; and puts a runtime dependency back on the Pi. &lt;code&gt;-lpthread&lt;/code&gt; and &lt;code&gt;-ldl&lt;/code&gt; resolve against the sysroot’s glibc.&lt;/p&gt;
&lt;p&gt;The attribute name matters. &lt;code&gt;toolchains_llvm&lt;/code&gt; has two families of flag attributes, both keyed by target pair. &lt;code&gt;compile_flags&lt;/code&gt;, &lt;code&gt;link_flags&lt;/code&gt; and &lt;code&gt;link_libs&lt;/code&gt; &lt;em&gt;replace&lt;/em&gt; the toolchain’s computed defaults, so using them means restating everything the toolchain already emits. Their &lt;code&gt;extra_*&lt;/code&gt; twins &lt;em&gt;append&lt;/em&gt; after the defaults, which is what you want almost every time.&lt;/p&gt;
&lt;p&gt;Don’t count on upstream to catch a regression here. &lt;code&gt;toolchains_llvm&lt;/code&gt; 1.9.0’s CI tests every &lt;code&gt;stdlib&lt;/code&gt; value, but only in native builds&lt;sup&gt;&lt;a href=&quot;#user-content-fn-tllvm-ci&quot; id=&quot;user-content-fnref-tllvm-ci&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;. Its one cross-compile test leaves &lt;code&gt;stdlib&lt;/code&gt; unset&lt;sup&gt;&lt;a href=&quot;#user-content-fn-tllvm-xcompile&quot; id=&quot;user-content-fnref-tllvm-xcompile&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;, so it takes the silent libstdc++ path described above. Nothing upstream builds cross with &lt;code&gt;libc++&lt;/code&gt;, so in this repo the ABI check further down is the only guard on that combination.&lt;/p&gt;
&lt;h3 id=&quot;a-file-nobody-declared&quot;&gt;A file nobody declared&lt;/h3&gt;
&lt;p&gt;The second quiet failure looked like a broken include path and wasn’t one:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fatal error: &apos;__config_site&apos; file not found&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-cxx-isystem&lt;/code&gt; path pointed exactly where &lt;code&gt;__config_site&lt;/code&gt; lives in the ARM64 tree. The file simply wasn’t in the sandbox. Each compile action’s inputs come from a generated filegroup:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bazel-contrib/toolchains_llvm/blob/5370e47ae9d668a3a13a2da8818a2b72c907f8e1/toolchain/internal/configure.bzl#L706-L715&quot;&gt;&lt;code&gt;toolchain/internal/configure.bzl:706-715&lt;/code&gt;&lt;/a&gt; (toolchains_llvm 1.9.0)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;filegroup(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;compiler-components-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{suffix}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;:cxx_builtin_include_files-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{suffix}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;:sysroot-components-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{suffix}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{llvm_dist_label_prefix}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;extra_config_site&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{toolchain_root}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:clang&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {extra_compiler_files}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{llvm_dist_label_prefix}&lt;/code&gt; is the &lt;em&gt;exec&lt;/em&gt; distribution. With one distribution serving both sides that’s correct. With X64 as exec and ARM64 as target, it declares the x86_64 &lt;code&gt;__config_site&lt;/code&gt; and nobody declares the aarch64 copy that the include path actually resolves to. The template quietly assumes exec and target are the same tree.&lt;/p&gt;
&lt;p&gt;The fix is one tag:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/MODULE.bazel#L127-L131&quot;&gt;&lt;code&gt;MODULE.bazel:127-131&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;llvm.extra_compiler_files(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;llvm_pi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;@llvm_arm64//:extra_config_site&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    targets&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;linux-aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the pattern to recognise, because any cross setup built from two trees will hit it again: a sandboxed action fails not because a path is wrong, but because a correct path was never declared as an input. The error talks about a missing file. The actual bug is a missing declaration. &lt;code&gt;extra_compiler_files&lt;/code&gt; and its twin &lt;code&gt;extra_linker_files&lt;/code&gt; are the escape hatches, and &lt;code&gt;aquery&lt;/code&gt;’s input list tells you which one you need.&lt;/p&gt;
&lt;h2 id=&quot;an-execution-platform-nothing-runs-on&quot;&gt;An Execution Platform Nothing Runs On&lt;/h2&gt;
&lt;p&gt;This block looks wrong, and most reviewers would recommend deleting it:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/MODULE.bazel#L15-L21&quot;&gt;&lt;code&gt;MODULE.bazel:15-21&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Test-toolchain resolution needs a registered execution platform matching the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# target platform before it consults a target&apos;s exec_compatible_with, so :pi5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# must be registered even though no action ever runs on aarch64 hardware.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;register_execution_platforms(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;//bazel/platforms:host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;//bazel/platforms:pi5&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No action in this project ever executes on aarch64. Remove &lt;code&gt;:pi5&lt;/code&gt; anyway, and &lt;code&gt;bazel test --config=pi //...&lt;/code&gt; fails at analysis:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ERROR: .../bazel/deploy/BUILD.bazel:5:8: While resolving toolchains for target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//bazel/deploy:device_smoke (d9c8cc0): No matching toolchains found for types:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @@bazel_tools//tools/test:default_test_toolchain_type: By default, tests are&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  executed on the first execution platform that matches all constraints specified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  by the target platform. Either register the target platform (or a platform with&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  at least the same constraints) as an execution platform, or override the default&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  behavior by registering a custom toolchain for&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @bazel_tools//tools/test:default_test_toolchain_type.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To its credit, Bazel 9 says exactly what to do. What’s surprising is the reason. A test whose target platform is aarch64 needs an aarch64 execution platform to exist, even when its own &lt;code&gt;exec_compatible_with&lt;/code&gt; sends every action to x86_64. Registering &lt;code&gt;:pi5&lt;/code&gt; satisfies that check, and nothing ever runs on it. The other way out, a custom toolchain for &lt;code&gt;default_test_toolchain_type&lt;/code&gt;, is more machinery for the same result. Restore the line and the suite goes green again.&lt;/p&gt;
&lt;p&gt;Order matters here too. &lt;code&gt;:host&lt;/code&gt; is listed first, so every action that doesn’t constrain its own exec platform lands on the build machine. In a &lt;code&gt;--config=pi&lt;/code&gt; build the compile, link and genrule actions all report &lt;code&gt;Execution platform: //bazel/platforms:host&lt;/code&gt;, which is what you want.&lt;/p&gt;
&lt;h2 id=&quot;tests-that-know-which-side-theyre-on&quot;&gt;Tests That Know Which Side They’re On&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;target_compatible_with&lt;/code&gt; is how one &lt;code&gt;bazel test //...&lt;/code&gt; stays correct under both configs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;&lt;code&gt;target_compatible_with&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--config=host&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--config=pi&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;//src/hello:hello_test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;runs&lt;/td&gt;
&lt;td&gt;skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;//bazel/deploy:cross_artifact_abi_check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;aarch64&lt;/td&gt;
&lt;td&gt;skipped&lt;/td&gt;
&lt;td&gt;runs, no Pi needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;//bazel/deploy:device_smoke&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;aarch64&lt;/td&gt;
&lt;td&gt;skipped&lt;/td&gt;
&lt;td&gt;runs, needs the Pi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Incompatible tests are skipped, not failed, and that cuts both ways. Incompatibility is transitive, &lt;code&gt;data&lt;/code&gt; edges included: a CPU constraint on &lt;code&gt;hello_lib&lt;/code&gt; would make &lt;code&gt;hello&lt;/code&gt; incompatible in the Pi build, &lt;code&gt;device_smoke&lt;/code&gt; with it, and the run would report green with the device test silently skipped. That’s why &lt;code&gt;hello_lib&lt;/code&gt; stays unconstrained, with a comment saying so.&lt;/p&gt;
&lt;p&gt;The two tests in &lt;code&gt;bazel/deploy&lt;/code&gt; check an aarch64 binary from the x86_64 build machine, and Bazel can express that directly:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/bazel/deploy/BUILD.bazel#L41-L56&quot;&gt;&lt;code&gt;bazel/deploy/BUILD.bazel:41-56&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sh_test(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;cross_artifact_abi_check&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;small&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cross_artifact_abi_check.sh&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    args&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;$(rlocationpath @llvm_x64//:readelf)&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;//src/hello&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@llvm_x64//:readelf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    exec_compatible_with&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@platforms//cpu:x86_64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@platforms//os:linux&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    target_compatible_with&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@platforms//cpu:aarch64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    deps&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@rules_shell//shell/runfiles&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;target_compatible_with&lt;/code&gt; puts the test in the Pi build, so &lt;code&gt;data&lt;/code&gt; brings in &lt;code&gt;//src/hello&lt;/code&gt; as the aarch64 binary. &lt;code&gt;exec_compatible_with&lt;/code&gt; keeps the test itself, the &lt;code&gt;cross_artifact_abi_check.sh&lt;/code&gt; script, on the x86_64 build machine. &lt;code&gt;@llvm_x64//:readelf&lt;/code&gt; sits in &lt;code&gt;data&lt;/code&gt; too and survives the aarch64 configuration only because it’s a &lt;code&gt;filegroup&lt;/code&gt; around a prebuilt binary; a &lt;code&gt;cc_binary&lt;/code&gt; in the same slot would come out as aarch64 and fail to start. With that readelf, the script checks the two things all the toolchain work was for: the artifact is AArch64, and its &lt;code&gt;NEEDED&lt;/code&gt; list has &lt;code&gt;libc.so.6&lt;/code&gt; but no &lt;code&gt;libstdc++&lt;/code&gt;, &lt;code&gt;libc++&lt;/code&gt; or &lt;code&gt;libgcc_s&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;device_smoke&lt;/code&gt; has the same shape, plus &lt;code&gt;scp&lt;/code&gt; and &lt;code&gt;ssh&lt;/code&gt; to prove the binary actually runs on the Pi. &lt;code&gt;env_inherit&lt;/code&gt; forwards &lt;code&gt;HOME&lt;/code&gt;, &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; and &lt;code&gt;PI_SSH_HOST&lt;/code&gt; to this one test only, and the &lt;code&gt;external&lt;/code&gt; tag keeps its result out of the cache.&lt;/p&gt;
&lt;h2 id=&quot;one-output-directory-for-two-targets&quot;&gt;One Output Directory for Two Targets&lt;/h2&gt;
&lt;p&gt;Until recently, both configs wrote to the same place. &lt;code&gt;bazel aquery&lt;/code&gt; on the link action reported &lt;code&gt;bazel-out/k8-fastbuild/bin/src/hello/hello&lt;/code&gt; under &lt;code&gt;--config=host&lt;/code&gt; and &lt;code&gt;--config=pi&lt;/code&gt; alike, so &lt;code&gt;bazel-bin/src/hello/hello&lt;/code&gt; was whichever build ran last. I hit it while checking something else: an x86-64 ELF sitting at the path an aarch64 build had produced earlier in the same session. It’s also why the ABI check lives inside the build graph and not as a &lt;code&gt;file bazel-bin/...&lt;/code&gt; line in the README.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;k8&lt;/code&gt; is Bazel’s legacy CPU name for x86_64, and despite the look, no relation to k8s. It’s named after AMD’s K8, the Athlon 64 and Opteron family that shipped the first x86-64 chips, and it came over from Blaze, Google’s internal build system, together with &lt;code&gt;piii&lt;/code&gt; for 32-bit x86. &lt;code&gt;rules_cc&lt;/code&gt; still carries the mapping&lt;sup&gt;&lt;a href=&quot;#user-content-fn-k8&quot; id=&quot;user-content-fnref-k8&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;k8&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; arch &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;amd64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;x86_64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;x64&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;piii&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output directory is named &lt;code&gt;&amp;lt;cpu&amp;gt;-&amp;lt;compilation_mode&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;cpu&amp;gt;&lt;/code&gt; comes from the deprecated &lt;code&gt;--cpu&lt;/code&gt; flag. Nothing here sets it, so Bazel falls back to the host’s legacy name and every build gets &lt;code&gt;k8&lt;/code&gt;, the Pi build included. That’s also why &lt;code&gt;bazel config&lt;/code&gt; shows &lt;code&gt;cpu: k8&lt;/code&gt; for the Pi configuration right next to &lt;code&gt;platforms: //bazel/platforms:pi5&lt;/code&gt;. It looks contradictory, but nothing reads that field, and &lt;code&gt;$(TARGET_CPU)&lt;/code&gt; correctly says &lt;code&gt;aarch64&lt;/code&gt;. The CPU value was never wrong. The target platform just never reached the path.&lt;/p&gt;
&lt;p&gt;Three lines in &lt;code&gt;.bazelrc&lt;/code&gt; fix it:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel/blob/5c4ba20/.bazelrc#L10-L12&quot;&gt;&lt;code&gt;.bazelrc:10-12&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;common --experimental_platform_in_output_dir=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;common --experimental_override_platform_cpu_name=//bazel/platforms:host=host&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;common --experimental_override_platform_cpu_name=//bazel/platforms:pi5=pi5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first puts a platform name in the output path instead of the CPU. The other two pick those names explicitly instead of leaving them to an inference scheme Bazel documents as experimental. Outputs now land in &lt;code&gt;bazel-out/host-fastbuild&lt;/code&gt; and &lt;code&gt;bazel-out/pi5-fastbuild&lt;/code&gt; and coexist on disk. The lines use &lt;code&gt;common&lt;/code&gt; rather than &lt;code&gt;build&lt;/code&gt; so &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;cquery&lt;/code&gt; and &lt;code&gt;aquery&lt;/code&gt; all agree on the paths. Both flags are still experimental, so a future Bazel may rename them.&lt;/p&gt;
&lt;p&gt;Two things stay as they were. &lt;code&gt;bazel-bin&lt;/code&gt; is a single symlink and still follows the last build, so a stable path means asking &lt;code&gt;bazel cquery --output=files&lt;/code&gt;. And switching configs still discards the analysis cache, because &lt;code&gt;--platforms&lt;/code&gt; is a configuration input no matter where the outputs land.&lt;/p&gt;
&lt;h2 id=&quot;next-board-next-steps&quot;&gt;Next Board, Next Steps&lt;/h2&gt;
&lt;p&gt;Moving this to a different aarch64 Linux board is a short edit: a new &lt;code&gt;platform()&lt;/code&gt; (possibly with the same two constraints), different &lt;code&gt;-mcpu&lt;/code&gt;/&lt;code&gt;-mtune&lt;/code&gt; in &lt;code&gt;extra_compile_flags&lt;/code&gt;, a different sysroot. A different architecture, say armv7 or riscv64, adds a matching &lt;code&gt;target_toolchain_root&lt;/code&gt; distribution and a new &lt;code&gt;stdlib&lt;/code&gt; key. &lt;code&gt;src/&lt;/code&gt; doesn’t change in either case, which was the point of keeping every per-target difference in the toolchain declaration.&lt;/p&gt;
&lt;p&gt;For this project, the next step is replacing the stretch sysroot with a Debian trixie snapshot that matches the Pi’s actual glibc. After that, GStreamer and friends go in through &lt;code&gt;third_party/&lt;/code&gt; as C libraries. That last part matters more than it sounds: the static libc++ decision only holds as long as no C++ library from the sysroot ever lands on the link line. Then a remote cache, because &lt;code&gt;~/.cache/bazel&lt;/code&gt; on my workstation is already at 62 GB.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-repo&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/pi-bazel&quot;&gt;astavonin/pi-bazel on GitHub.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-repo&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-platforms&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://bazel.build/extending/platforms&quot;&gt;Platforms. Bazel documentation.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-platforms&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-toolchains&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://bazel.build/extending/toolchains&quot;&gt;Toolchains: toolchain resolution. Bazel documentation.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-toolchains&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-tllvm&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bazel-contrib/toolchains_llvm/tree/v1.9.0&quot;&gt;bazel-contrib/toolchains_llvm on GitHub, v1.9.0.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-tllvm&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-aquery&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://bazel.build/query/aquery&quot;&gt;Action Graph Query (aquery). Bazel documentation.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-aquery&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-tllvm-ci&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bazel-contrib/toolchains_llvm/blob/5370e47ae9d668a3a13a2da8818a2b72c907f8e1/.github/workflows/tests.yml#L77-L150&quot;&gt;toolchains_llvm 1.9.0, &lt;code&gt;.github/workflows/tests.yml&lt;/code&gt;: &lt;code&gt;external_test&lt;/code&gt; and &lt;code&gt;xcompile_test&lt;/code&gt;.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-tllvm-ci&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-tllvm-xcompile&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bazel-contrib/toolchains_llvm/blob/5370e47ae9d668a3a13a2da8818a2b72c907f8e1/tests/MODULE.bazel#L220-L229&quot;&gt;toolchains_llvm 1.9.0, &lt;code&gt;tests/MODULE.bazel&lt;/code&gt;: &lt;code&gt;llvm_toolchain_with_sysroot&lt;/code&gt;.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-tllvm-xcompile&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-k8&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bazelbuild/rules_cc/blob/21e14308c2afc7691f43295acc9852d9a6844f04/cc/private/toolchain/lib_cc_configure.bzl#L260&quot;&gt;rules_cc 0.2.22, &lt;code&gt;cc/private/toolchain/lib_cc_configure.bzl&lt;/code&gt;.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-k8&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 8&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>C++</category><category>Bazel</category><category>Tooling</category><category>Raspberry Pi</category></item><item><title>Context enrichment for multi-agent code review</title><link>https://sysdev.me/2026/09/29/context-enrichment-for-multi-agent-code-review/</link><guid isPermaLink="true">https://sysdev.me/2026/09/29/context-enrichment-for-multi-agent-code-review/</guid><description>When a model gets something wrong, we reach for the same words: it hallucinated, it ignored the instructions, it isn&apos;t smart enough yet. Sometimes that&apos;s true.</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/context-enrichment/kok-moynok-lake.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/context-enrichment/kok-moynok-lake.jpg&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Kok-Moinok lake, eleven kilometres and 1,100 metres above the trailhead. No preview on the way up: you get the whole thing at the end or nothing.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When a model gets something wrong, we reach for the same words: it hallucinated, it ignored the instructions, it isn’t smart enough yet. Sometimes that’s true. More often, in my experience, the model did a decent job with what it was given, and what it was given was thin.&lt;/p&gt;
&lt;p&gt;Think about what makes a developer good on a project they’ve been on for a while. It’s rarely typing speed. It’s the context they carry around: which approaches were tried and dropped, which subsystem already solves a similar problem, and above all why each decision was made. None of that is in the diff. It lives in their heads, in the docs, in old tickets, in a Slack thread nobody bookmarked.&lt;/p&gt;
&lt;p&gt;A model can use that context too, to a degree, if we hand it over. The question is how: which inputs, in what shape, and how much of them.&lt;/p&gt;
&lt;p&gt;Here’s the review that made me take that question seriously.&lt;/p&gt;
&lt;h2 id=&quot;two-reviews-one-document&quot;&gt;Two reviews, one document&lt;/h2&gt;
&lt;p&gt;A few weeks ago a design MR proposed a request/mailbox protocol for feature control. My review setup went through it (three Claude reviewers and a Codex cross-model pass, plus adversarial verification for anything only one agent raised&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;) while I read the same document myself.&lt;/p&gt;
&lt;p&gt;The panel returned 35 findings. Claim atomicity, a deny list that fails open, stale sessions, a camera daemon with no idle mode. Every one of them was correct.&lt;/p&gt;
&lt;p&gt;I returned six Criticals, and none of them was on the panel’s list.&lt;/p&gt;
&lt;p&gt;One pair shows the gap. The panel found that the camera daemon has no idle mode, so “idle until requested” can’t be implemented: add an idle mode. I found that “idle until requested” is state control, which this mechanism shouldn’t touch at all: delete the requirement. Same line of the design, opposite fixes.&lt;/p&gt;
&lt;p&gt;Fixing all 35 would have produced a self-consistent design of the wrong system. I posted the six and held the rest back, since most of them die once the frame changes.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/context-enrichment/frame-vs-findings.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;&lt;em&gt;35 findings inside the frame, 6 Criticals aimed at the frame itself. The bold pair is the same line of the design.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-i-had-that-they-didnt&quot;&gt;What I had that they didn’t&lt;/h2&gt;
&lt;p&gt;Look at where my six came from.&lt;/p&gt;
&lt;p&gt;Two of them pointed at work the roadmap already owned: a milestone planning the dependency graph that multi-hop requests need, and an epic scoping the audit that dynamic stopping depends on. One pointed at OTA, which already splits config ownership the way this design was trying to reinvent. And I’d already told the team in Slack that this could be simple, and that we already do something simpler. No reviewer prompt was ever going to include that thread.&lt;/p&gt;
&lt;p&gt;The panel got the document, the diff and the code, with no browsing allowed. Not the repo’s docs, where almost every decision on this project is written down. A reviewer can’t cite what it was never given.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/context-enrichment/what-i-had.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Missing inputs aren’t the only cause. Role bias works against premise attacks too, and so does the way consensus treats a lone dissent. Those get their own post. This one is about the inputs.&lt;/p&gt;
&lt;h2 id=&quot;so-give-them-more-context&quot;&gt;So give them more context&lt;/h2&gt;
&lt;p&gt;The fix follows from that picture: hand every reviewer the inputs my Criticals came from, or as many of them as exist in writing. On this project that’s most of them. Since mid-September the MR review gives every agent three more inputs.&lt;/p&gt;
&lt;p&gt;The first, and the one that matters most, is a reference table over those docs. &lt;a href=&quot;https://github.com/astavonin/projctl&quot;&gt;projctl&lt;/a&gt;, my open-source CLI for this workflow, has a &lt;code&gt;search docs&lt;/code&gt; command: offline BM25 over the repo’s docs root and its &lt;code&gt;planning/&lt;/code&gt; tree, with every matched Markdown section sorted into a tier such as &lt;code&gt;docs&lt;/code&gt;, &lt;code&gt;roadmap&lt;/code&gt;, &lt;code&gt;failure&lt;/code&gt;, &lt;code&gt;alternative&lt;/code&gt; or &lt;code&gt;constraint&lt;/code&gt;. The top 40 go into the prompt as rows of score, tier, repo, path and heading, with no body text at all.&lt;/p&gt;
&lt;p&gt;A row costs a line and a body costs a page, so reviewers get the rows and open the bodies they want.&lt;/p&gt;
&lt;p&gt;The second is the ticket. On a bug-fix MR the same week, the review command called &lt;code&gt;projctl load issue&lt;/code&gt; to find the epic folder, kept that one line and dropped the description, the one part that explained the change. An intentional change came back as a defect. Now the description travels verbatim from the same call.&lt;/p&gt;
&lt;p&gt;The third comes from the same bug-fix MR, where one finding rested on a 3 MiB log record that exists only in a test fixture. The instruction is one sentence: before judging whether something can happen, find what registers the changed code to run, what launches it and what CI selects. It names no paths, because those differ per repo.&lt;/p&gt;
&lt;p&gt;Codex gets all three through its review-request document, since that’s the only thing it reads.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/context-enrichment/three-inputs.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;&lt;em&gt;Everything that was written down now reaches every prompt. The Slack thread still doesn’t.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Two details matter as much as the search itself. The table opens with its query, its flag state and &lt;code&gt;N of M shown&lt;/code&gt;, so a table cut at 40 rows never reads as full coverage. And the prompt frames everything as inputs, not gates:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/genai-automations/blob/5d7ac1c/platforms/claude/commands/review-mr.md#L121&quot;&gt;&lt;code&gt;platforms/claude/commands/review-mr.md:121&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a hit in the &lt;code&gt;failure&lt;/code&gt; or &lt;code&gt;alternative&lt;/code&gt; tier is prior art to weigh, not a directive, and a reviewer handed unframed prior art treats it as authority and stops reasoning instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Measured against my six Criticals, that covers most of the ground. The two roadmap ones now have a tier of their own, and precedent like OTA arrives through the docs. The Slack thread stays out, and so does anything else nobody wrote down.&lt;/p&gt;
&lt;h2 id=&quot;three-agents-one-opinion&quot;&gt;Three agents, one opinion&lt;/h2&gt;
&lt;p&gt;My consensus protocol has a shortcut. A finding raised by only one agent goes to adversarial verification, where another agent tries to refute it. A finding raised by two or more skips that step, on the theory that independent readers agreeing is evidence.&lt;/p&gt;
&lt;p&gt;The readers aren’t independent, though. Every agent gets the same inputs, so whatever is missing from them, all three miss together, and their agreement waves the mistake past the one step built to catch it.&lt;/p&gt;
&lt;p&gt;On the bug-fix MR, four findings were coherent against the diff and false against the deployment. Two questions from me killed them: “is it real or theoretical?” and “check the ticket description and the MR text”. Every one of the four had been raised by more than one agent, so none of them ever reached verification.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/context-enrichment/consensus-shortcut.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;&lt;em&gt;Three agents, one input, one opinion counted three times.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Richer inputs shrink what’s missing, but every agent still gets the same richer set. So agreement should count as a prior at most: a finding that claims something can happen at runtime needs verifying however many agents raised it.&lt;/p&gt;
&lt;h2 id=&quot;reading-the-proposal-last&quot;&gt;Reading the proposal last&lt;/h2&gt;
&lt;p&gt;Inputs are the cheap fix, and they leave the order untouched: every reviewer still reads the proposal first, and whatever it reads first becomes its frame.&lt;/p&gt;
&lt;p&gt;Next is a counter-designer: an agent that gets only the problem statement, the roadmap and an instruction to diff against the nearest in-repo precedent, writes the minimal design it would build, and only then reads the proposal. The calibration set already exists: the design MR from the top of this post. If it finds even one of my six Criticals without me in the loop, that’s the next post.&lt;/p&gt;
&lt;h2 id=&quot;code-paths-touched&quot;&gt;Code paths touched&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/astavonin/projctl/blob/0138e50/projctl/handlers/docs_search.py&quot;&gt;&lt;code&gt;projctl/handlers/docs_search.py&lt;/code&gt;&lt;/a&gt;: the BM25 search and the reference table it renders&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/astavonin/genai-automations/blob/5d7ac1c/platforms/claude/commands/review-mr.md#L87-L98&quot;&gt;&lt;code&gt;platforms/claude/commands/review-mr.md&lt;/code&gt;, Step 3b&lt;/a&gt;: the three inputs every MR reviewer now gets&lt;/li&gt;
&lt;/ul&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/02/19/making-genai-code-review-actually-useful/&quot;&gt;Making GenAI code review actually useful. sysdev.me, February 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>AI</category><category>Tooling</category><category>Processes</category></item><item><title>I cancelled my JetBrains subscription</title><link>https://sysdev.me/2026/09/07/i-cancelled-my-jetbrains-subscription/</link><guid isPermaLink="true">https://sysdev.me/2026/09/07/i-cancelled-my-jetbrains-subscription/</guid><description>Today I cancelled my JetBrains subscription.</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/cancelling-jetbrains/sulutor-lakes.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/cancelling-jetbrains/sulutor-lakes.jpg&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Sulutor Lakes. The only gate up here is the weather, and it&apos;s the only one that actually gets enforced.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Today I cancelled my JetBrains subscription. Cancelling subscriptions is a popular trend right now, except the ones people announce dropping are Claude, ChatGPT, whichever model annoyed them this week. I went the other way. The models stay. The IDE goes. For a line item on a credit card statement it feels more symbolic than it should.&lt;/p&gt;
&lt;p&gt;I have always been a Vim person. That never changed, and it isn’t changing now. But I have been paying JetBrains since 2012, because for a long time Vim lost on two things. Navigation was the real gap: before language servers you had ctags, which is a text index pretending to understand code. No types, no scopes, five candidates for every common name. The other gap was visual. An IDE highlights faster and shows more, errors and types painted onto the code before you ask for them. Language servers closed the first gap. The second one JetBrains still wins, and the rest of this post is about why that no longer buys anything.&lt;/p&gt;
&lt;p&gt;Somewhere along the way the reason changed. The products stopped being the thing I was buying and started being the thing I was supporting. If you want a good product to keep existing, the most honest thing you can do is pay for it, so I kept paying. Fourteen years of that.&lt;/p&gt;
&lt;h2 id=&quot;what-is-actually-on-my-screen-in-2026&quot;&gt;What is actually on my screen in 2026&lt;/h2&gt;
&lt;p&gt;tmux for splits, Vim in most of them. Claude Code and Codex for writing code. Typora for reading and writing documents. Sublime Text and Sublime Merge for the cases where Vim is inconvenient, which in practice means big merge requests with a lot of changes, where I want a fast visual diff and nothing else.&lt;/p&gt;
&lt;p&gt;There is no IDE in that list, and nothing lighter took its slot. The function it performed left my hands entirely.&lt;/p&gt;
&lt;h2 id=&quot;the-ide-is-very-good-at-something-i-no-longer-do&quot;&gt;The IDE is very good at something I no longer do&lt;/h2&gt;
&lt;p&gt;Completion, refactoring, jump-to-definition, inline inspections: the whole apparatus exists to make one person produce and navigate code by hand, quickly. It’s excellent apparatus. Thirty years of work went into it and it shows.&lt;/p&gt;
&lt;p&gt;I don’t do that anymore. Writing code by hand in 2026 is an inefficient use of a senior engineer’s day. Less politely: it’s stupid. My time goes into deciding what should be built, putting a fence around it, and checking what came back against the fence. None of that happens in a buffer.&lt;/p&gt;
&lt;p&gt;Nothing is wrong with JetBrains. Their tools are as good as they ever were. The category is what expired.&lt;/p&gt;
&lt;p&gt;The obvious objection is the AI-native editors, Cursor and its relatives, and I don’t think they change the argument. Putting a model next to the buffer gives you a faster buffer. The buffer is not where my day goes. They are still code-centric tools; they just generate the code instead of autocompleting it.&lt;/p&gt;
&lt;h2 id=&quot;what-i-would-pay-for-and-nobody-sells&quot;&gt;What I would pay for and nobody sells&lt;/h2&gt;
&lt;p&gt;Here is where my money would go if the product existed.&lt;/p&gt;
&lt;p&gt;My workflow with LLMs is a state machine. Research, design, design review, implementation, code review, verification, and back around. I drew the whole thing out earlier&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, so I won’t repeat it here. What matters is the shape: states, transitions, entry conditions, and gates that a transition is not allowed to skip.&lt;/p&gt;
&lt;p&gt;Nothing on my screen understands any of that. tmux shows panes. Claude Code shows a conversation, a long one. Sublime Merge shows a diff. Three views of three artifacts, and no view of the process that produced them.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/cancelling-jetbrains/have-and-need.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;I want the opposite. Show me which state I’m in and which gate is holding. What evidence closed the last one? How much code did the last transition produce, against how much was declared up front? Then the part no tool even attempts: what is not allowed to happen next. And let me tune agent behaviour in one place, visually, instead of editing a scattered tree of markdown files and hoping the model reads the one I meant.&lt;/p&gt;
&lt;p&gt;To be clear about the shape: this is not a CI harness. Orchestrators like Bernstein&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; take a goal in one end and hand back a verified merge at the other, and that’s useful, but I’m inside the loop at every transition, and the tool has to be too. Kiro&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; got closer by putting requirements, design and tasks into the interaction itself, then wrapped it around a code editor and left the gates as conventions.&lt;/p&gt;
&lt;h2 id=&quot;non-determinism-needs-rules-not-vibes&quot;&gt;Non-determinism needs rules, not vibes&lt;/h2&gt;
&lt;p&gt;Underneath all of that is one problem: predictability.&lt;/p&gt;
&lt;p&gt;I wrote last month about a feature I’d sized at roughly 200 lines coming back as 8,391 added&lt;sup&gt;&lt;a href=&quot;#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. Nothing went off the rails. Every guard defended against something real, every review finding was correct, and the total was still absurd. The fix I landed on was making each design option declare what it costs, so the expensive option has to justify itself against a number instead of against adjectives.&lt;/p&gt;
&lt;p&gt;That’s a rule. Right now it’s enforced by being written as prose in a template that I then read carefully, which is the weakest enforcement mechanism available to a human being.&lt;/p&gt;
&lt;p&gt;What I want is somewhere to declare rules like that and have something check them. The volume of code a transition may produce is bounded, and the bound is declared before the transition starts. Verification runs before a state can be exited, no exception for changes somebody labelled trivial. A finding doesn’t close without evidence attached. Implementation doesn’t start without an approved design review. I have every one of those rules today, written down. Nothing checks them.&lt;/p&gt;
&lt;p&gt;This problem has been solved before, for a different flavour of non-determinism. P&lt;sup&gt;&lt;a href=&quot;#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, which started at Microsoft and now lives at AWS, is a language where you model a system as communicating state machines and hand it to a checker that explores message interleavings and failures against the correctness specifications you wrote. It’s not academic furniture: P shipped the USB 3.0 drivers in Windows 8.1 and Windows Phone, and teams across AWS use it on S3, EBS, DynamoDB, MemoryDB, Aurora, EC2 and IoT to reason about whether their designs are correct&lt;sup&gt;&lt;a href=&quot;#user-content-fn-6&quot; id=&quot;user-content-fnref-6&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;. The non-determinism there comes from interleaving and node failure. Mine comes from a sampling distribution. Different source, same shape: a system whose behaviours you cannot enumerate by hand, with properties you still need to hold.&lt;/p&gt;
&lt;p&gt;I am not asking for a model checker over agents. That isn’t a coherent thing to want: the state space is natural language, there is nothing to exhaustively explore. What I want is P’s posture without P’s rigour. An explicit state machine instead of an implied one. Specifications of what must hold, written where a program can read them, and monitors that shout when a run violates one. P calls those spec monitors and runs them twice: against the model during verification, and afterwards against production logs, through a tool called PObserve&lt;sup&gt;&lt;a href=&quot;#user-content-fn-6&quot; id=&quot;user-content-fnref-6-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;. The second half is the part I could use tomorrow.&lt;/p&gt;
&lt;p&gt;Call it a P for LLM workflows. Looser than the original, but with rules that are real.&lt;/p&gt;
&lt;h2 id=&quot;so-im-building-a-bad-version-of-it-myself&quot;&gt;So I’m building a bad version of it myself&lt;/h2&gt;
&lt;p&gt;Since it doesn’t exist, I’ve been inventing it, badly, for about a year. A command per phase. An agent per role. A contract file every model reads on entry. YAML state that survives task switching. Consensus review with an inclusion threshold&lt;sup&gt;&lt;a href=&quot;#user-content-fn-7&quot; id=&quot;user-content-fnref-7&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;, because a single agent’s opinion isn’t worth acting on. Fix loops with iteration caps. It works well enough that I stopped considering the alternative&lt;sup&gt;&lt;a href=&quot;#user-content-fn-8&quot; id=&quot;user-content-fnref-8&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;It is also, honestly described, a state machine implemented as prose and handed to a language model with a polite request that it comply. The rules are suggestions, the gates are conventions, and nothing anywhere in the system can say no.&lt;/p&gt;
&lt;p&gt;I know exactly how weak that is, because I went looking. Three separate files in my configuration asserted something about review history that was flatly untrue, and had been for months&lt;sup&gt;&lt;a href=&quot;#user-content-fn-4&quot; id=&quot;user-content-fnref-4-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. Nothing caught it, because nothing in the system is capable of catching anything. A tool with a real notion of state would have failed on the first transition that depended on it.&lt;/p&gt;
&lt;h2 id=&quot;where-this-lands&quot;&gt;Where this lands&lt;/h2&gt;
&lt;p&gt;The IDE industry spent three decades optimising the production of code, and did an outstanding job. Then the bottleneck moved somewhere else, and the whole category is still standing where the bottleneck used to be.&lt;/p&gt;
&lt;p&gt;I don’t miss the IDE. I miss having somewhere to put the rules. Whoever ships that gets my next fourteen years.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/01/14/development-processes-in-the-genai-era/&quot;&gt;Development processes in the GenAI era. sysdev.me, January 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/sipyourdrink-ltd/bernstein&quot;&gt;Bernstein: the open-source governance layer for AI agents. GitHub, sipyourdrink-ltd/bernstein.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://kiro.dev&quot;&gt;Kiro: an agentic IDE with specs, hooks and steering. AWS.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/08/10/over-engineering-used-to-be-bounded-by-boredom/&quot;&gt;Over-engineering used to be bounded by boredom. sysdev.me, August 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-4-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4-2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/research/blog/p-programming-language-asynchrony/&quot;&gt;P: A programming language designed for asynchrony, fault-tolerance and uncertainty. Microsoft Research, May 19, 2017.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-6&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://p-org.github.io/P/&quot;&gt;The P programming language. Documentation and case studies.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-6&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-6-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6-2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-7&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/02/19/making-genai-code-review-actually-useful/&quot;&gt;Making GenAI code review actually useful. sysdev.me, February 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-7&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-8&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/03/25/vibe-coding-experiment/&quot;&gt;Vibe Coding experiment. sysdev.me, March 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-8&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 8&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>AI</category><category>Tooling</category><category>Processes</category></item><item><title>Over-engineering used to be bounded by boredom</title><link>https://sysdev.me/2026/08/10/over-engineering-used-to-be-bounded-by-boredom/</link><guid isPermaLink="true">https://sysdev.me/2026/08/10/over-engineering-used-to-be-bounded-by-boredom/</guid><description>A feature I&apos;d sized at roughly 200 lines came back as 8,391 added and 692 removed, 3,431 of those lines being tests.</description><pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/over-engineering/belogorka-waterfall.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/over-engineering/belogorka-waterfall.jpg&quot; alt=&quot;Belogorka waterfall in the Sokuluk gorge&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Belogorka waterfall, Sokuluk gorge. Two hours from &quot;the office&quot; and nothing there needs a daemon.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A feature I’d sized at roughly 200 lines came back as 8,391 added and 692 removed, 3,431 of those lines being tests.&lt;/p&gt;
&lt;p&gt;The requirement was one sentence. A CI job needs one of the two physical devices wired to the test rig, so: enumerate what’s attached, pick one that’s online, hand it back at teardown. What shipped was a forked daemon holding a &lt;code&gt;flock&lt;/code&gt; for the lifetime of the job, PID identity verification against &lt;code&gt;/proc&lt;/code&gt; start times, a SIGTERM to SIGKILL termination ladder, orphan sweeps, a readiness pipe, file-descriptor relocation, stdio detach, signal reset, and the tests to keep all of that honest.&lt;/p&gt;
&lt;p&gt;Nothing there is an agent going off the rails. Every review finding was correct, and every guard defends against something that can really happen. The design is defensible option by option, which is the problem: the failure isn’t visible anywhere except in the total. One incident, examined afterward against a census of my own configuration, so n is one.&lt;/p&gt;
&lt;h2 id=&quot;the-column-that-got-deleted&quot;&gt;The column that got deleted&lt;/h2&gt;
&lt;p&gt;Think about picking between two designs in 2019. One is 150 lines with a known weakness. The other is a thousand lines with a background process and doesn’t have that weakness. Part of what you weighed, without noticing you were weighing it, was three weeks of your life, a chunk of that spent on file-descriptor semantics, and a pager going off at 2am when the reaper missed an orphan.&lt;/p&gt;
&lt;p&gt;Nobody wrote that down. No design template has a field for implementation cost, because the field was redundant: the person choosing was the person paying, and the bill arrived on its own.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/over-engineering/feedback-loops.png&quot; width=&quot;800&quot; alt=&quot;The cost of a heavy design used to feed back to whoever chose it; now the loop is broken&quot;&gt;
&lt;p&gt;We weren’t disciplined, to be clear. We shipped mountains of over-built code, usually because something was interesting or looked good on a résumé, and plenty of it is still running. But over-engineering had a ceiling, and the ceiling was how much tedium one person would absorb before going to find the cheaper option. Low ceiling. Effective. It’s now zero.&lt;/p&gt;
&lt;p&gt;The nearest human analogue is a very smart junior who wants everything perfect, and the analogy breaks in the place that matters. Two years on the rig teaches a junior which failures actually arrive, and the perfectionism decays into judgment on its own. There’s no trajectory here. Correct a person once and it sticks; correct this and it holds until the session ends, which is why every correction I’ve made ends up as a file on disk rather than a habit.&lt;/p&gt;
&lt;p&gt;Here’s what that costs in practice. A review finding objected to a device-health step of mine: if we fail to restore a device, assume the device is at fault. The objection was that restoring also fails on a corrupt package, a full runner disk, or a dead adb server, none of which say anything about the board, so a healthy device gets quarantined. All true. And a colleague who’s run that rig for a year answers “sure, twice in eighteen months, both times obvious in the log” and it’s over in four seconds. Frequency is what seasoning consists of. Without it, every true-but-negligible statement arrives at the weight of a real one, and enumerating them costs nothing, so you get the complete taxonomy rather than the three entries a bored human stops at.&lt;/p&gt;
&lt;h2 id=&quot;where-it-shows&quot;&gt;Where it shows&lt;/h2&gt;
&lt;p&gt;The design document enumerated seven options and rejected six. Each rejection is defensible on its own terms. One is load-bearing: a simpler lockfile scheme was thrown out to avoid a stale-holder identity problem, and the winning design then spent 550 lines solving the identical problem after relocating it from a lockfile to a daemon PID. The defect class didn’t go away, it got a bigger budget.&lt;/p&gt;
&lt;p&gt;That section runs 1,200 words across seven options and contains no number of any kind. Not lines, not files, not new-mechanism versus reuse. My template’s version is four lines long: option name, pros, cons, decision.&lt;/p&gt;
&lt;p&gt;So the agent wasn’t choosing badly. It had nothing to compare on. Robustness was on the page in words, cost wasn’t on the page at all, and one option got rejected on a diagnostics-quality argument that appears nowhere in the requirements section, which makes it unanswerable precisely because nothing can check it.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/over-engineering/option-comparison.png&quot; width=&quot;1024&quot; alt=&quot;The same requirement solved two ways: a lockfile and daemon at about 1,020 lines, or sorted serials indexed by concurrency slot at about 30&quot;&gt;
&lt;p&gt;An eighth option never made the page. The runner already runs exactly as many concurrent jobs as there are devices, because the chosen design leans on that for throttling, and it exposes a stable slot index per job.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;serials &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sorted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(online_serials)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;device &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; serials[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(os.environ[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;CI_CONCURRENT_ID&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Distinct devices by construction. No lock, no daemon, no PID identity, nothing to reap. It has a real weakness: unplug a device while the concurrency setting stays put and two jobs index the same serial. Kernel-enforced correctness under fleet drift, against 970 lines and a background process. That’s a one-sentence question with an obvious asker and an obvious answerer, and it was never asked, because the number that makes it obvious was never written down.&lt;/p&gt;
&lt;h2 id=&quot;the-same-thing-at-review-time&quot;&gt;The same thing at review time&lt;/h2&gt;
&lt;p&gt;Seven review passes on the design, producing 84 findings, exiting on “accepted after pass six without re-review.” Round four alone returned 9 high-severity and 14 medium, all new rather than unresolved.&lt;/p&gt;
&lt;p&gt;That doesn’t happen with human reviewers, and not because human reviewers are better calibrated. It doesn’t happen because round four is when somebody says enough. Fatigue is a convergence mechanism. Ugly, unprincipled, effective.&lt;/p&gt;
&lt;p&gt;Tirelessness isn’t the whole of it either. A tireless human with perfect recall would still converge, because they’d remember what they’d already accepted and stop reopening it. My fix loop retains exactly one thing between passes: the iteration counter. The review document is overwritten each pass, findings and severity IDs and all. Three separate files in my configuration claim git history preserves prior reviews, and all three are false, because that directory is gitignored and never had a file tracked. So each pass isn’t continuing the review, it’s drawing a fresh sample against a target that got bigger since last time. I did have stall detection, and it keys on the same root cause appearing &lt;em&gt;unresolved&lt;/em&gt; three passes running, which is exactly the shape this doesn’t have. Growth looks like progress from every angle that predicate can see.&lt;/p&gt;
&lt;p&gt;Round four’s headline was that mutation testing killed seven guards and the tests survived. They asserted things that could not fail. The honest reading is that the primitive had grown too intricate to test truthfully and should be replaced. The loop’s terminal states are approved and changes-requested, so the only expressible reading is insufficient coverage. More tests. Of the 58 entries in my regression ledger, 50 originated as findings raised and fixed inside that loop, which means most of the permanent test surface guards defects that never existed outside the branch.&lt;/p&gt;
&lt;h2 id=&quot;the-obvious-diagnosis-is-wrong&quot;&gt;The obvious diagnosis is wrong&lt;/h2&gt;
&lt;p&gt;Nothing deletes, sure. But that’s a constant, not a variable. We didn’t delete documentation before either, or dead code, or tests guarding behaviour nobody had shipped in years. If the bloat is new, the cause has to be something that changed, and deletion reluctance didn’t change. It decides whether the result is permanent. It doesn’t decide the volume.&lt;/p&gt;
&lt;h2 id=&quot;putting-the-price-tag-back&quot;&gt;Putting the price tag back&lt;/h2&gt;
&lt;p&gt;One fix does most of the work: every option in a design carries two declared lines, what it costs in files and new mechanisms and test cases, and which stated requirement it fails, or “none.” Then the cheap option wins by default and the expensive one is admissible only by naming a requirement the cheap one misses. In my design document that addition measures 31 words as a single table for all seven options.&lt;/p&gt;
&lt;p&gt;It has a hole, and the hole is the same missing signal one level up. If enumerating failure modes is free and every enumerated mode lands in the requirements section formatted like a requirement, the override is always available. No back-filling needed, no bad faith. So a requirement can only override cost if its provenance is checkable by someone other than its author: it traces to the ticket, to a dated incident, or to an external authority like a protocol or a platform limit. Anything produced during analysis and never observed gets recorded and can’t override. The axis isn’t observed versus derived, since a constraint read off a spec is never observed and is genuinely hard. It’s checkable by someone else, or not.&lt;/p&gt;
&lt;p&gt;None of which enforces anything. The requirements and the options get written in the same pass by the same agent, document order isn’t authoring order, and I can’t prove which came first. What the cost line buys is that the trade sits somewhere a human comparing document against ticket has a chance of catching it.&lt;/p&gt;
&lt;p&gt;What I actually miss is cheaper than any of this. I miss knowing, while reading option A, that picking it meant I’d be the one debugging fd inheritance on a Tuesday night. That did more design review than seven passes did.&lt;/p&gt;
</content:encoded><category>AI</category><category>Processes</category><category>Software Architecture</category></item><item><title>Vibe Coding experiment</title><link>https://sysdev.me/2026/03/25/vibe-coding-experiment/</link><guid isPermaLink="true">https://sysdev.me/2026/03/25/vibe-coding-experiment/</guid><description>A bit more interesting data on vibe-coding vs structured assistant-coding.</description><pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/vibe-coding-experiment.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/vibe-coding-experiment.png&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;A bit more interesting data on vibe-coding vs structured assistant-coding.&lt;/p&gt;
&lt;p&gt;Yesterday I ran an experiment. Had a task that will never touch production (local utility), and I’d been reading yet another piece about some engineer cranking out 10 MRs per day with AI. Good enough reason to actually try the dump-everything-at-once approach.&lt;/p&gt;
&lt;p&gt;So I described the full scope to Claude upfront and let it run. Two things stood out. It took longer and produced more frustrating dead ends than my usual flow (detailed design first, then feature-by-feature implementation&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;). And it burned roughly twice the tokens compared to the structured approach. March 24 alone hit $35 after a session involving all three models, versus $10 on a more focused day. If you want to track your own numbers, &lt;code&gt;npx ccusage@latest&lt;/code&gt; does the job.&lt;/p&gt;
&lt;p&gt;I’ve seen the “feed it the whole task at once” question pop up a lot lately. Based on this, my answer is: don’t. At least not yet. The model doesn’t have the context to make the right trade-offs upfront, and you end up steering it through corrections that a proper design phase would have avoided entirely. And if you think vibe-coded output is going into a proper review pipeline, that’s a separate problem&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Vibe-coding makes for great demos. In practice, it’s just paying more for worse results.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/01/14/development-processes-in-the-genai-era/&quot;&gt;Development processes in the GenAI era. sysdev.me, January 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/02/19/making-genai-code-review-actually-useful/&quot;&gt;Making GenAI code review actually useful. sysdev.me, February 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>AI</category><category>Tooling</category><category>Processes</category></item><item><title>Making GenAI code review actually useful</title><link>https://sysdev.me/2026/02/19/making-genai-code-review-actually-useful/</link><guid isPermaLink="true">https://sysdev.me/2026/02/19/making-genai-code-review-actually-useful/</guid><description>I ran a single Claude reviewer agent against an approximately 1000-line C++ change last month (yes, it&apos;s too big for a normal workflow, but we are in the GenAI era and norms are different)—some pretty important video pipeline updates, but nothing exotic.</description><pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/consensus-review.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/consensus-review.jpg&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Alamedin Gorge is one of my favorite places for a short weekend walk.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I ran a single Claude reviewer agent against an approximately 1000-line C++ change last month (yes, it’s too big for a normal workflow, but we are in the GenAI era and norms are different)—some pretty important video pipeline updates, but nothing exotic. The review came back with 14 findings. Three were real. The rest included a phantom race condition in code that runs on a single thread, two style nitpicks elevated to High severity, and a complaint about missing error handling on a function that already returns &lt;code&gt;std::expected&lt;/code&gt;. The signal-to-noise ratio was bad enough that I almost closed the tab.&lt;/p&gt;
&lt;p&gt;This is the dirty secret of GenAI-assisted code review. The models are good enough to spot real issues, sometimes ones a tired human would miss. But they also hallucinate problems with enough confidence to waste your time, and the false positives are not random. They cluster around the same blind spots every run because they come from the same weights, the same training distribution, the same biases baked into one model family.&lt;/p&gt;
&lt;p&gt;I wrote about the false positive problem briefly in my earlier piece on development processes in the GenAI era&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, but I didn’t have a concrete solution at the time. Now I do, and it’s been running in my workflow for a few weeks. The short version: stop asking one agent. Ask three, and only keep what two of them agree on.&lt;/p&gt;
&lt;h2 id=&quot;why-single-agent-review-fails-predictably&quot;&gt;Why single-agent review fails predictably&lt;/h2&gt;
&lt;p&gt;The intuition is that running a model three times should give you roughly the same output three times. That’s wrong, and understanding why is the key to making this work.&lt;/p&gt;
&lt;p&gt;LLM generation is not deterministic in practice (even at temperature zero, batching and floating-point ordering introduce variation). Each independent run samples a different path through the model’s latent space. One run might fixate on thread safety and miss an error-handling gap. Another might catch the error handling gap and hallucinate a performance concern. The overlap between runs is where the model is confident &lt;em&gt;and&lt;/em&gt; correct. The divergence is where it’s guessing.&lt;/p&gt;
&lt;p&gt;Single-agent review gives you no way to distinguish between these. Every finding arrives with the same authoritative tone. A real race condition and a phantom one look identical in the output. So you end up doing the thing GenAI was supposed to save you from: reading every finding carefully, mentally re-deriving whether it’s valid, and throwing away half of them. At that point you’re doing a code review with extra steps.&lt;/p&gt;
&lt;p&gt;The problem compounds over time. Engineers who get burned by false positives stop trusting the review output. They start skimming or skipping it entirely. And then the real findings, the ones the model actually caught, get ignored too. Trust erosion is the failure mode, not any single bad finding.&lt;/p&gt;
&lt;h2 id=&quot;the-23-inclusion-rule&quot;&gt;The 2/3 inclusion rule&lt;/h2&gt;
&lt;p&gt;The fix is conceptually simple. Run three independent reviewer agents against the same change. Each agent gets the same input: the diff, the design document (if one exists), and the review checklist. They don’t see each other’s output. Three separate invocations, three separate generation paths.&lt;/p&gt;
&lt;p&gt;Then apply one rule: a finding only makes it into the final review if at least two out of three agents flagged it independently. Everything else gets dropped.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/consensus-review/inclusion-rule.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;The 2/3 inclusion threshold is the noise filter. It works because the false positives from independent runs are mostly uncorrelated, they come from different hallucination paths, so they rarely overlap. Real issues, on the other hand, tend to be visible from multiple reasoning paths because they’re grounded in actual code problems, not model artifacts.&lt;/p&gt;
&lt;p&gt;In practice, this cuts the finding count roughly in half while keeping nearly all the real issues. The phantom race condition from my cache invalidation review? Only one agent flagged it. Gone. The actual missing bounds check on an index parameter? All three caught it.&lt;/p&gt;
&lt;h2 id=&quot;severity-consensus&quot;&gt;Severity consensus&lt;/h2&gt;
&lt;p&gt;Filtering findings is only half the problem. The other half is severity. One agent calls something Critical, another says Medium, the third says High. Which one do you trust?&lt;/p&gt;
&lt;p&gt;I use a simple rule. If two or three agents agree on severity, that’s the severity. If all three disagree (say Critical, High, and Medium), take the middle one. This isn’t sophisticated. It doesn’t need to be. The goal is to prevent a single agent’s tendency to catastrophize from dominating the output.&lt;/p&gt;
&lt;p&gt;The resolution rules:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent votes&lt;/th&gt;
&lt;th&gt;Severity used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2/2 or 3/3 agree&lt;/td&gt;
&lt;td&gt;Agreed severity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 agree, 1 differs&lt;/td&gt;
&lt;td&gt;Majority severity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All 3 differ&lt;/td&gt;
&lt;td&gt;Middle severity (Critical &amp;gt; High &amp;gt; Medium &amp;gt; Low)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The alternative is letting the highest severity win, which is what most people default to. That’s a mistake. It biases every review toward alarm, which feeds right back into the trust erosion problem. If every finding is Critical, nothing is.&lt;/p&gt;
&lt;h2 id=&quot;cross-model-verification&quot;&gt;Cross-model verification&lt;/h2&gt;
&lt;p&gt;The consensus protocol handles intra-model noise well. But there’s a second failure mode it doesn’t address: systematic blind spots shared across all three runs because they come from the same model family.&lt;/p&gt;
&lt;p&gt;Claude (to pick the obvious example, since that’s what I use for review agents) has specific patterns it over-indexes on and specific patterns it consistently misses. Three Claude runs will share those biases. The 2/3 rule filters random noise, not systematic noise.&lt;/p&gt;
&lt;p&gt;The obvious next step is a cross-model pass. After the Claude consensus, run the same review through a different model family (OpenAI’s Codex, in my case) as an independent verifier. Codex sees the same diff and checklist but produces its own findings without seeing the Claude output. Then cross-aggregate: findings that both families flag get marked as corroborated, Claude-only findings stay as-is, and Codex-only findings get included but labeled clearly so you know they didn’t survive the primary consensus.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/consensus-review/cross-model-pass.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;The corroborated findings are almost always real. A finding that survives the 2/3 Claude consensus and independent Codex verification has been a genuine issue every time I’ve checked. That part works.&lt;/p&gt;
&lt;p&gt;What I’m less sure about is the right protocol around Codex-only findings. Right now I include them with a label and review them manually, which is fine at low volume. But the question I keep circling back to is whether the right move is to feed Codex-only findings back to Claude for re-verification. That creates a tighter loop: Codex catches something Claude missed, Claude gets a second chance to evaluate it with the specific finding in front of it. The risk is that Claude just agrees because the finding is now in the prompt, which would defeat the purpose of independent verification.&lt;/p&gt;
&lt;p&gt;The other option I haven’t tried yet is running multiple Codex instances with the same 2/3 consensus approach, then cross-aggregating two independent consensus outputs. That’s more compute, but it would give the cross-model step the same noise filtering the primary review gets. Right now, a single Codex run is a single opinion, and I already know why single opinions are unreliable (that’s the whole premise of this article).&lt;/p&gt;
&lt;p&gt;I don’t have a settled answer here. The single Codex pass is good enough to catch systematic Claude blind spots, and the corroboration signal is strong. But the handling of Codex-only findings feels like it needs another iteration. If you’re implementing something similar, this is the part I’d expect to change first.&lt;/p&gt;
&lt;h2 id=&quot;what-the-agents-should-not-flag&quot;&gt;What the agents should not flag&lt;/h2&gt;
&lt;p&gt;The protocol is only as good as the instructions each agent receives. One thing I learned early: you have to explicitly tell the reviewer agents what to ignore, not just what to look for.&lt;/p&gt;
&lt;p&gt;Without explicit exclusions, GenAI reviewers love to flag pre-existing issues that have nothing to do with the change under review. They’ll also flag style preferences as if they were correctness problems, and raise speculative bugs that depend on specific inputs without any evidence those inputs are reachable. A senior engineer would never raise these in a review. The model will, confidently, every time.&lt;/p&gt;
&lt;p&gt;My reviewer agents get explicit instructions to skip: pre-existing issues not introduced by the current change, subjective style preferences, potential bugs without clear evidence of reachability, and nitpicks a senior engineer would dismiss. This doesn’t eliminate all noise (that’s what the 2/3 rule is for), but it reduces the volume each individual agent produces, which makes the consensus step cleaner.&lt;/p&gt;
&lt;p&gt;For what it’s worth, I run this full protocol (three Opus reviewer agents, Codex cross-model pass, plus separate agents for research, implementation, and debugging) on a $100/month Claude Pro plan. After a full working week, my usage sits around 20 to 30 percent. The consensus approach sounds expensive when you describe it in the abstract. In practice, if you keep your changesets small and your review inputs focused, it barely dents the budget.&lt;/p&gt;
&lt;h2 id=&quot;what-this-doesnt-solve&quot;&gt;What this doesn’t solve&lt;/h2&gt;
&lt;p&gt;The consensus protocol is a noise filter. It makes GenAI review signal usable. It does not make GenAI review authoritative.&lt;/p&gt;
&lt;p&gt;Formal correctness guarantees still come from static analysis, testing, and runtime instrumentation. The consensus review is a complement to those, not a replacement. It catches the category of issues that are visible in code but not caught by automated tooling: design adherence gaps, missing error handling strategies, observability blind spots, and subtle concurrency concerns that a linter won’t flag.&lt;/p&gt;
&lt;p&gt;And it still requires a human at the end. The engineer reviews the filtered findings and makes the final call. The protocol reduces the volume of findings to something a human can actually process without fatigue, which is the whole point. GenAI review that produces 14 findings is a chore. GenAI review that produces 5 high-confidence findings, with the noise already removed, is a tool.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/2026/01/14/development-processes-in-the-genai-era/&quot;&gt;Development processes in the GenAI era. sysdev.me, January 14, 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>AI</category><category>Tooling</category><category>Processes</category></item><item><title>IPO Season and the Death of Software Engineering (Again)</title><link>https://sysdev.me/2026/02/12/ipo-season-and-the-death-of-software-engineering-again/</link><guid isPermaLink="true">https://sysdev.me/2026/02/12/ipo-season-and-the-death-of-software-engineering-again/</guid><description>I check Blind the way some people check horoscopes: every morning, expecting drama, occasionally finding truth.</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/song-kol-camp.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/song-kol-camp.jpg&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Our Perseids campsite at Song Kol. No cell signal, no Blind, no LinkedIn panic.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I check Blind the way some people check horoscopes: every morning, expecting drama, occasionally finding truth. The Economist I read for the opposite reason, it’s calm, structured, backed by actual data. These two don’t usually agree on much. But over the past couple of months, they’ve converged on the same narrative, and that’s when I start paying attention.&lt;/p&gt;
&lt;h2 id=&quot;december-2025-the-quiet-ipo-prep&quot;&gt;December 2025: the quiet IPO prep&lt;/h2&gt;
&lt;p&gt;Most people missed the December signal. No press conference, no earnings call. Just a Schumpeter column in The Economist&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; noting that SpaceX, OpenAI, and Anthropic are all circling public listings. Anthropic hired Wilson Sonsini, the firm that took Google and LinkedIn public. Valuation tripled in six months to $183 billion&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. Revenue reportedly went up ninefold in a year&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;None of this was loud. But if you’ve watched companies prepare for IPOs before, you recognize the choreography. Legal team locked in. Valuation inflating fast. Private rounds getting so large they start to look absurd. OpenAI’s last was $40 billion, bigger than most IPOs in history.&lt;/p&gt;
&lt;p&gt;The Economist asked the right question: will more capital trump more scrutiny? Public markets are a different audience than a handful of VCs fighting for allocation. They want a story that justifies tenfold valuation growth in two years. That story showed up in January.&lt;/p&gt;
&lt;h2 id=&quot;january-2026-the-bold-claim&quot;&gt;January 2026: the bold claim&lt;/h2&gt;
&lt;p&gt;Dario Amodei sat down with Zanny Minton Beddoes at the World Economic Forum in Davos and said what every IPO roadshow needs to hear&lt;sup&gt;&lt;a href=&quot;#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. “We might be six to twelve months away from when the model is doing most, maybe all of what SWEs do end to end.” He added that engineers within Anthropic already don’t write code anymore, they just let the model generate it and then edit the output.&lt;/p&gt;
&lt;p&gt;That’s a hell of a statement from the CEO of a company actively preparing for one of the largest IPOs in American history.&lt;/p&gt;
&lt;p&gt;I’ve watched this exact script play out before. At Motional, the AV pitch was always the same: full autonomy is right around the corner. The only difference between then and now is the buzzword. Back then the investor story was “human drivers are obsolete.” Today it’s “human engineers are obsolete.” Both claims share one feature: they’re calibrated for capital markets, not engineering reality. At Motional, we believed the timeline enough to staff for it. Then we cut 40 percent of engineering when reality didn’t cooperate.&lt;/p&gt;
&lt;p&gt;Saying “AI is a useful tool that makes engineers 30 percent more productive” doesn’t get you to a $350 billion valuation. Saying “software engineering is about to be obsolete” does. It implies total market capture, minimal future labor costs, exponential returns. Whether it’s true is almost beside the point for the roadshow.&lt;/p&gt;
&lt;h2 id=&quot;february-2026-the-panic&quot;&gt;February 2026: the panic&lt;/h2&gt;
&lt;p&gt;And then, right on schedule, LinkedIn lit up. “We’re all dead.” “Software engineering is over.” “Time to learn plumbing.” The usual cycle. Every major tech publication ran a variation of the story. Zoho’s founder Sridhar Vembu posted on X advising developers to consider alternative livelihoods. I’ve seen this exact reaction pattern play out at least three times in my career (yeah, it looks like I’m old :-D), each time with a different buzzword. Cloud was going to kill ops. Low-code was going to kill developers. Autonomous vehicles were going to wipe out the entire transportation workforce within five years. The panic always sounds the same, and it always serves someone’s fundraising timeline.&lt;/p&gt;
&lt;h2 id=&quot;also-february-2026-the-blind-reality&quot;&gt;Also February 2026: the Blind reality&lt;/h2&gt;
&lt;p&gt;While LinkedIn was running eulogies, Blind had something more interesting. A post about OpenAI’s hiring freeze&lt;sup&gt;&lt;a href=&quot;#user-content-fn-7&quot; id=&quot;user-content-fnref-7&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;: rumors of drastically reduced headcount, people stuck in the reference stage, roles going quiet. Not the dramatic AI-replaces-everyone narrative. Just the mundane, familiar silence of a company that stopped calling candidates back.&lt;/p&gt;
&lt;p&gt;Sam Altman had said at a developer town hall in late January that OpenAI plans to “dramatically slow down” hiring&lt;sup&gt;&lt;a href=&quot;#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;. AI lets them do more with fewer people, he explained. Careful phrasing: not a freeze, not a layoff, just “hiring more slowly.” Blind didn’t buy the distinction.&lt;/p&gt;
&lt;p&gt;So one CEO says engineering is becoming obsolete. The other says he needs fewer engineers. And both companies are hemorrhaging money. OpenAI is projected to lose around $12 billion this year. The Economist noted they expect to burn through another $115 billion before profitability around 2030&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Anthropic might break even a couple of years earlier. Might.&lt;/p&gt;
&lt;p&gt;If your AI is truly replacing engineers, your costs should be going down. They aren’t.&lt;/p&gt;
&lt;h2 id=&quot;what-the-data-says&quot;&gt;What the data says&lt;/h2&gt;
&lt;p&gt;I covered this in detail in my earlier piece on what actually drove the tech layoffs&lt;sup&gt;&lt;a href=&quot;#user-content-fn-6&quot; id=&quot;user-content-fnref-6&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;, and the short version hasn’t changed: even after all the cuts, Meta and Google employ 50 to 65 percent more people than they did in 2019. Spending per engineer went up, not down. The pandemic hiring bubble created the correction. AI was the convenient scapegoat.&lt;/p&gt;
&lt;p&gt;Ask yourself: if you were preparing to take a company public at a $350 billion valuation, would you keep hiring at pandemic-era rates? Of course not. You’d slow down, tighten the numbers, show Wall Street that you run a disciplined operation. That’s what’s happening. The AI angle is a press release. The hiring freeze is an accounting decision.&lt;/p&gt;
&lt;h2 id=&quot;where-this-actually-lands&quot;&gt;Where this actually lands&lt;/h2&gt;
&lt;p&gt;I use AI tools every day. Genuinely useful. Claude Code alone is reportedly a $1 billion a year product for Anthropic&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, and I understand why.&lt;/p&gt;
&lt;p&gt;That said. I once spent six hours debugging a video pipeline that produced garbage output on a single device out of twelve. The root cause: a thermal throttle changed the encoder bitrate, and a downstream component written by a different team assumed constant bitrate and silently dropped frames. One-line fix. Six hours of staring at thermal logs and corrupt H.264 frames to get there. Try putting that in a prompt when you don’t know the problem yet.&lt;/p&gt;
&lt;p&gt;“AI writes boilerplate faster” is real. “Engineering is obsolete” is an IPO story. The AV industry promised my car would drive itself by now. Still holding the steering wheel.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.economist.com/business/2025/12/16/spacex-openai-anthropic-and-their-giga-ipo-dreams&quot;&gt;The Economist, “SpaceX, OpenAI, Anthropic and their giga-IPO dreams.” December 16, 2025.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1-2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://money.usnews.com/investing/news/articles/2025-12-02/anthropic-plans-an-ipo-as-early-as-2026-ft-reports&quot;&gt;Anthropic IPO preparations. Financial Times via Reuters, December 2, 2025.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sacra.com/c/anthropic/&quot;&gt;Anthropic revenue and valuation data. Sacra.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3-2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.entrepreneur.com/business-news/ai-ceo-says-software-engineers-could-be-replaced-in-months/502087&quot;&gt;Dario Amodei at Davos. Interview with Zanny Minton Beddoes, World Economic Forum, January 20, 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-7&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.teamblind.com/us/s/0sd7ef8k&quot;&gt;OpenAI hiring freeze. Blind post.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-7&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://futurism.com/artificial-intelligence/sam-altman-openai-slashing-hiring&quot;&gt;OpenAI hiring slowdown. Sam Altman at developer town hall, January 27, 2026.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-6&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://sysdev.me/what-actually-drove-the-tech-layoffs/&quot;&gt;What Actually Drove the Tech Layoffs. My earlier analysis.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-6&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>Industry Analysis</category><category>Engineering Career</category><category>Economics</category></item><item><title>Development processes in the GenAI era</title><link>https://sysdev.me/2026/01/14/development-processes-in-the-genai-era/</link><guid isPermaLink="true">https://sysdev.me/2026/01/14/development-processes-in-the-genai-era/</guid><description>The current debate around GenAI and C++ is a good illustration of the real problem. Many engineers report that models are worse than juniors.</description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/gen-ai-processes/genai-era.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/gen-ai-processes/genai-era.png&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;The current debate around GenAI and C++ is a good illustration of the real problem. Many engineers report that models are worse than juniors. Others report dramatic speedups on the same language and problem space. Both observations are correct.&lt;/p&gt;
&lt;p&gt;The difference is not the model. It is the absence or presence of the state.&lt;/p&gt;
&lt;p&gt;Most GenAI usage today is stateless. A model is dropped into an editor with a partial view of the codebase, no durable memory, no record of prior decisions, no history of failed attempts, and no awareness of long-running context. In that mode, the model behaves exactly like an amnesic junior engineer. It repeats mistakes, ignores constraints, and proposes changes without understanding downstream consequences.&lt;/p&gt;
&lt;p&gt;When engineers conclude that “AI is not there yet for C++”, they are often reacting to this stateless setup.&lt;/p&gt;
&lt;p&gt;At the same time, GenAI does not elevate engineering skill. It does not turn a junior into a senior. What it does is amplify the level at which an engineer already operates. A senior engineer using GenAI effectively becomes a faster senior, and a junior becomes a faster junior. Judgment is not transferred, and the gap does not close automatically.&lt;/p&gt;
&lt;p&gt;These two facts are tightly coupled. In stateless, unstructured usage, GenAI amplifies noise. In a stateful, constrained workflow with explicit ownership and review, it amplifies competence.&lt;/p&gt;
&lt;p&gt;This is why reported productivity gains vary so widely. Claims of 200–300% speedup are achievable, but only locally and only within the bounds of the user’s existing competence. Drafting, exploration, task decomposition, and mechanical transformation accelerate sharply. End-to-end throughput increases are lower because planning, integration, validation, and responsibility remain human-bound.&lt;/p&gt;
&lt;p&gt;The question, then, is not whether GenAI is “good enough”. The question is what kind of system you embed it into.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Everything I’ll explain below is &lt;strong&gt;only&lt;/strong&gt; applicable to the &lt;strong&gt;Stateful GenAI&lt;/strong&gt; setup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;stateless-vs-stateful&quot;&gt;Stateless vs Stateful&lt;/h2&gt;
&lt;h2 id=&quot;what-changed-and-what-did-not&quot;&gt;What changed and what did not&lt;/h2&gt;
&lt;p&gt;One fundamental shift is that everyone becomes a TPM, at least a little. GenAI moves the bottleneck from typing to coordination. Breaking work into coherent tasks, sequencing dependencies, tracking risk, and explaining intent clearly now dominate more of the day. Engineers with prior TPM experience adapt faster. Others find it uncomfortable at first, but the skill is learnable.&lt;/p&gt;
&lt;p&gt;There is a failure mode here. When coordination skills are weak, work fragments into tickets that lose architectural coherence, or decisions get made implicitly inside prompts instead of explicitly in reviews. That is why process matters more as velocity increases, not less.&lt;/p&gt;
&lt;p&gt;Another thing that did not change is the nature of GenAI itself. These systems are powerful pattern matchers, not reasoning engines. They accelerate drafting and exploration, but they do not replace engineering judgment. Humans remain the source of truth for architecture, correctness, performance, concurrency guarantees, undefined behavior, and production readiness. There are entire classes of problems where trusting model output is actively dangerous, especially in low-level or highly concurrent systems.&lt;/p&gt;
&lt;h2 id=&quot;process-matters-more-under-higher-speed&quot;&gt;Process matters more under higher speed&lt;/h2&gt;
&lt;p&gt;GenAI allows more change in less time. Without discipline, that extra change turns into larger merge requests, unclear ownership, brittle integration, and review fatigue. GenAI amplifies whatever process you already have, good or bad.&lt;/p&gt;
&lt;p&gt;A practical end-to-end flow that works well looks roughly like this: planning, architectural research and documentation, implementation, integration testing, and documentation updates. Every stage includes a mandatory human review. GenAI can assist, but it cannot be the approver.&lt;/p&gt;
&lt;p&gt;Ownership is explicit. The engineer who initiated the change remains accountable throughout. GenAI is treated like a junior contributor that can draft and suggest, not like an authority. Peer review at the end is mandatory, but the author is always the first human gatekeeper.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/gen-ai-processes/the-process.svg&quot; width=&quot;1024&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Review gates are not overhead. They are the control mechanism that prevents increased speed from degrading correctness.&lt;/p&gt;
&lt;h2 id=&quot;code-review-and-validation&quot;&gt;Code review and validation&lt;/h2&gt;
&lt;p&gt;Manual review quality degrades as the change size increases. Unfortunately, GenAI assistance does not remove this limitation; changes must remain small and coherent to remain reviewable under higher throughput. GenAI-based review tools generate useful signals but also a high rate of false positives in complex systems code. Review output must be treated as hypotheses. Formal guarantees still come from static analysis, testing, and runtime instrumentation.&lt;/p&gt;
&lt;p&gt;Cross-model review reduces correlated failure modes. Different models tend to fail differently and can unblock each other. Human reviewers arbitrate conflicts and retain decision authority.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/gen-ai-processes/review-and-validation.svg&quot; width=&quot;640&quot; alt=&quot;&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Failure mode without control&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Without structure, GenAI introduces a reinforcing failure loop. Increased change volume leads to unfamiliar code and larger diffs. Review fatigue follows, then shallow validation, latent defects, incidents, and loss of trust. Velocity declines as rework dominates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Process discipline exists to break this loop.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;traceability-task-switching-and-state&quot;&gt;Traceability, task switching, and state&lt;/h2&gt;
&lt;p&gt;Higher iteration speed increases task switching. This is not a focus problem. It is a structural consequence of discovering constraints late. The same pattern exists without GenAI. When a hard dependency is discovered during implementation, scope is reduced, new tasks are created, work is paused, and attention shifts elsewhere. The difference is not the mechanism, but the frequency. GenAI accelerates the path to constraint discovery, which makes these transitions happen earlier and more often.&lt;/p&gt;
&lt;p&gt;A common pattern is partial implementation followed by discovery of a hard dependency. Work pauses while the dependency is resolved. The primary cost is not the interruption, but the effort required to resume.&lt;/p&gt;
&lt;p&gt;A persisting feature or epic state in a small, human-readable file externalizes working memory. The state captures what was discovered, why work stopped, which assumptions changed, and what the next action is. This reduces reload cost and enables both humans and models to resume work deterministically.&lt;/p&gt;
&lt;p&gt;This mechanism is particularly effective across weekends and public holidays, where implicit context is most likely to be lost.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/gen-ai-processes/task-switching.svg&quot; width=&quot;640&quot; alt=&quot;&quot;&gt;
&lt;h2 id=&quot;multi-agent-workflows&quot;&gt;Multi-agent workflows&lt;/h2&gt;
&lt;p&gt;Single-agent GenAI workflows collapse architectural, implementation, and operational concerns into a single output. This does not scale.&lt;/p&gt;
&lt;p&gt;From an LLM perspective, all decisions in a single-agent setup are derived from the same prompt, context window, and latent state. Assumptions are shared across concerns and are not independently challenged. As scope grows, the model resolves competing objectives implicitly within one generation path, which causes errors to become correlated. Design mistakes propagate directly into code and operational logic, and incorrect assumptions are rarely revisited once embedded in the output.&lt;/p&gt;
&lt;p&gt;To scale safely, independent reasoning paths are required. At a minimum, separate agents should reason independently about structure, correctness, and operability. Disagreement between agents is expected and desirable. Humans arbitrate conflicts and retain ownership.&lt;/p&gt;
&lt;img src=&quot;https://sysdev.me/img/gen-ai-processes/multi-agent.svg&quot; width=&quot;640&quot; alt=&quot;&quot;&gt;
&lt;h2 id=&quot;yaml-based-epic-and-feature-state&quot;&gt;YAML-based epic and feature state&lt;/h2&gt;
&lt;p&gt;Persisting state requires a concrete, durable representation. In practice, YAML works well for this role.&lt;/p&gt;
&lt;p&gt;Epic- and feature-level YAML files act as shared execution state for humans and GenAI agents. They capture intent, scope, constraints, and current understanding in a form that is diffable, reviewable, and version-controlled. Unlike conversational context, this state survives task switching, interruptions, and tool changes.&lt;/p&gt;
&lt;p&gt;These files intentionally describe &lt;em&gt;what&lt;/em&gt; needs to be achieved rather than &lt;em&gt;how&lt;/em&gt; it should be implemented. Outcome-focused descriptions and acceptance criteria prevent premature design decisions from being embedded into state and allow independent reasoning by both humans and agents.&lt;/p&gt;
&lt;p&gt;Validation at the state boundary keeps the structure intact as the iteration speed increases. Required sections, label constraints, and basic consistency checks ensure that the state remains predictable and reviewable over time.&lt;/p&gt;
&lt;p&gt;YAML state does not replace issue trackers. Trackers remain the system of record for execution and discussion. The YAML files represent the current shared understanding of the work and serve as a stable input contract for GenAI usage.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A concrete example of this approach, including templates and validation rules, is covered in a separate article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;cross-model-consistency-and-shared-state&quot;&gt;Cross-model consistency and shared state&lt;/h2&gt;
&lt;p&gt;Using multiple models introduces inconsistency unless entry conditions are controlled. A single repository-level agent contract, such as &lt;code&gt;AGENT.md&lt;/code&gt; or &lt;code&gt;CLAUDE.md&lt;/code&gt;, provides a stable starting point for all models. In practice, this file is shared across tools, often via symlinks, to ensure that different models operate under the same constraints.&lt;/p&gt;
&lt;p&gt;The agent contract defines global invariants: coding standards, validation requirements, review expectations, and links to authoritative documentation. It does not duplicate domain knowledge. Its role is to fix assumptions at the boundary and make them explicit.&lt;/p&gt;
&lt;p&gt;On its own, this is not sufficient. The agent contract establishes &lt;em&gt;how&lt;/em&gt; models should behave, but not &lt;em&gt;what&lt;/em&gt; they are working on. That role is filled by explicit task and execution state, represented in YAML at the epic and feature level. Together, the agent contract and YAML state form a minimal, durable memory for GenAI-assisted workflows.&lt;/p&gt;
&lt;p&gt;This combination addresses the primary failure mode of GenAI systems. Statelessness. Models no longer infer intent from partial diffs or conversational history. They enter with fixed rules and consume explicit state. Memory becomes an artifact, not an assumption.&lt;/p&gt;
&lt;h2 id=&quot;costs-and-constraints&quot;&gt;Costs and constraints&lt;/h2&gt;
&lt;p&gt;GenAI-assisted development introduces costs that follow directly from its operating model. Effective usage typically involves multiple models, trading latency and cost for correctness and review quality. Inference cost is usually small relative to senior engineer time but becomes a predictable infrastructure expense at scale. The relevant metric is cost per accepted change.&lt;/p&gt;
&lt;p&gt;The dominant non-monetary cost is cognitive. GenAI replaces typing with validation. Engineers spend more time reading diffs, checking assumptions, resolving contradictions, and restoring context. This resembles continuous code review and leads to faster fatigue if unmanaged.&lt;/p&gt;
&lt;p&gt;There is also a process cost. Large gains require explicit state, strict ownership, small changesets, role separation, and shared contracts. These controls add upfront overhead but prevent regression in correctness and operability.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;GenAI increases the rate at which change is produced. It does not reduce system complexity, validation requirements, or engineering responsibility.&lt;/p&gt;
&lt;p&gt;Sustained gains require explicit state, clear ownership, disciplined review, role separation, and controlled entry points. Without these, GenAI accelerates failure as efficiently as it accelerates delivery.&lt;/p&gt;
&lt;p&gt;Used as part of a system, GenAI is a force multiplier. Used in isolation, it is noise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GenAI rapidly evolves. Meaning, this info is valid as of Jan 2026 and may no longer be valid.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>AI</category><category>Tooling</category><category>Processes</category></item><item><title>Today I learned... git shallow</title><link>https://sysdev.me/2025/12/31/today-i-learned-git-shallow/</link><guid isPermaLink="true">https://sysdev.me/2025/12/31/today-i-learned-git-shallow/</guid><description>Sometimes you need to understand why something exists, and instead, you’re staring at a mystery. It feels like magic for a moment. But there is no magic in IT.</description><pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/git-shallow.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/git-shallow.png&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
Sometimes you need to understand why something exists, and instead, you’re staring at a mystery. It feels like magic for a moment. But there is no magic in IT. There is always a reason, and usually it’s painfully concrete.
&lt;p&gt;Today I learned that if &lt;code&gt;git blame&lt;/code&gt; suddenly claims I wrote the entire million-line project, it might be lying 🙂&lt;/p&gt;
&lt;p&gt;I ran into a situation where my local &lt;code&gt;git blame&lt;/code&gt; attributed every line to a single recent commit, while GitLab showed the correct historical authors. At first glance, it looked like history had been rewritten, which is odd and incorrect.&lt;/p&gt;
&lt;p&gt;The first real clue was in the blame output itself, the caret prefix before the commit hash:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;^4e8fb9eb2 (Alex Stavonin …) #include &amp;lt;cassert&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Google told me that &lt;code&gt;^&lt;/code&gt; means “boundary commit”, meaning Git had reached the end of the history it could see. A quick check confirmed it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ls .git/shallow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git log --oneline | wc -l&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only a couple of commits were visible. The repo was a shallow clone.&lt;/p&gt;
&lt;p&gt;In a shallow clone, &lt;code&gt;git blame&lt;/code&gt; can’t walk past the depth limit, so it assigns all lines to the oldest commit it knows about. The real authors live before that boundary. GitLab has the full history, so its blame view stays correct.&lt;/p&gt;
&lt;p&gt;The fix was simple:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git fetch --unshallow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That pulls the missing history, removes &lt;code&gt;.git/shallow&lt;/code&gt;, and turns the repo into a normal clone. After that, &lt;code&gt;git blame&lt;/code&gt; immediately showed the correct commits and authors, with no boundary markers.&lt;/p&gt;
&lt;p&gt;And Happy New Year!&lt;/p&gt;
</content:encoded><category>Git</category><category>Tooling</category></item><item><title>Dealing with ThreadSanitizer Fails on Startup</title><link>https://sysdev.me/2025/12/05/dealing-with-threadsanitizer-fails-on-startup/</link><guid isPermaLink="true">https://sysdev.me/2025/12/05/dealing-with-threadsanitizer-fails-on-startup/</guid><description>Usually, you need just a few lines to initialize TSan in your project: you compile with the sanitizer flags, run the tests, and get a clear report of which threads touched which memory locations.</description><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/pi_crab.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/pi_crab.png&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;Usually, you need just a few lines to initialize TSan in your project: you compile with the sanitizer flags, run the tests, and get a clear report of which threads touched which memory locations. On a modern Linux system, that simple expectation can fail in a very non-obvious way.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FATAL: ThreadSanitizer: unexpected memory mapping 0x...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, I attached TSan to a not-so-young C++ codebase and immediately encountered a fatal runtime error from the sanitizer, long before any of the project’s code executed. No race report, no helpful stack trace, just a hard abort complaining about an “unexpected memory mapping.”&lt;/p&gt;
&lt;p&gt;If you can upgrade your toolchain to LLVM 18.1 or newer, this problem effectively disappears, because newer TSan builds know how to recover from the incompatible memory layout. Suppose you are pinned to an older LLVM (by CI images, production constraints, or corporate distro policy). In that case, you are in the same situation I was: you have to understand what the sanitizer is trying to do with the address space, and work around the failure mode yourself.&lt;/p&gt;
&lt;p&gt;The process terminates in its initialization phase, long before your program’s logic executes. The failure stems from changes to Linux’s memory-layout randomization and from assumptions baked into TSan’s address-space model. At the end, it was not as complicated&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; an issue as I initially thought, and it was interesting to dig a bit.&lt;/p&gt;
&lt;h2 id=&quot;a-minimal-possible-example&quot;&gt;A Minimal Possible Example&lt;/h2&gt;
&lt;p&gt;To keep the demonstration as small as possible, let’s make a deliberately naive example: multiple threads increment a shared global integer with no synchronization of any kind. We ignore atomics, mutexes, and every correctness rule you would normally apply.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shared_counter &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; increment_counter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; thread_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; iterations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iterations; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; old_value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shared_counter;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        shared_counter &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; old_value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any sanitizer-enabled build should report concurrent accesses to shared_counter, show at least two stacks, and explain that the update is non-atomic. Compiling with Clang and ThreadSanitizer is straightforward.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And one note upfront: if you build this locally with an LLVM toolchain newer than 18.1, you may not see the failure at all. Modern TSan versions recover automatically by re-executing the process with a compatible memory layout, so the crash only appears when using older LLVM releases. For experiments I &lt;a href=&quot;https://github.com/astavonin/tsan_aslr_demo&quot;&gt;made a project&lt;/a&gt; with LLCV-14 installed that uses Docker to provide builds with reproducible issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;clang++&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -std=c++17&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -g&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -Wall&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    -fsanitize=thread&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -fno-omit-frame-pointer&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; simple_test&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; simple_test.cpp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On older Linux distributions, you get a standard TSan report. In newer environments, TSan may crash before producing any output.&lt;/p&gt;
&lt;h2 id=&quot;will-non-pie-binaries-help&quot;&gt;Will non-PIE Binaries Help?&lt;/h2&gt;
&lt;p&gt;Typically, GCC/LLVM generates Position Independent Executables (PIE) by default. From the loader’s perspective, a PIE binary behaves like a shared object: it can be mapped at any randomized base address. A non-PIE binary (ET_EXEC) has a static image layout, and the loader only applies relocations around fixed segments. TSan’s shadow memory model behaves differently depending on which form you use.&lt;/p&gt;
&lt;p&gt;First of all, we need to build two variants:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# PIE build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;clang++ -std=c++17 -g -Wall \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -fsanitize=thread -o simple_test_pie simple_test.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# non-PIE build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;clang++ -std=c++17 -g -Wall \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -fsanitize=thread -fno-PIE -no-pie \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -o simple_test_nopie simple_test.cpp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On systems with high memory randomization, the PIE variant usually fails first because its relocation window is more likely to overlap with TSan’s expected shadow ranges. The non-PIE build can also fail, but slightly less frequently. So, this is also not a solution, even though I initially thought it was the cause.&lt;/p&gt;
&lt;h2 id=&quot;what-actually-goes-wrong-tsans-shadow-memory-layout&quot;&gt;What Actually Goes Wrong: TSan’s Shadow Memory Layout&lt;/h2&gt;
&lt;p&gt;ThreadSanitizer maps every real address to a shadow address. It doesn’t do this on demand for each page. It wants large, fixed chunks of the address space reserved up front for its metadata: access histories, timestamps, and everything else it needs to track races. The layout for x86_64 was designed at a time when Linux used much lower ASLR ranges, and the sanitizer relied on that space being predictable.
You can check how much randomness your kernel uses with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sud&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cat&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /proc/sys/vm/mmap_rnd_bits&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Typical value on modern Linux is 32. Older kernels stayed around 28 to 30. Bumping it to 32 simply widens the area where the kernel can drop anonymous mappings, shared libraries, JIT code, or loader segments. With that extra room, the chance that one of those mappings lands in the region TSan wants for its shadow space goes up noticeably.&lt;/p&gt;
&lt;p&gt;When that happens, the sequence is boringly predictable:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The loader picks a random high address for a library or for the binary itself.&lt;/li&gt;
&lt;li&gt;TSan starts up and asks for its shadow memory slice.&lt;/li&gt;
&lt;li&gt;That slice is already occupied.&lt;/li&gt;
&lt;li&gt;Older TSan doesn’t try to recover. It just aborts immediately.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;disabling-aslr-for-the-test-process&quot;&gt;Disabling ASLR for the Test Process&lt;/h2&gt;
&lt;p&gt;It’s possible to disable ASLR for the whole system, but it does not look like a good idea. It will weaken the security posture of the host, which is usually unacceptable. Fortunately, Linux allows individual processes to run with ASLR off via the &lt;code&gt;ADDR_NO_RANDOMIZE&lt;/code&gt; personality flag. The simplest interface is &lt;code&gt;setarch&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setarch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;uname&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;-R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./simple_test_nopie&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command launches the child process with randomized layout disabled while leaving the system configuration intact. With deterministic placement restored, TSan can reserve its shadow memory and run normally. This was sufficient for me for local development and CI pipelines that run dynamic analysis tools.&lt;/p&gt;
&lt;h2 id=&quot;but-what-is-about-docker&quot;&gt;But What is about Docker?&lt;/h2&gt;
&lt;p&gt;Here comes the tricky part. Containers don’t get their own ASLR policy; they just reuse whatever the host is doing. And most CI images run with Docker’s default seccomp profile, which blocks a bunch of syscalls, including personality(). Since setarch depends on that syscall, it simply can’t flip the ASLR flag inside the container, so the call fails with an error.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;setarch: failed to set personality to x86_64: Operation not permitted&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Relaxing the container’s &lt;code&gt;seccomp&lt;/code&gt; configuration solves the problem:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --security-opt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; seccomp=unconfined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the sandbox permits &lt;code&gt;personality()&lt;/code&gt;, you can run your sanitizer-instrumented binary via &lt;code&gt;setarch -R&lt;/code&gt; inside the container with the same behavior you observe on a host.&lt;/p&gt;
&lt;p&gt;If you want a more selective policy, you can create a custom &lt;code&gt;seccomp&lt;/code&gt; file that mirrors Docker’s defaults but allows &lt;code&gt;personality&lt;/code&gt;. This avoids granting full &lt;code&gt;syscall&lt;/code&gt; access to the container while still enabling TSan.&lt;/p&gt;
&lt;h2 id=&quot;why-upgrading-the-toolchain-solves-the-problem&quot;&gt;Why Upgrading the Toolchain Solves the Problem&lt;/h2&gt;
&lt;p&gt;LLVM 18.1 changes how TSan handles bad luck with the address layout. When it sees that the current mappings collide with what it needs, it doesn’t give up anymore. Instead, it quietly re-executes the process with ASLR turned off just for that run. The sanitizer gets a layout it can work with, and from the outside the program simply starts normally.&lt;/p&gt;
&lt;p&gt;With LLVM 18.1 or newer, you don’t need setarch, you don’t need to relax seccomp in Docker, and you don’t need to switch to non-PIE builds. The newer runtime handles the mismatch on its own and avoids the crashes that show up on older toolchains.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;Original issue discussion on GitHub: &lt;a href=&quot;https://github.com/google/sanitizers/issues/1716&quot;&gt;Thread Sanitizer FATAL error on kernel version 6.6.6-x&lt;/a&gt;. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>C++</category><category>Docker</category><category>CI</category><category>DevOps</category><category>Tooling</category></item><item><title>Cross-Compiling Rust for Raspberry Pi</title><link>https://sysdev.me/2025/11/27/cross-compiling-rust-for-raspberry-pi/</link><guid isPermaLink="true">https://sysdev.me/2025/11/27/cross-compiling-rust-for-raspberry-pi/</guid><description>I just started a new embedded pet project on the Raspberry Pi, and I expect it&apos;ll be a pretty big one, so I&apos;ve been thinking about the technology from the beginning.</description><pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/pi_crab.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/pi_crab.png&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;I just started a new embedded pet project on the Raspberry Pi, and I expect it’ll be a pretty big one, so I’ve been thinking about the technology from the beginning. The overall goal is to create a glass-to-glass video pipeline example. Let’s see how it’s going. For now, I’m using a USB &lt;code&gt;V4L2&lt;/code&gt; camera while waiting for the native Pi modules to arrive, but it’s enough to sketch the capture loop and start testing the build pipeline. The application itself is minimal—open &lt;code&gt;/dev/video0&lt;/code&gt;, request &lt;code&gt;YUYV&lt;/code&gt; at 1280x720, set up &lt;code&gt;MMAP&lt;/code&gt; buffers, and iterate over frames—but the real challenge occurs when &lt;code&gt;v4l&lt;/code&gt; triggers &lt;code&gt;bindgen&lt;/code&gt;, and the build must cross-compile cleanly for &lt;code&gt;aarch64&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;The language choice immediately becomes part of the equation right away. Go is my favorite and, usually, is not considered as an option by many embedded developers. But it’s a good choice for small embedded utilities because its cross-compilation story is nearly effortless. Need an ARM binary? One command and you have it!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GOOS=linux GOARCH=arm64 go build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But Go’s garbage collector isn’t something I want to use in a timing-sensitive video-processing capture loop. In theory, C++ seems like an alternative, but cross-compiling C++ code for any embedded platform is often a nightmare with toolchains, sysroots, and inconsistent environments. Frankly speaking, I’m not ready to do it just for fun; it’s OK only if I’m getting paid for it. Rust falls into a more practical middle ground. Its build system is not as elegant as Go’s, but it sidesteps the C++ toolchain maze and provides predictable behavior without GC pauses. It looks like a win-win for the Pi video-capture codebase.&lt;/p&gt;
&lt;p&gt;The plan is straightforward: develop on fast &lt;code&gt;x86&lt;/code&gt; hardware, reliably ship &lt;code&gt;aarch64&lt;/code&gt; binaries, and avoid relying on the Pi itself for builds.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re not interested in the reasoning behind the project but just want a reference, I’ve created a &lt;a href=&quot;https://github.com/astavonin/pi-cam-capture/tree/ci-and-cross&quot;&gt;small repository for you&lt;/a&gt;. It includes the capture loop, cross-compilation setup, CI pipeline, and all supporting configurations, presented exactly as they are today.&lt;/p&gt;
&lt;p&gt;It’s the straightforward, no-commentary version of this article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;test-app-v4l-camera-capture&quot;&gt;Test App: V4L Camera Capture&lt;/h2&gt;
&lt;p&gt;To start, I needed something as simple as possible while still being close to real life, so I chose a minimal Rust application that reads frames from a USB V4L2 camera. The goal wasn’t to build a complete pipeline from the beginning. I first need to have minimal, but stable CI and cross-compilation in place, and this small capture loop is good enough to identify any toolchain problems in the early stages.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;flowchart&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    subgraph&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; HW&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Hardware&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        CAM&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;USB Camera&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        DEV&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;//dev\/video0//&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    subgraph&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; SW&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Software Stack&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        API&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;V4L2 Kernel API&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        CRT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;v4l crate&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        APP&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Test App&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    CAM &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;gt; DEV &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; API &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;gt; CRT &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; APP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dev &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; v4l&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Device&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; mut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dev&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;width &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1280&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;height &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 720&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fourcc &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; v4l&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FourCC&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;b&quot;YUYV&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dev&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;set_format&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fmt)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; mut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stream &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    v4l&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mmap&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Stream&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;with_buffers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dev, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;v4l&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;buffer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;VideoCapture&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Ok&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((buf, meta)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stream&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;size={} seq={} ts={}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, buf&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), meta&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequence, meta&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;timestamp);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And I was right - you can’t just take it and cross-compile it without any problems! Even this tiny example should have some issues during cross-compilation: the &lt;code&gt;v4l&lt;/code&gt; crate, &lt;code&gt;bindgen&lt;/code&gt;, and the need for the correct &lt;code&gt;libclang&lt;/code&gt; inside the build environment.&lt;/p&gt;
&lt;h2 id=&quot;why-not-justgcc-aarch64-linux-gnu&quot;&gt;&lt;strong&gt;Why Not Just gcc-aarch64-linux-gnu?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;You can easily wire the toolchain manually. It usually starts like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; gcc-aarch64-linux-gnu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rustup&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; target&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; aarch64-unknown-linux-gnu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you point Cargo at the cross-linker:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# .cargo/config.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;aarch64-unknown-linux-gnu&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;linker = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;aarch64-linux-gnu-gcc&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, you can:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cargo build --target aarch64-unknown-linux-gnu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is working at the moment I’m writing this text. I did test it. But it may also cause issues in the future. The linker might be fine, but &lt;code&gt;bindgen&lt;/code&gt; now requires a compatible &lt;code&gt;libclang&lt;/code&gt;, along with the correct headers and sysroot. If any of those are missing or mismatched, the build collapses into the usual cascade of &lt;code&gt;libclang&lt;/code&gt; errors — exactly the experience that pushed me toward a dedicated cross-image instead.&lt;/p&gt;
&lt;p&gt;By contrast, pushing everything into a dedicated cross-image turns the toolchain into a versioned artifact. The linker, sysroot, libclang, and all the supporting packages live in a single Docker image that I can rebuild, tag, and publish. The Rust project only needs a &lt;code&gt;Cross.toml&lt;/code&gt; pointing at &lt;a href=&quot;https://github.com/users/astavonin/packages/container/package/cross-aarch64&quot;&gt;ghcr.io/astavonin/cross-aarch64:llvm12&lt;/a&gt; (yep, not a standard one from &lt;code&gt;ghcr.io/cross-rs/&lt;/code&gt;, I’ll explain it later), and both local builds and CI reuse precisely the same environment. That is the main reason I didn’t stop at &lt;code&gt;gcc-aarch64-linux-gnu&lt;/code&gt; and went straight to a containerized approach.&lt;/p&gt;
&lt;h2 id=&quot;building-a-customcross-image&quot;&gt;Building a Custom cross Image&lt;/h2&gt;
&lt;p&gt;The natural question is, why not just use the stock &lt;code&gt;ghcr.io/cross-rs/aarch64-unknown-linux-gnu&lt;/code&gt; image? It works for many projects, and for simple crates, it’s perfectly fine. The problem shows up the moment the build pulls in &lt;code&gt;v4l&lt;/code&gt;, because that drags in &lt;code&gt;bindgen&lt;/code&gt;, and &lt;code&gt;bindgen&lt;/code&gt; depends on &lt;code&gt;libclang&lt;/code&gt;. The default cross image ships an older &lt;code&gt;libclang&lt;/code&gt; that doesn’t play well with some of the functions &lt;code&gt;bindgen&lt;/code&gt; expects. That’s how you end up with the familiar error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A libclang function was called that is not supported&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;called function = clang_getTranslationUnitTargetInfo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I started from the official cross-rs base:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;docker&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ghcr.io/cross-rs/aarch64-unknown-linux-gnu:latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This image is based on &lt;strong&gt;Ubuntu 16.04 Xenial&lt;/strong&gt;, which becomes a critical part in components selection. Xenial is old, and the available LLVM packages for it are limited. If you look at: &lt;a href=&quot;https://apt.llvm.org/xenial/dists/&quot;&gt;https://apt.llvm.org/xenial/dists/&lt;/a&gt;, the highest LLVM/Clang version published for Xenial is &lt;strong&gt;12&lt;/strong&gt;. That’s the ceiling. Anything newer doesn’t exist for this base distribution; we can use PPA if needed, but 12 is enough for my case.&lt;/p&gt;
&lt;p&gt;So the choice of Clang wasn’t aesthetic — the platform dictated it. LLVM 12 is the newest supported toolchain you can install inside Xenial without rebuilding LLVM from source, and it’s new enough to keep &lt;code&gt;bindgen&lt;/code&gt; happy for &lt;code&gt;v4l&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/astavonin/cross-aarch64/blob/master/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt; installs the LLVM 12 family and exposes it to bindgen:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;docker&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; LIBCLANG_PATH=/usr/lib/llvm-12/lib&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CLANG_PATH=/usr/bin/clang-12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; LLVM_CONFIG_PATH=/usr/bin/llvm-config-12&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configuring-rust-projects-withcrosstoml&quot;&gt;Configuring Rust Projects with &lt;code&gt;Cross.toml&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Once the custom image is built, wiring it into a Rust project is trivial. This is the part I wanted to keep as boring as possible. All the configuration lives in a small &lt;code&gt;Cross.toml&lt;/code&gt; file at the root of the repo. In that file, I simply tell Cross which Docker image to use for aarch64 builds, so both local builds and CI run against the same toolchain. &lt;/p&gt;
&lt;p&gt;A minimal configuration looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[target.aarch64-unknown-linux-gnu]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ghcr.io/astavonin/cross-aarch64:llvm12&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[build.env]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;passthrough&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;RUST_BACKTRACE&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s all you need. Now the actual build becomes:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cross&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --release&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --target&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; aarch64-unknown-linux-gnu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And under the hood, cross will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pull or reuse your custom image with the correct libclang.&lt;/li&gt;
&lt;li&gt;Mount the Rust project inside the container.&lt;/li&gt;
&lt;li&gt;Invoke &lt;code&gt;cargo build&lt;/code&gt; using the aarch64 toolchain baked into the image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The important part here is that the toolchain is no longer spread across multiple machines or CI runners. Everything that matters — linker, sysroot, libclang, headers — travels together inside a versioned container.&lt;/p&gt;
&lt;h2 id=&quot;ci-strategy-fast-x86-builds--on-demand-arm-artifacts&quot;&gt;CI Strategy: Fast x86 Builds + On-Demand ARM Artifacts&lt;/h2&gt;
&lt;p&gt;Since this is a personal project and I am using the free GitHub plan, I need to minimize the time spent by the runners. Building natively for x86 on GitHub Actions provides quick feedback from unit tests and potentially integration tests. This approach avoids the costs associated with container startup and does not involve the aarch64 toolchain at all. This setup is sufficient for feature branches and routine development.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;gitGraph&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; LR:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;base&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    branch&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; feature&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    checkout&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; feature&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;FB1&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Commit&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;FB2&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;x86 Build&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;FB3&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Tests&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;FB4&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;CI Result&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    checkout&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MB1&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Commit&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MB2&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;x86 Build&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MB3&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;aarch64 Cross Build&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MB4&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Upload ARM Artifact&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MB5&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Tests&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; id:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MB6&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; tag:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;CI Result&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cross-compiling for ARM comes with overhead: container startup, toolchain load, and bindgen doing its thing. It’s fine when you need an artifact, but wasteful for every feature branch. To avoid chewing through CI time and monthly limits, I only run aarch64 builds on main. Sure, will never do it for the production environment, but it looks like a perfect idea for this case.&lt;/p&gt;
&lt;p&gt;This split keeps the feedback loop tight while still producing reliable ARM binaries when I actually need them. It also avoids depending on native ARM runners, which are either slower or require extra infrastructure. Everything runs on GitHub’s standard x86 machines, and the custom cross-image ensures the ARM build behaves exactly the same every time.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;With the toolchain in place and the CI behaving predictably, it’s time to move on to the parts of the project that actually matter. The tiny capture app still lacks many functionalities. Next comes testing with the native Pi camera modules, measuring queue depth, watching how the system behaves under thermal load, and shaping the first real version of the video pipeline.&lt;/p&gt;
</content:encoded><category>Rust</category><category>Raspberry Pi</category><category>CI</category><category>DevOps</category><category>Tooling</category></item><item><title>What Actually Drove the Tech Layoffs</title><link>https://sysdev.me/2025/11/20/what-actually-drove-the-tech-layoffs/</link><guid isPermaLink="true">https://sysdev.me/2025/11/20/what-actually-drove-the-tech-layoffs/</guid><description>I truly enjoy reading Blind and Levels. There is so much internal drama, messy details, and unexpected insights that you almost do not need reality shows anymore.</description><pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/chon-aryk-hills.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/chon-aryk-hills.jpeg&quot; alt=&quot;&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
	A 10-minute ride and you have such a view from Chon Aryk hills.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I truly enjoy reading Blind and Levels. There is so much internal drama, messy details, and unexpected insights that you almost do not need reality shows anymore. And if you ever feel bored, you can always drop a mildly toxic comment into a thread and watch the whole thing ignite. It fits the overall style of Blind a little too well, but that is part of the fun. And considering that mix of casual toxicity and surprisingly rational takes you see there, you would expect people to look at layoffs with a bit more perspective. But when the topic comes up, the conversation usually drifts to the same explanation. People blame AI. People say their jobs vanished because a model wrote some code. And while I understand the frustration, the logic never sits right with me. Nobody complained during the hiring boom of 2020 and 2021, when companies doubled their headcount like it was nothing. That part gets forgotten. Now that the correction is here, many want a simple villain. AI fits the story, but it does not fit the data.&lt;/p&gt;
&lt;p&gt;I have seen this pattern before. Back at Motional we were deep in the autonomous vehicle race. That industry grew on promises we could not keep, and considering what I know about the AV domain today, those promises were never possible in the first place. For years the message to investors was that full self driving was right around the corner. We hired fast and built big. Then reality caught up. Cruise paused its operations. Argo AI shut down. Uber ATG was sold. Zoox cut teams after delays. Pony.ai struggled with regulations. And inside Motional we cut about 40 percent of engineering in a single move, after several smaller rounds. It was painful, but it made sense once you looked past the hype. We hired for a future that had not arrived.&lt;/p&gt;
&lt;p&gt;Big tech did something similar. When the pandemic hit and the world moved online, demand exploded. Companies assumed that surge was permanent. They hired for a future where remote work and digital services would keep climbing forever. Then the world shifted back to normal, the economy cooled, and interest rates went up. Suddenly those oversized teams were hard to justify. The layoffs that followed were not about engineers losing relevance. They were about companies correcting unrealistic growth curves.&lt;/p&gt;
&lt;h2 id=&quot;headcount-is-still-way-above-pre-pandemic-levels&quot;&gt;Headcount is still way above pre pandemic levels&lt;/h2&gt;
&lt;p&gt;Even after layoffs, Meta, Google, and Netflix remain much larger than they were before COVID. The numbers make this very clear.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Employees 2019&lt;/th&gt;
&lt;th&gt;Employees 2024&lt;/th&gt;
&lt;th&gt;Growth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Meta&lt;/td&gt;
&lt;td&gt;44,942&lt;/td&gt;
&lt;td&gt;74,067&lt;/td&gt;
&lt;td&gt;+65 percent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google (Alphabet)&lt;/td&gt;
&lt;td&gt;118,899&lt;/td&gt;
&lt;td&gt;183,323&lt;/td&gt;
&lt;td&gt;+54 percent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If AI replaced engineers at scale, we would see headcounts dropping below 2019 levels. That did not happen. Tech firms grew fast, then trimmed the excess.&lt;/p&gt;
&lt;h2 id=&quot;companies-spend-more-money-per-engineer-than-before&quot;&gt;Companies spend more money per engineer than before&lt;/h2&gt;
&lt;p&gt;Now look at what each company spends on its engineering and research efforts. To compare across years, I used a simple method. Assume that about thirty percent of the workforce is engineering and use R&amp;amp;D or a similar cost as a proxy for engineering investment. It is not perfect, but it shows direction.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Estimated Engineers&lt;/th&gt;
&lt;th&gt;Investment (R&amp;amp;D or FCF)&lt;/th&gt;
&lt;th&gt;Investment per Engineer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Meta&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;~13,483&lt;/td&gt;
&lt;td&gt;~$10.3B&lt;/td&gt;
&lt;td&gt;~$760k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;~22,220&lt;/td&gt;
&lt;td&gt;~$43.9B&lt;/td&gt;
&lt;td&gt;~$1.98M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;~35,670&lt;/td&gt;
&lt;td&gt;~$30.97B&lt;/td&gt;
&lt;td&gt;~$870k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;~54,997&lt;/td&gt;
&lt;td&gt;~$72.76B&lt;/td&gt;
&lt;td&gt;~$1.32M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So companies are spending more money per engineer than before. Not less. That does not sound like replacement. It sounds like deeper investment. Fewer generalists. More specialized people. More infrastructure. More complex systems. More AI operations. More cost per seat. This is not what a company does when it is trying to remove engineers from the equation. This is what a company does when it is trying to level up.&lt;/p&gt;
&lt;h2 id=&quot;why-the-engineer-layoffs-feel-confusing&quot;&gt;Why the engineer layoffs feel confusing&lt;/h2&gt;
&lt;p&gt;The AV industry taught me that hype can inflate teams far beyond what reality can support. When the correction comes, people feel blindsided because they only remember the hiring wave, not the assumptions that created it. The same thing is happening now. Engineers see cuts, but they do not see the earlier hiring spike that caused the imbalance. They see AI headlines, but they do not see the rising investment per engineer. They feel the pain, but they do not see the context.&lt;/p&gt;
&lt;p&gt;If you work in tech today, your job is not evaporating. The shape of the work is just shifting. The companies are cutting the soft edges, the duplicated roles, the nice-to-have teams. They are keeping the people who understand systems, scale, and actual engineering. That is not a philosophical trend. It is just how budgets behave when the hype dies down.&lt;/p&gt;
&lt;p&gt;And yes, layoffs are rough. I lived through enough of them to know how it feels from the inside. But pointing at AI as the main cause is an easy way to avoid looking at the real picture. The hiring boom was unrealistic from day one, and the correction was coming no matter what models we built. The only surprise is that it took this long.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.macrotrends.net/stocks/charts/META/meta-platforms/number-of-employees&quot;&gt;Meta employee counts for 2019 and historical values. Macrotrends.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://investor.atmeta.com&quot;&gt;Meta employee count for 2024. Meta Q4 2024 earnings release.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.macrotrends.net/stocks/charts/GOOGL/alphabet/number-of-employees&quot;&gt;Google employee counts for 2019 and 2024. Macrotrends.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.marketbeat.com/stocks/NASDAQ/META/financials&quot;&gt;Meta R&amp;amp;D spending for 2019. MarketBeat financial summary.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.macrotrends.net/stocks/charts/META/meta-platforms/research-development-expenses&quot;&gt;Meta R&amp;amp;D spending for 2024. Macrotrends.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.macrotrends.net/stocks/charts/GOOGL/alphabet/free-cash-flow&quot;&gt;Alphabet free cash flow for 2019. Macrotrends.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://mlq.ai/stocks/GOOG.NE/free-cash-flow&quot;&gt;Alphabet free cash flow for 2024. MLQ.ai financial summary.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://electroiq.com/stats/how-many-people-work-at-meta&quot;&gt;Engineering headcount ratio assumption. ElectroIQ analysis.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>Industry Analysis</category><category>Engineering Career</category><category>Economics</category></item><item><title>Identifying Video Streams Using RTP Header Extensions</title><link>https://sysdev.me/2025/11/09/identifying-video-streams-using-rtp-header-extensions/</link><guid isPermaLink="true">https://sysdev.me/2025/11/09/identifying-video-streams-using-rtp-header-extensions/</guid><description>When a system manages dozens of cameras or edge devices, packets alone don’t tell you much.</description><pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/Birch-Grove.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/Birch-Grove.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Birch Grove near Bishkek. The grove is incredibly popular in the autumn.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When a system manages dozens of cameras or edge devices, packets alone don’t tell you much. An IP and port might change, SSRCs can roll over, and NATs tend to shuffle everything just enough to break simple assumptions. Yet every media packet still needs a clear identity — not for transport, but for logic.&lt;/p&gt;
&lt;p&gt;There are many ways to attach that identity: control channels, per-session negotiation, external registries. But the most simple one is already part of RTP itself — the header extension defined by &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8285&quot;&gt;RFC 8285&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;RTP was designed to be extensible. After the fixed header and payload, packets can carry short metadata blocks called &lt;em&gt;header extensions&lt;/em&gt;. Each extension has a small numeric ID and a URI describing its purpose.&lt;/p&gt;
&lt;p&gt;That makes it an ideal place for lightweight metadata such as a unique stream identifier. The ID repeats on every packet and travels through NATs, relays, and tunnels with zero signaling overhead. It’s simple, deterministic, and standardized.&lt;/p&gt;
&lt;p&gt;Quite often you need t o create an additional plugin for GStreamr, but, furtunatly, not in this case. GStreamer already exposes the right API hooks through &lt;code&gt;gstrtpbuffer.h&lt;/code&gt; and &lt;code&gt;gstrtphdrext.h&lt;/code&gt;. One pad probe is enough to inject an identifier before the payload leaves the payloader.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;lt;gst/gst.h&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;lt;gst/rtp/gstrtpbuffer.h&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;lt;gst/rtp/gstrtphdrext.h&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GstPadProbeReturn &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;add_ext_probe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(GstPad &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;pad&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, GstPadProbeInfo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, gpointer &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;user_data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GstBuffer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;buf &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GST_PAD_PROBE_INFO_BUFFER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(info);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;buf)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GST_PAD_PROBE_PASS;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    buf &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; gst_buffer_make_writable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GstRTPBuffer rtp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GST_RTP_BUFFER_INIT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gst_rtp_buffer_map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buf, GST_MAP_WRITE, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rtp))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        guint8 &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;34&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;56&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;78&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // Stream ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        gst_rtp_buffer_add_extension_onebyte_header&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rtp, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, data, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        gst_rtp_buffer_unmap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rtp);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    GST_PAD_PROBE_INFO_DATA&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(info) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; buf;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GST_PAD_PROBE_PASS;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sender can advertise the mapping:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;extmap-1=urn:example:stream-id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the receiver can decode it using the same API. No external signaling, no synchronization delay. Each packet holds data to tell who it belongs to.&lt;/p&gt;
&lt;h2 id=&quot;why-it-matters&quot;&gt;Why it matters&lt;/h2&gt;
&lt;p&gt;Media servers often depend on connection state to classify incoming data. When a camera reconnects, the backend has to guess whether this new socket belongs to the same stream or not. That guess introduces delay, synchronization issues, and sometimes packet loss. With an identity embedded inside RTP itself, the backend doesn’t need to guess. The stream is self-describing. Relays and recorders can demultiplex by ID, loggers can tag frames consistently, and analytics services can correlate metadata without waiting for control messages.&lt;/p&gt;
&lt;h2 id=&quot;performance-and-compliance&quot;&gt;Performance and compliance&lt;/h2&gt;
&lt;p&gt;The extension adds roughly five bytes per packet — one-byte header, one-byte ID, and the payload itself. For &lt;code&gt;H.264&lt;/code&gt; or &lt;code&gt;H.265&lt;/code&gt; streams, that’s less than 0.1 % bandwidth overhead.&lt;/p&gt;
&lt;p&gt;Because it’s part of RFC 8285, it’s backward-compatible: receivers that don’t recognize the extension simply skip it. No negotiation cost, no version mismatch.&lt;/p&gt;
&lt;h2 id=&quot;verification&quot;&gt;Verification&lt;/h2&gt;
&lt;p&gt;You can use any tool you like to capture traffick and check for RTP Header Extension in captured traffic. It will contain:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RTP Header Extension&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  One-Byte Header&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ID: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Data: 12 34 56 78&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The complete example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can find example &lt;a href=&quot;https://github.com/astavonin/streaming/tree/main/RTP-RFC-8285&quot;&gt;here&lt;/a&gt;. It was tested using Ubuntu 24.04 and GStreamer ≥1.18.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>GStreamer</category><category>Video Streaming</category><category>C++</category></item><item><title>Multilingual benchmarking project =&gt; Bazel for advanced engineering</title><link>https://sysdev.me/2025/08/01/multilingual-benchmarking-project--bazel-for-advanced-engineering/</link><guid isPermaLink="true">https://sysdev.me/2025/08/01/multilingual-benchmarking-project--bazel-for-advanced-engineering/</guid><description>When working on performance experiments across C++ and Go, you obviously need a multilingual project structure.</description><pubDate>Fri, 01 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/molly-tash-rabat.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/molly-tash-rabat.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    In her one year, Molly saw many more exciting places than I did until I was about 28. She does pretty well :-D
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When working on performance experiments across C++ and Go, you obviously need a multilingual project structure. There were two paths forward: create separate build systems under a shared repository, or consolidate everything under a single, coherent framework. Bazel made that decision easy.&lt;/p&gt;
&lt;p&gt;Using Bazel to unify builds isn’t just convenient—it should be the default choice for any serious engineering effort that involves multiple languages. It eliminates the friction of managing isolated tools, brings deterministic builds, and handles dependencies, benchmarking, and cross-language coordination with minimal ceremony.&lt;/p&gt;
&lt;p&gt;Here’s why Bazel makes sense for performance-critical, multilingual projects like this one—no fragile tooling, no redundant setups, just clean integration that scales.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The project I’m talking about is available &lt;a href=&quot;https://github.com/astavonin/perf-tests&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;structuring-a-multilingual-bazel-project&quot;&gt;Structuring a Multilingual Bazel Project&lt;/h2&gt;
&lt;p&gt;I do not need any complicated structures here, as this repo is only about performance testing on multiple languages. For now, it’s only Go and C++, but because of Bazel, it would be pretty easy to add additional language support.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;perf-tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daily-temps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BUILD.bazel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daily_temperatures.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daily_temperatures.h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   └──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daily_temperatures_benchmark.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   └──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BUILD.bazel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daily_temperatures.go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       └──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daily_temperatures_test.go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MODULE.bazel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;└──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BUILD.bazel&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Structure is also standard for Bazel and covers all my needs. Each test has a separate language-specific folder. Considering the goal of comparing implementation on different languages, this is precisely what I need and is fully covered by Bazel out of the box.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-bazel-modules-approach&quot;&gt;The Modern Bazel Modules Approach&lt;/h2&gt;
&lt;p&gt;Bazel used to rely on &lt;code&gt;WORKSPACE&lt;/code&gt; files for dependency and toolchain management, but that approach quickly turns into a mess—too much boilerplate, too little structure. &lt;code&gt;MODULE.bazel&lt;/code&gt; is the modern replacement, and it actually gets the job done. It lets you declare dependencies with proper versioning, avoids the usual transitive hell, and makes projects easier to share and compose. Instead of treating every repo like a silo, modules help build real ecosystems.&lt;/p&gt;
&lt;p&gt;A typical module setup for a Go + C++ performance project may look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;module(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    name = &quot;perf_tests&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    version = &quot;0.1.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bazel_dep(name = &quot;rules_go&quot;, version = &quot;0.55.1&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bazel_dep(name = &quot;googletest&quot;, version = &quot;1.14.0.bcr.1&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bazel_dep(name = &quot;google_benchmark&quot;, version = &quot;1.9.2&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;go_sdk = use_extension(&quot;@rules_go//go:extensions.bzl&quot;, &quot;go_sdk&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;go_sdk.download(version = &quot;1.23.1&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s pretty evident that you have a much clearer separation between configuration and implementation with modules compared with the old &lt;code&gt;WORKSPACE&lt;/code&gt; files approach. I love how it makes dependencies explicit and maintainable!&lt;/p&gt;
&lt;h2 id=&quot;bazels-advantage-over-cmake-and-go-build-systems&quot;&gt;Bazel’s Advantage over CMake and Go Build Systems&lt;/h2&gt;
&lt;p&gt;Bazel handles multiple languages out of the box. Trying to do the same with CMake and Go’s native tooling is either painful or flat-out unworkable. CMake is great (primarily for historical reasons) for C++, but anything beyond that usually means wrestling with clunky extensions. Go’s build system works well—for Go—but it has zero awareness of external toolchains or how to wire them together. There’s no clean way to coordinate builds across both without gluing everything together by hand.&lt;/p&gt;
&lt;p&gt;This is where Bazel actually delivers. It builds C++ and Go in the same context, tracks dependencies across both, and rebuilds only what’s necessary—no wrappers, no hacks. Outputs are reproducible regardless of language, which means CI doesn’t have to guess, and toolchain drift isn’t a thing.&lt;/p&gt;
&lt;h2 id=&quot;joining-multilingual-performance-tests-with-bazel-tags&quot;&gt;Joining Multilingual Performance Tests with Bazel Tags&lt;/h2&gt;
&lt;p&gt;Running benchmarks across languages is usually messy—different tools, different assumptions, no common entry point. Bazel simplifies this by letting you tag benchmark targets regardless of language. With consistent tags in place, you can query and run all benchmarks from a single script without needing language-specific logic. You can write it up pretty quickly:&lt;/p&gt;
&lt;h3 id=&quot;tagging-benchmark-targets&quot;&gt;Tagging Benchmark Targets&lt;/h3&gt;
&lt;p&gt;C++ benchmark (&lt;code&gt;daily-temps/cpp/BUILD.bazel&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cc_binary(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    name = &quot;daily_temperatures_benchmark&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    srcs = [&quot;daily_temperatures_benchmark.cpp&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    deps = [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &quot;:daily_temperatures&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &quot;@google_benchmark//:benchmark_main&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    tags = [&quot;benchmark&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go benchmark (&lt;code&gt;daily-temps/go/BUILD.bazel&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;go_test(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    name = &quot;daily_temperatures_test&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    srcs = [&quot;daily_temperatures_test.go&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    embed = [&quot;:daily_temperatures&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    importpath = &quot;perf-tests/daily-temps/go&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    tags = [&quot;benchmark&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;running-all-benchmarks-via-bash&quot;&gt;Running All Benchmarks via Bash&lt;/h3&gt;
&lt;p&gt;With all benchmarks tagged consistently, a simple Bash script can query and execute benchmarks for multiple languages at once:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -euo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pipefail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Executing Go benchmarks...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bazel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; test&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --test_output=streamed&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --test_arg=-test.bench=.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --test_arg=-test.benchmem&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --test_tag_filters=benchmark&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --cache_test_results=no&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Executing C++ benchmarks...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cpp_benchmarks&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bazel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;attr(tags, &quot;benchmark&quot;, kind(cc_binary, //...))&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bench &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $cpp_benchmarks; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Running &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$bench&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  bazel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -c&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; opt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$bench&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It might look crude, but using tags with a Bash runner is hard to beat for flexibility and portability. Bazel doesn’t natively support “run all benchmarks,” and this approach gives you exactly that—without reinventing anything. It scales across languages, plays well with CI, and stays out of the way.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Bazel handles complex multilingual builds in a way that legacy tools like CMake or language-specific systems simply don’t. With modules, tagging, and minimal scripting, even mixed-language benchmarks become easy to manage. For engineers who care about repeatability, clean integration, and performance visibility across stacks, Bazel isn’t just an option—it’s the default.&lt;/p&gt;
</content:encoded><category>Tooling</category><category>Go</category><category>C++</category><category>Bazel</category></item><item><title>Functional Programming style in C++</title><link>https://sysdev.me/2025/06/21/functional-programming-style-in-c/</link><guid isPermaLink="true">https://sysdev.me/2025/06/21/functional-programming-style-in-c/</guid><description>I’ve been on a bit of a Leetcode streak lately, poking at problems from companies I secretly admire.</description><pubDate>Sat, 21 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/Teskey-Torpok_Pass.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/Teskey-Torpok_Pass.jpg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Teskey-Torpok Pass is a lovely pass leading you to Song-Kol Lake, Naryn region.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I’ve been on a bit of a Leetcode streak lately, poking at problems from companies I secretly admire. To keep things interesting (and to avoid nodding off in front of the screen), I challenged myself to solve the same task three ways: good old C++, its shiny modern C++20 cousin, and Elixir. It turns out that staring at a problem through a functional programming lens is like putting on X-ray specs—you see the same lines of code, but suddenly, there’s a weird beauty in that filter chain.&amp;lt;&lt;/p&gt;
&lt;p&gt;My first pass was as traditional as it gets—a simple C++ class with an array and a loop. No magic here, just pushing timestamps and scanning them one by one. The implementation is pretty naive, but considering the constraints provided by the &lt;a href=&quot;https://leetcode.com/problems/design-hit-counter&quot;&gt;Design a hit counter&lt;/a&gt; challenge, even an unscalable approach is acceptable. So, we will push all new timestamps into the &lt;del&gt;&lt;code&gt;std::queue&lt;/code&gt;&lt;/del&gt; &lt;code&gt;std::vector&lt;/code&gt; and then simply count.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HitCounter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamps_;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; constexpr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HITS_PERIOD &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 300&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    HitCounter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        timestamps_.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;reserve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; timestamp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        timestamps_.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push_back&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getHits&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; timestamp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; delta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HITS_PERIOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;timestamps_.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;begin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); it &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamps_.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); it&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamp) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;it &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; delta) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                res&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, yes, this solution is completely compliant with the requirements and passes all tests. But it’s hideous, isn’t it?&lt;/p&gt;
&lt;p&gt;Next up was the Elixir version, where I leaned on an &lt;code&gt;Agent&lt;/code&gt; to hold my list of timestamps. Elixir doesn’t let you mutate global state willy-nilly, so I spun up a small process to mind that list for me. Here’s the gist:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HitCounter&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  @hits_period &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;300&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  @agent_name &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__MODULE__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  @spec &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;init_&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :ok&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, any}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; init_&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Process&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;whereis&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(@agent_name) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      Agent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(@agent_name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Agent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;name:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; @agent_name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    |&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; case&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;_pid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :ok&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      error &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  @spec &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; integer) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; :ok&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; is_integer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Agent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(@agent_name, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamps &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamps &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [timestamp] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  @spec &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get_hits&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; integer) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; integer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_hits&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; is_integer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(timestamp) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    lower_bound &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; @hits_period&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Agent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(@agent_name, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamps &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      timestamps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      |&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Stream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;take_while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      |&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Stream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lower_bound &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      |&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Enum&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    end&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why an &lt;code&gt;Agent&lt;/code&gt;? Because each test run in Leetcode’s harness executes sequentially without dropping your module’s state. If you don’t stop the Agent on &lt;code&gt;init_/0&lt;/code&gt;, you end up with phantom hits from previous tests—state leaks faster than coffee through a paper filter. I guarantee a clean slate every time by explicitly stopping any existing Agent before starting fresh.&lt;/p&gt;
&lt;p&gt;Finally, I couldn’t resist a modern C++ take using C++20 &lt;code&gt;ranges&lt;/code&gt; and &lt;code&gt;views&lt;/code&gt;. No loops, no manual counters—just a pipeline that reads like a little poem:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getHits&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; timestamp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; delta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HITS_PERIOD;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ranges&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;distance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        timestamps_&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;views&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;take_while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; ts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; timestamp;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // stop processing beyond this point&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;views&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; ts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; delta;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s concise, lazy, and stops early once hits exceed the current timestamp—exactly mirroring our original loop logic. Feels a bit like writing haiku in code: short lines, precise meaning, and a dash of Zen.&lt;/p&gt;
&lt;p&gt;Mixing paradigms this way has been surprisingly refreshing. You refactor the same logic through different lenses and discover nuances you’d never notice if you stuck to one language. Plus, it’s way more entertaining than another unadorned C++ loop.&lt;/p&gt;
</content:encoded><category>C++</category><category>Elixir</category><category>LeetCode</category></item><item><title>Fixing C++ lacks switches on strings</title><link>https://sysdev.me/2025/02/17/fixing-c-lacks-switches-on-strings/</link><guid isPermaLink="true">https://sysdev.me/2025/02/17/fixing-c-lacks-switches-on-strings/</guid><description>C++ is a powerful language, and I genuinely love it, but sometimes, even in modern versions, it lacks some surprisingly simple features.</description><pubDate>Mon, 17 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/birch-grove-fog.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/birch-grove-fog.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Birch Grove is just 40 minutes from Bishkek. Although I was concerned about the strong, foggy weather, it was an amazing opportunity for photography!
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;C++ is a powerful language, and I genuinely love it, but sometimes, even in modern versions, it lacks some surprisingly simple features. One such missing feature is switch on &lt;code&gt;std::string&lt;/code&gt;. You’d think that by now, we could use a switch statement on strings just like we do with integers or enums—after all, Go has it! But no, C++ keeps us on our toes.&lt;/p&gt;
&lt;p&gt;Why Doesn’t C++ Support switch on strings? Because “you only pay for what you use,” which is the standard C++ mantra. The switch statement in C++ relies on integral types. Under the hood, it works by converting the case values into jump table indices for efficient execution. But &lt;code&gt;std::string&lt;/code&gt; (or even &lt;code&gt;std::string_view&lt;/code&gt;) is not an integral type—it’s a more complex data structure. That’s why you can’t simply do:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (msg-&amp;gt;get_value&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()) {&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // Nope, not possible :-(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;topology&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Handle network topology&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;broadcast&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Handle network broadcast&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So… how do we work around this? We need a way to turn strings into something &lt;code&gt;switch&lt;/code&gt; can handle—like an integer. And that’s where hashing comes in. If we can map strings to unique integer values at compile time, we can switch on those instead. To do so, we need a user-defined &lt;code&gt;literal operator&lt;/code&gt; and compile-time hash function. Something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;constexpr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; uint64_t&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;string_view&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    uint64_t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hash &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chr : str) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hash &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (hash &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 131&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chr;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hash;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;constexpr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; uint64_t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; operator&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; char*&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;size_t&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;std&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;string_view&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(str, len));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, no magic, we have &lt;code&gt;switch&lt;/code&gt; on strings!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(msg-&amp;gt;get_value&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;::data_type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;topology&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;_hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Handle topology case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;broadcast&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;_hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Handle broadcast case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Handle unknown case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are some downsides to using this approach, specifically regarding hash collisions. Hashing introduces a small, but non-zero, risk of collisions. The proposed hash function, which multiplies the current hash by 131 and adds each character, is sufficiently effective for this use case. It provides a fast, compile-time method to map short, distinct string literals to unique integer values.&lt;/p&gt;
&lt;p&gt;The choice of 131 as a multiplier contributes to achieving a reasonable distribution of values, thus reducing the likelihood of collisions, especially for a limited set of keywords like “topology,” “broadcast,” and others. The number 131 is commonly used in simple hash functions because it is a prime number. This helps to spread out the hash values more evenly across the available space, minimizing clustering and improving distribution properties. Additionally, it is small enough to maintain fast calculations while ensuring sufficient entropy accumulation over short strings.&lt;/p&gt;
&lt;p&gt;Since the hash is computed at compile-time (using &lt;code&gt;constexpr&lt;/code&gt;), there is no runtime overhead, and the resulting integer comparisons in switch statements remain &lt;strong&gt;O(1)&lt;/strong&gt;. This makes this approach both efficient and practical for situations involving a limited number of predefined strings. Furthermore, the risk of hash collisions is exceedingly low for a small number of strings. Using the Birthday Problem approximation, we can estimate that even with 1,000 unique strings, the collision probability remains below 0.000003%, making it practically irrelevant for most real-world applications where a switch statement would be used.&lt;/p&gt;
&lt;p&gt;It’s ridiculous that in 2025, we still have to hack our way around something as basic as &lt;code&gt;switch&lt;/code&gt; on strings in C++. But on the bright side, this was a perfect excuse to use a user-defined &lt;code&gt;literal (operator&quot;&quot;)&lt;/code&gt; finally! It’s one of those C++ features you rarely use, but it feels like a wizard when you do.&lt;/p&gt;
</content:encoded><category>C++</category><category>Optimizations</category></item><item><title>AI, Software Engineering, and the Evolution of Code Generation</title><link>https://sysdev.me/2025/02/05/ai-software-engineering-and-the-evolution-of-code-generation/</link><guid isPermaLink="true">https://sysdev.me/2025/02/05/ai-software-engineering-and-the-evolution-of-code-generation/</guid><description>Mark Zuckerberg recently made a bold statement: AI will soon take over the work of mid-level engineers (Forbes).</description><pubDate>Wed, 05 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/mountains.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/mountains.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    From a weekend trip to a nearby gorge. Why is it here? Because I love the mountains of Kyrgyzstan!
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Mark Zuckerberg recently made a bold statement: AI will soon take over the work of mid-level engineers &lt;a href=&quot;https://www.forbes.com/sites/quickerbettertech/2025/01/26/business-tech-news-zuckerberg-says-ai-will-replace-mid-level-engineers-soon&quot;&gt;(Forbes)&lt;/a&gt;. While this may sound like another tech CEO hyping AI, my latest experience with OpenAI’s o3-mini-high model suggests he might not be too far off.&lt;/p&gt;
&lt;p&gt;Thanks to DeepSeek, OpenAI was compelled to make o3-mini-high available in a regular ChatGPT subscription instead of locking it behind a steep $200 paywall. I would never pay original $200 for a model, but since I already have the regular ChatGPT subscription, it was an obvious choice to try it out. With this in mind, I decided to experiment: Could o3-mini-high generate a functional Go codebase for my &lt;a href=&quot;https://github.com/astavonin/gfsm&quot;&gt;GFSM library&lt;/a&gt;?&lt;/p&gt;
&lt;h2 id=&quot;the-experiment&quot;&gt;The experiment&lt;/h2&gt;
&lt;p&gt;For context, &lt;a href=&quot;https://github.com/astavonin/gfsm&quot;&gt;GFSM&lt;/a&gt; is my Go Finite State Machine library, and I needed a new generator to extract and save state machines in formats like &lt;a href=&quot;https://plantuml.com&quot;&gt;PlantUML&lt;/a&gt; and &lt;a href=&quot;https://mermaid.js.org&quot;&gt;Mermaid&lt;/a&gt;. Writing such a generator requires a solid understanding of Go’s &lt;a href=&quot;https://pkg.go.dev/go/ast&quot;&gt;Abstract Syntax Tree (AST) package&lt;/a&gt;, something I hadn’t used in years.
Instead of writing the code myself, I handed most of the heavy lifting to o3-mini-high. The result? Almost all the code for the generator was AI-generated, with minimal manual adjustments. You can check out the generated code in this &lt;a href=&quot;https://github.com/astavonin/gfsm/commit/e9f6296ce6b52eefdaa9848cb1bbb8480c241611&quot;&gt;GitHub commit&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Code Quality: Mid-Level Engineer Competency (But Better Documentation)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The AI-generated code was not just functional—it worked on the first attempt! Unfortunately, it still needed some refinements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improving logging and error handling&lt;/li&gt;
&lt;li&gt;Refactoring functions for better readability and maintainability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, the output quality was comparable to that of a solid mid-level engineer—not perfect, but good enough. But here’s the kicker: Mid-level engineers usually suck at writing documentation (if they write any at all), while the &lt;a href=&quot;https://github.com/astavonin/gfsm/blob/main/GFSM_UML.md&quot;&gt;documentation generated by o3-mini-high&lt;/a&gt; was actually pretty nice—clear, structured, and covering all the necessary details.&lt;/p&gt;
&lt;p&gt;My productivity gains may not be impressive, but they are reasonably good for a first attempt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time spent “coding” with ChatGPT: 3 hours&amp;lt;&lt;/li&gt;
&lt;li&gt;The time it would have taken manually (my estimation): 6+ hours&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The biggest time-saver? AI handled the AST package interactions, which would have required me to refresh my knowledge.&lt;/p&gt;
&lt;h2 id=&quot;the-future-of-software-engineering&quot;&gt;The Future of Software Engineering&lt;/h2&gt;
&lt;p&gt;What does this mean for engineers? I see this as an evolutionary shift rather than a revolutionary one—similar to previous transitions from Assembly to C or from C to Java. AI isn’t outright replacing engineers; it’s redefining what it means to be one.&lt;/p&gt;
&lt;p&gt;The reality is that “boot camp engineers” and all other types of IT specialists without fundamental education are likely obsolete. Learning how to piece together APIs or follow tutorials won’t be enough when AI can do it better and faster, with approximately the same number of mistakes. Instead, fundamental Computer Science education—actual deep IT knowledge—becomes more critical than ever.&lt;/p&gt;
&lt;p&gt;Why? Because understanding how and why a system works will distinguish those who can drive AI-assisted development from those who are replaced by it. Engineers with a strong foundation in algorithms, data structures, OS internals, networking, cybersecurity, and distributed systems will remain invaluable.&lt;/p&gt;
&lt;p&gt;The AI-driven migration process will not be fast or simple. Even at first glance, there are a significant number of challenges ahead.&lt;/p&gt;
&lt;p&gt;The crucial part of the AI migration challenge is system verification. If AI is going to generate more and more of our code, who is going to verify that it actually works? This is where Software Development Engineers in Test (SDET), or rather their next evolution—Software Verification Engineers (SVE), become indispensable. SVE will focus not just on functional correctness but also on formal verification, property-based testing, and system reliability. AI can generate code, but it doesn’t inherently know if it’s correct, secure, or efficient—that’s where human engineers must step in.&lt;/p&gt;
&lt;p&gt;Another major challenge? AI-generated code will include security vulnerabilities. It’s not a question of if but when. AI models don’t “understand” security—they just generate plausible-looking code. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secure coding practices must be enforced at the architecture level.&lt;/li&gt;
&lt;li&gt;Penetration testing and threat modeling will be more crucial than ever.&lt;/li&gt;
&lt;li&gt;Automated security analysis tools will need to evolve alongside AI-generated code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Security engineers will no longer be an “optional” hire for big enterprises. Every AI-assisted development team will need a strong cybersecurity presence, or they risk shipping highly vulnerable software.&lt;/p&gt;
&lt;p&gt;And the ultimate pain in the ass? AI suggests solutions for AI-generated code. This is where I start crying :-D Right now, AI is great at creating “reasonable-looking” code but lacks true reasoning about correctness and intent. This means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AI suggests code that works—but may be subtly wrong.&lt;/li&gt;
&lt;li&gt;The engineer spots a bug and asks AI for a fix.&lt;/li&gt;
&lt;li&gt;AI proposes a fix—but does it actually solve the problem, or does it introduce new ones?&lt;/li&gt;&lt;/li&gt;
&lt;li&gt;Repeat until frustration kicks in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This loop is already frustrating enough when debugging human-written code. With AI generating both the problem and the solution, we might end up in an infinite AI-fix cycle, in which engineers waste more time validating AI’s reasoning than fixing it manually.&lt;/p&gt;
&lt;p&gt;How do I see my future? It’s perfectly incredible! With AI tools, I can do more, build faster, and &lt;em&gt;&lt;strong&gt;focus on higher-level problem-solving&lt;/strong&gt;&lt;/em&gt; instead of getting bogged down in boilerplate coding. AI is an amplifier, not a replacement—it extends my capabilities rather than diminishing my value.&lt;/p&gt;
&lt;p&gt;And as for job security? I’m not worried in the slightest. With my specialization and experience in system architecture, distributed computing, and software engineering at scale, I will always have a place in this industry. If anything, AI clears the way for me to work on more interesting, complex problems—things AI alone can’t solve.&lt;/p&gt;
</content:encoded><category>Go</category><category>AI</category></item><item><title>How to compile C++ in 2025. Bazel or CMake?</title><link>https://sysdev.me/2025/01/20/how-to-compile-c-in-2025-bazel-or-cmake/</link><guid isPermaLink="true">https://sysdev.me/2025/01/20/how-to-compile-c-in-2025-bazel-or-cmake/</guid><description>Today, we’re examining two modern build systems for C++: CMake, the industry favorite, and Bazel, a powerful alternative.</description><pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/cpp_bazel_and_cmake.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/cpp_bazel_and_cmake.png&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;Today, we’re examining two modern build systems for C++: &lt;a href=&quot;https://cmake.org&quot;&gt;CMake&lt;/a&gt;, the industry favorite, and &lt;a href=&quot;https://bazel.build&quot;&gt;Bazel&lt;/a&gt;, a powerful alternative. While CMake is often the default choice, I believe that approach warrants a bit more scrutiny—after all, we’re focusing on modern tools here (yep, not counting Make, right?). To explore this, I’ve created a practical demo project showcasing how both systems manage a real-world scenario.&lt;/p&gt;
&lt;p&gt;Using the &lt;a href=&quot;https://github.com/astavonin/maelstrom-challenges&quot;&gt;maelstrom-challenges&lt;/a&gt; project as a starting point, I’ve extracted a C++ library called &lt;a href=&quot;https://github.com/astavonin/maelstrom-node&quot;&gt;maelstrom-node&lt;/a&gt;. This library has been set up to work seamlessly with both &lt;strong&gt;Bazel&lt;/strong&gt; and &lt;strong&gt;CMake&lt;/strong&gt;, giving us a hands-on comparison of their approaches, strengths, and quirks.&lt;/p&gt;
&lt;h2 id=&quot;the-project-structure&quot;&gt;The Project Structure&lt;/h2&gt;
&lt;p&gt;Here’s what the final directory layout for maelstrom-node looks like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;➜&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  maelstrom-node&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; git:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;tree&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; BUILD.bazel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CMakeLists.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CMakePresets.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; LICENSE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MODULE.bazel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MODULE.bazel.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; include&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   └──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; maelstrom-node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; handler.hpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; message.hpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       └──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; node.hpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CMakeLists.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   ├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; message.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;│&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   └──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; node.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; vcpkg-configuration.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;└──&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; vcpkg.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this structure in place, the library could seamlessly integrate with both Bazel and CMake while maintaining clarity and modularity.&lt;/p&gt;
&lt;h2 id=&quot;building-with-bazel&quot;&gt;Building with Bazel&lt;/h2&gt;
&lt;p&gt;Bazel provided a relatively straightforward approach to compile &lt;a href=&quot;https://github.com/astavonin/maelstrom-node&quot;&gt;maelstrom-node&lt;/a&gt;. The key was to define two critical files: &lt;code&gt;MODULE.bazel&lt;/code&gt; and &lt;code&gt;BUILD.bazel&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;modulebazel&quot;&gt;MODULE.bazel&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;MODULE.bazel&lt;/code&gt; file in Bazel serves as a central place to define project metadata and manage dependencies. In this file, we use &lt;code&gt;bazel_dep&lt;/code&gt; for some dependencies like spdlog and &lt;code&gt;nlohmann_json&lt;/code&gt;, but not for &lt;code&gt;rules_boost&lt;/code&gt;. Why the difference? It all comes down to how these dependencies are distributed and managed in Bazel’s ecosystem.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;module(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;maelstrom_node&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1.0.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    compatibility_level&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Adding spdlog and nlohmann_json via Bazel Dependency Archive&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;spdlog&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1.15.0.bcr.3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;nlohmann_json&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;3.11.3.bcr.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Adding rules_boost manually due to its unavailability in Bazel Central Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rules_boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;repo_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;com_github_nelhage_rules_boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;archive_override(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    module_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rules_boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    integrity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;sha256-ZLcmvYKc2FqgLvR96ApPXxp8+sXKqhBlCK66PY/uFIo=&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    strip_prefix&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rules_boost-e3adfd8d6733c914d2b91a65fb7175af09602281&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    urls&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://github.com/nelhage/rules_boost/archive/e3adfd8d6733c914d2b91a65fb7175af09602281.tar.gz&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;bazel_dep&lt;/code&gt; function simplifies dependency management by fetching dependencies from the &lt;a href=&quot;https://registry.bazel.build&quot;&gt;Bazel Central Registry (BCR)&lt;/a&gt;—a curated collection of Bazel-compatible packages. Dependencies like &lt;strong&gt;spdlog&lt;/strong&gt; and &lt;strong&gt;nlohmann_json&lt;/strong&gt; are available in the BCR, which makes adding them straightforward and efficient. Using BCR ensures version consistency and eliminates the need for custom configuration.&lt;/p&gt;
&lt;h3 id=&quot;why-not-use-bazel_dep-for-rules_boost&quot;&gt;Why Not Use &lt;code&gt;bazel_dep&lt;/code&gt; for &lt;code&gt;rules_boost&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;While popular, the &lt;code&gt;rules_boost&lt;/code&gt; library is not often updated in the Bazel Central Registry, and while we have 1.86 release publicly available, BCR includes only 1.83. That’s why, we &lt;em&gt;&lt;strong&gt;may&lt;/strong&gt;&lt;/em&gt; need to configure its source using &lt;code&gt;archive_override&lt;/code&gt; manually. This involves specifying details like the URL of the archive, the integrity hash, and the strip prefix.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;bazel_dep&lt;/code&gt; wherever possible simplifies project configuration by leveraging the Bazel Central Registry, ensuring that dependencies are well-maintained and easy to integrate. However, when libraries like &lt;code&gt;rules_boost&lt;/code&gt; aren’t available in the registry, Bazel’s manual configuration options, like &lt;code&gt;archive_override&lt;/code&gt;, allow us to include them. This dual approach showcases Bazel’s flexibility and ability to adapt to different project needs.&lt;/p&gt;
&lt;h3 id=&quot;buildbazel&quot;&gt;BUILD.bazel&lt;/h3&gt;
&lt;p&gt;We cannot proceed without &lt;code&gt;BUILD.bazel&lt;/code&gt;, as it serves as a location to store the actual app or library information.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cc_library(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;maelstrom_node&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;src/message.cpp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;src/node.cpp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    hdrs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; glob([&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;include/maelstrom-node/**/*.hpp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    copts&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-std=c++23&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    includes&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;include&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    visibility&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;//visibility:public&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    deps&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@boost//:algorithm&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@boost//:asio&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@nlohmann_json//:json&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@spdlog&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;includes = [&quot;include&quot;]&lt;/code&gt; directive ensures Bazel can resolve headers using shorter paths like &lt;code&gt;#include &quot;maelstrom-node/message.hpp&quot;&lt;/code&gt; instead of full paths like &lt;code&gt;#include &quot;include/maelstrom-node/message.hpp&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To build the library, simply run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bazel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; //...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;building-with-cmake&quot;&gt;Building with CMake&lt;/h2&gt;
&lt;p&gt;Adapting maelstrom-node for CMake brought its own set of challenges and solutions, exposing the limitations of this ancient technology. Despite its widespread use, even a straightforward migration effort highlighted CMake’s inherent bottlenecks and inefficiencies, reinforcing the idea that it might be time to consider moving on from it entirely.&lt;/p&gt;
&lt;h3 id=&quot;managing-includes&quot;&gt;Managing Includes&lt;/h3&gt;
&lt;p&gt;This is the first challenge you’ll face when trying to move from Bazel to CMake. CMake assumes &lt;code&gt;#include&lt;/code&gt; paths are relative to the include directory, so you write:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;maelstrom-node/message.hpp&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bazel, on the other hand, often requires full paths from the project root unless explicitly configured using the includes property in the &lt;code&gt;cc_library&lt;/code&gt; rule:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;include/maelstrom-node/message.hpp&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While CMake’s shorter paths may appear cleaner within the source code and more natural for C++ developers, they abstract away the file hierarchy, making it harder to understand the project structure at a glance. As Bazel requires, full paths provide better context by explicitly showing where a file is located in the directory tree, making navigation and maintenance easier, especially in larger projects.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-cmake-and-vcpkg&quot;&gt;Setting Up CMake and VCPKG&lt;/h3&gt;
&lt;p&gt;Using &lt;a href=&quot;https://vcpkg.io&quot;&gt;vcpkg&lt;/a&gt; as the package manager is one of the ways to simplify dependency management if you are a CMake fun. After installing vcpkg, I initialized the project and added the necessary ports. The following command will create &lt;code&gt;vcpkg-configuration.json&lt;/code&gt; and &lt;code&gt;vcpkg.json&lt;/code&gt; files:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vcpkg&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --application&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vcpkg&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; port&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; boost-algorithm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vcpkg&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; port&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; boost-asio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;vcpkg&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; port&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; nlohmann-json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another option for CMake dependency management is &lt;strong&gt;Conan&lt;/strong&gt;. However, based on my experience, it’s even more complex than &lt;strong&gt;VCPKG&lt;/strong&gt;, so I won’t discuss it in this post.&lt;/p&gt;
&lt;h3 id=&quot;making-a-cmakeliststxt&quot;&gt;Making a CMakeLists.txt&lt;/h3&gt;
&lt;p&gt;Managing dependencies was relatively streamlined using the vcpkg package manager. The toolchain file was included dynamically to ensure compatibility, allowing for flexibility when configuring the build environment. This setup reduces manual configuration efforts while ensuring that dependencies are correctly integrated:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cmake&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DEFINED&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CMAKE_TOOLCHAIN_FILE)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(CMAKE_TOOLCHAIN_FILE &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        CACHE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; STRING&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Vcpkg toolchain file&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;endif&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main part of the &lt;code&gt;CMakeLists.txt&lt;/code&gt; file is standard for CMake projects. The maelstrom_node library is defined as a static library with its core functionality implemented in message.cpp and node.cpp. Dependencies such as Boost, nlohmann_json, and spdlog are configured using &lt;code&gt;find_package&lt;/code&gt;, ensuring they are available during the build process.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cmake&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;add_library&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(maelstrom_node STATIC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    src/message.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    src/node.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;find_package&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Boost REQUIRED NO_MODULE)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;find_package&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(nlohmann_json REQUIRED)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;find_package&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(spdlog REQUIRED)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;target_include_directories&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(maelstrom_node &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PUBLIC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ${CMAKE_SOURCE_DIR}&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/include&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ${Boost_INCLUDE_DIRS}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;target_link_libraries&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(maelstrom_node &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;PUBLIC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    nlohmann_json::nlohmann_json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    spdlog::spdlog&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Boost::headers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;find_package&lt;/code&gt; command works seamlessly in this setup because the vcpkg toolchain file is integrated into the project configuration. This file, specified as &lt;code&gt;CMAKE_TOOLCHAIN_FILE&lt;/code&gt;, ensures that CMake knows the paths to all dependencies installed via vcpkg. When find_package is called, it searches these paths for the necessary configuration files, allowing dependencies like Boost, nlohmann_json, and spdlog to be located and linked automatically. This integration eliminates manual dependency management and ensures consistency across environments.&lt;/p&gt;
&lt;p&gt;Finally, the installation section of the file ensures that the library can be packaged and reused in other projects. This includes specifying where the compiled library should be installed and ensuring that headers are copied to the appropriate directories:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cmake&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(TARGETS maelstrom_node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    EXPORT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; maelstrom_nodeConfig&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ARCHIVE DESTINATION lib&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LIBRARY DESTINATION lib&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RUNTIME DESTINATION bin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(DIRECTORY &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;${CMAKE_SOURCE_DIR}&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/include/maelstrom-node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DESTINATION include/maelstrom-node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup ensures the maelstrom-node library is compiled correctly and packaged in a way that makes it easy to integrate into other projects. While CMake offers much flexibility, its verbosity and reliance on external tools like vcpkg highlight some of its limitations in managing dependencies efficiently. CMake provides multiple approaches to configure and build the project. Ideally, you could use a preset configuration for simplicity, such as (based on the official documentation):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cmake&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --preset=vcpkg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cmake&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --build&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach is clean and convenient, leveraging a preset defined in &lt;code&gt;CMakePresets.json&lt;/code&gt;. However, due to a &lt;a href=&quot;https://stackoverflow.com/questions/78046929/cannot-use-environment-variable-in-cmakepresets-json-with-cmaketools-in-vscode-t&quot;&gt;defect in Visual Studio Code’s CMake Tools&lt;/a&gt;, environment variables like &lt;code&gt;$VCPKG_ROOT&lt;/code&gt; cannot be directly used in &lt;code&gt;CMakePresets.json&lt;/code&gt;. As a result, the preset may fail to locate the toolchain file correctly.&lt;/p&gt;
&lt;p&gt;To address this issue, the following manual configuration proves reliable despite its ugliness:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cmake&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -S&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -B&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -DCMAKE_TOOLCHAIN_FILE=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$VCPKG_ROOT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;/scripts/buildsystems/vcpkg.cmake&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cmake&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --build&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both methods ultimately lead to the same result, but the manual approach is necessary in environments where preset limitations or toolchain file paths are not resolved automatically. This highlights a minor inconvenience in the CMake ecosystem that can complicate workflows when integrating with tools like vcpkg.&lt;/p&gt;
&lt;h2 id=&quot;lets-integrate-it&quot;&gt;Let’s integrate it!&lt;/h2&gt;
&lt;p&gt;Let’s integrate maelstrom-node into our application! This is the final step to bring everything together, and it’s where the differences between Bazel and CMake really stand out in terms of setup, dependency management, and usability. Here’s how each approach handles this critical task.&lt;/p&gt;
&lt;h3 id=&quot;bazel-integration&quot;&gt;Bazel Integration&lt;/h3&gt;
&lt;p&gt;In Bazel, the integration is streamlined by declaring the maelstrom-node library as a dependency using &lt;code&gt;bazel_dep&lt;/code&gt; and providing a &lt;code&gt;git_override&lt;/code&gt; to fetch the library from its repository:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;maelstrom_node&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1.0.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;git_override(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    module_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;maelstrom_node&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;004b1d793427838db32d5e175f1f474bec260766&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    remote&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://github.com/astavonin/maelstrom-node.git&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The library is then added as a dependency in the build rule for the application:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cc_binary(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;echo-cpp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;main.cpp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    copts&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;-std=c++23&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    visibility&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;//visibility:public&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    deps&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@maelstrom_node&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@spdlog&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bazel’s approach to dependencies is more declarative and centralized. Dependencies are fetched and managed globally at the repository level, ensuring consistency across the project. The use of &lt;code&gt;bazel_dep&lt;/code&gt; and overrides simplifies versioning, and Bazel’s caching mechanism avoids redundant downloads and builds, leading to faster iterations.&lt;/p&gt;
&lt;p&gt;The compilation process is still very straightforward for the final app:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bazel&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; //...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;cmake-integration&quot;&gt;CMake Integration&lt;/h3&gt;
&lt;p&gt;The CMakeLists.txt file for the integration is relatively standard, with one notable addition: the use of &lt;code&gt;FetchContent&lt;/code&gt; to fetch and include the maelstrom-node library directly from its Git repository. This setup is straightforward, as shown below.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cmake&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(FetchContent)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;FetchContent_Declare(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    maelstrom_node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GIT_REPOSITORY https://github.com/astavonin/maelstrom-node.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    GIT_TAG v1.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;FetchContent_MakeAvailable(maelstrom_node)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This snippet looks pretty and allows the library to be built alongside the application. But, it’s not without its drawbacks. Unlike Bazel, CMake relies heavily on external tools like vcpkg for dependency management. Unfortunately, vcpkg requires a complete list of dependencies to be declared for every project (is it a defect? It looks so to me.). In this case, both echo-cpp and maelstrom-node must redundantly list their dependencies in echo-cpp’s vcpkg.conf file. This duplication can be tedious and error-prone, especially in larger, more complex projects.&lt;/p&gt;
&lt;p&gt;Another significant limitation is CMake’s lack of integration with other ecosystems like Rust and Cargo. If you already have existing integration tests written in Rust, as I have, CMake cannot reuse them natively. This inability to leverage cross-language infrastructure is one of CMake’s most significant downsides and a stark contrast to Bazel, which excels in mixed-language project support.&lt;/p&gt;
&lt;p&gt;While this integration approach works, it highlights the challenges of relying on CMake for projects beyond simple use cases, especially when dealing with multiple dependencies or mixed-language ecosystems.&lt;/p&gt;
</content:encoded><category>C++</category><category>Bazel</category><category>CMake</category><category>Tooling</category></item><item><title>Managing Multi-Language Projects with Bazel</title><link>https://sysdev.me/2025/01/14/managing-multi-language-projects-with-bazel/</link><guid isPermaLink="true">https://sysdev.me/2025/01/14/managing-multi-language-projects-with-bazel/</guid><description>In today’s software development landscape, it’s rare to encounter a project built with just one programming language or platform.</description><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/cpp-bazel.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/cpp-bazel.png&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;In today’s software development landscape, it’s rare to encounter a project built with just one programming language or platform. Modern applications often require integrating multiple technologies to meet diverse requirements. This complexity is both a challenge and an opportunity, demanding robust tools to manage dependencies, builds, and integrations seamlessly. Bazel, a powerful build system, is one such tool that has proven invaluable for multi-language projects.&lt;/p&gt;
&lt;p&gt;Recently, I decided to extend my &lt;a href=&quot;https://github.com/astavonin/maelstrom-challenges&quot;&gt;Maelstrom challenges&lt;/a&gt; with a C++-based test to explore how Bazel can simplify managing multi-language dependencies and streamline development workflows.&lt;/p&gt;
&lt;h2 id=&quot;why-bazel-for-multi-language-projects&quot;&gt;Why Bazel for Multi-Language Projects?&lt;/h2&gt;
&lt;p&gt;Bazel’s design philosophy emphasizes performance and scalability, making it an excellent choice for projects that involve multiple languages. With its support for Bazel modules, adding dependencies is as simple as declaring them in a MODULE.bazel file. For example, integrating the popular logging library spdlog is straightforward:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;spdlog&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1.15.0.bcr.3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, not every library supports Bazel modules natively, which can complicate things. A case in point is BOOST, a widely used C++ library. Adding BOOST required a bit of additional setup, fortunately, we have &lt;a href=&quot;https://github.com/nelhage/rules_boost&quot;&gt;rules_boost&lt;/a&gt; project:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rules_boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;repo_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;com_github_nelhage_rules_boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;archive_override(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    module_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rules_boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    integrity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;sha256-ZLcmvYKc2FqgLvR96ApPXxp8+sXKqhBlCK66PY/uFIo=&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    strip_prefix&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rules_boost-e3adfd8d6733c914d2b91a65fb7175af09602281&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    urls&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://github.com/nelhage/rules_boost/archive/e3adfd8d6733c914d2b91a65fb7175af09602281.tar.gz&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;non_module_boost_repositories &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; use_extension(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@com_github_nelhage_rules_boost//:boost/repositories.bzl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;non_module_dependencies&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;use_repo(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    non_module_boost_repositories,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;boost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the setup for BOOST is less elegant than for spdlog, Bazel’s flexibility ensures that even complex dependencies can be integrated efficiently.&lt;/p&gt;
&lt;h2 id=&quot;improving-code-navigation-with-hedrons-compile-commands&quot;&gt;Improving Code Navigation with Hedron’s Compile Commands&lt;/h2&gt;
&lt;p&gt;Another critical aspect of C++ development is code navigation. Generating a &lt;code&gt;compile_commands.json&lt;/code&gt; file makes life easier for developers using tools like Vim or Emacs. Thankfully, Bazel has a powerful solution: &lt;a href=&quot;https://github.com/hedronvision/bazel-compile-commands-extractor&quot;&gt;Hedron Compile Commands Extractor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s pretty easy to set it up:&lt;/p&gt;
&lt;h3 id=&quot;1-add-the-dependency-to-your-modulebazel-file&quot;&gt;1. Add the dependency to your MODULE.bazel file:&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel_dep(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;hedron_compile_commands&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;dev_dependency&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;git_override(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    module_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;hedron_compile_commands&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    commit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;4f28899228fb3ad0126897876f147ca15026151e&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    remote&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://github.com/hedronvision/bazel-compile-commands-extractor.git&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-generate-the-compile_commandsjson-file-by-running&quot;&gt;2. Generate the compile_commands.json file by running:&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bazel run &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;hedron_compile_commands&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;//&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:refresh_all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple addition dramatically improves the developer experience, making it easy to navigate and understand the C++ codebase. What I love even more there is NeoVim’s &lt;a href=&quot;https://github.com/neovim/nvim-lspconfig&quot;&gt;lsp-config&lt;/a&gt; provides excellent navigation and auto-completion with the generated &lt;code&gt;compile_commands.json&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;building-c-support-in-maelstrom-challenges&quot;&gt;Building C++ Support in Maelstrom challenges&lt;/h2&gt;
&lt;p&gt;The process of adding C++ support to the &lt;a href=&quot;https://github.com/astavonin/maelstrom-challenges&quot;&gt;Maelstrom challenges&lt;/a&gt; project involved several key changes. In &lt;a href=&quot;https://github.com/astavonin/maelstrom-challenges/commit/8814adbc613b37b6e143aeb1506b2d5660bded0a&quot;&gt;this commit&lt;/a&gt;, I outlined the required modifications to the &lt;code&gt;MODULE.bazel&lt;/code&gt; file to set up the necessary dependencies. This provided the foundation for implementing a basic Maelstrom protocol in C++.&lt;/p&gt;
&lt;p&gt;The implementation itself, along with a simple echo app as a proof of concept, can be seen in &lt;a href=&quot;https://github.com/astavonin/maelstrom-challenges/commit/f0be5f8a2928a30af7614c157145fa2cab0accc9&quot;&gt;this commit&lt;/a&gt;. This example showcases how Bazel simplifies managing dependencies and building multi-language projects while allowing for robust testing and experimentation.&lt;/p&gt;
</content:encoded><category>Tooling</category><category>C++</category><category>Bazel</category></item><item><title>Bazel and Rust: A Perfect Match for Scalable Development</title><link>https://sysdev.me/2024/12/29/bazel-and-rust-a-perfect-match-for-scalable-development/</link><guid isPermaLink="true">https://sysdev.me/2024/12/29/bazel-and-rust-a-perfect-match-for-scalable-development/</guid><description>Bazel never fails to impress, and its support for Rust demonstrates its versatility and commitment to modern development.</description><pubDate>Sun, 29 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/rust_and_bazel.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/rust_and_bazel.png&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;Bazel never fails to impress, and its support for Rust demonstrates its versatility and commitment to modern development. Two distinct dependency management modes—Cargo—based and pure Bazel—allow developers to tailor workflows to their projects’ needs. This adaptability is particularly valuable for integrating Rust applications into monorepos or scaling complex systems.
I decided to explore how Bazel supports Rust, including managing dependencies, migrating from &lt;code&gt;Cargo.toml&lt;/code&gt; to &lt;code&gt;BUILD.bazel&lt;/code&gt;, and streamlining integration testing.&lt;/p&gt;
&lt;h2 id=&quot;harnessing-cargo-based-dependency-management&quot;&gt;Harnessing Cargo-Based Dependency Management&lt;/h2&gt;
&lt;p&gt;Bazel’s ability to integrate with Cargo, Rust’s native package manager, is a standout feature. This approach preserves compatibility with the Rust ecosystem while allowing projects to benefit from Bazel’s powerful build features. By using &lt;a href=&quot;https://bazelbuild.github.io/rules_rust/&quot;&gt;rules_rust&lt;/a&gt;, a Bazel module can seamlessly import dependencies defined in &lt;code&gt;Cargo.toml&lt;/code&gt; and &lt;code&gt;Cargo.lock&lt;/code&gt; into its build graph.
It was pretty easy to feed dependency information from Cargo to Bazel. The only tricky part is Cargo workspaces. If you use a Cargo workspace like I do, you should list all &lt;code&gt;Cargo.toml&lt;/code&gt; files under the &lt;code&gt;manifests&lt;/code&gt; section in &lt;code&gt;MODULE.bazel&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;crate &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; use_extension(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@rules_rust//crate_universe:extensions.bzl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;crate&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;crate.from_cargo(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;crate_index&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    cargo_lockfile&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;//:Cargo.lock&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    manifests&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;//:Cargo.toml&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;//:echo/Cargo.toml&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;//:tests/Cargo.toml&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;use_repo(crate, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;crate_index&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup allows Bazel to parse and manage dependencies from multiple &lt;code&gt;Cargo.toml&lt;/code&gt; files. The &lt;a href=&quot;https://bazelbuild.github.io/rules_rust/crate_universe.html&quot;&gt;crate_universe&lt;/a&gt; extension ensures that Bazel respects the dependency versions specified in the lockfile, providing reproducible builds. This approach is particularly practical in projects with nested crates or submodules, as Bazel automatically consolidates them into a unified build graph.&lt;/p&gt;
&lt;p&gt;Integrating with Cargo in this way provides the best of both worlds. Developers can continue using Rust’s native tooling for development while leveraging Bazel for its scalability and advanced dependency management. Although this dual-system approach adds some complexity, the flexibility it offers is invaluable for scaling projects and ensuring compatibility with the broader Rust ecosystem.&lt;/p&gt;
&lt;h2 id=&quot;migrating-from-cargotoml-to-buildbazel&quot;&gt;Migrating from &lt;code&gt;Cargo.toml&lt;/code&gt; to &lt;code&gt;BUILD.bazel&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The next step after making a proper &lt;code&gt;MODULE.bazel&lt;/code&gt; file is to create &lt;code&gt;BUILD.bazel&lt;/code&gt;. It’s basically a translation of all component-level &lt;code&gt;Cargo.toml&lt;/code&gt; files into &lt;code&gt;BUILD.bazel&lt;/code&gt; one-by-one.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[package]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;echo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;edition&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2021&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[[bin]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;src/main.rs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;echo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[dependencies]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async-trait&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.1.83&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;maelstrom-node&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.1.6&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Bazel, each &lt;code&gt;bin&lt;/code&gt; section from &lt;code&gt;Cargo.toml&lt;/code&gt; should be translated into a &lt;code&gt;rust_binary&lt;/code&gt; definition in &lt;code&gt;BUILD.bazel&lt;/code&gt;. Another important part here is &lt;code&gt;@crate_index&lt;/code&gt; which refer to &lt;code&gt;crate.from_cargo(name = &quot;crate_index&quot;, ...)&lt;/code&gt; command from the &lt;code&gt;MODULE.bazel&lt;/code&gt; described above.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;load(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@rules_rust//rust:defs.bzl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rust_binary&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rust_binary(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;echo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;src/main.rs&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    proc_macro_deps&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@crate_index//:async-trait&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    deps&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;@crate_index//:maelstrom-node&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shift ensures that Bazel can handle the entire build lifecycle, from compiling dependencies to linking final binaries. While &lt;code&gt;Cargo.toml&lt;/code&gt; defines dependencies and build metadata, &lt;code&gt;BUILD.bazel&lt;/code&gt; brings this into Bazel’s optimized dependency graph. This transition is seamless for developers familiar with both systems and ensures scalability as the project grows.&lt;/p&gt;
&lt;h2 id=&quot;simplifying-integration-testing-with-bazel&quot;&gt;Simplifying Integration Testing with Bazel&lt;/h2&gt;
&lt;p&gt;Integration testing is another area where Bazel simplifies workflows compared to Cargo. In Rust’s native system, it’s not straightforward to ensure that an application is built before a test requiring that application is executed. With Bazel, this process becomes effortless.&lt;/p&gt;
&lt;p&gt;Consider a &lt;code&gt;BUILD.bazel&lt;/code&gt; setup for integration tests:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;load(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;@rules_rust//rust:defs.bzl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rust_library&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rust_test&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rust_library(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;utils&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;utils/lib.rs&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;utils/paths.rs&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;utils/runner.rs&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;rust_test(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;test_echo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;small&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    srcs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;echo/test_echo.rs&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;:maelstrom/lib/maelstrom.jar&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    deps&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;:utils&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;//echo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration defines a &lt;code&gt;rust_test&lt;/code&gt; that explicitly depends on the &lt;code&gt;//echo&lt;/code&gt; binary. Bazel ensures that the binary is built before the test is executed, avoiding any manual coordination. Shared utilities used during the tests are included in a &lt;code&gt;rust_library&lt;/code&gt;, promoting code reuse. Additionally, test resources like &lt;code&gt;:maelstrom/lib/maelstrom.jar&lt;/code&gt; are specified as data, ensuring they are available during execution.&lt;/p&gt;
&lt;p&gt;It took some Googling to figure out how to access external resources like &lt;code&gt;maelstrom.jar&lt;/code&gt; for the Rust integration test. However, it’s simpler than it appears at first. We basically need to retrieve the value from the &lt;code&gt;RUNFILES_DIR&lt;/code&gt; environment variable, which points to a temporary Bazel-managed directory, and then add the &lt;code&gt;&quot;_main&quot;&lt;/code&gt; folder on top of it.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;pub&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bazel_runfiles_dir&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PathBuf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    PathBuf&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;var_os&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;RUNFILES_DIR&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unwrap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;_main&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;pub&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; maelstrom_dir&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PathBuf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    bazel_runfiles_dir&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;tests&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;maelstrom&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;lib&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bazel’s approach eliminates the need for custom scripts or workarounds, streamlining the test lifecycle and ensuring reliable, reproducible results. This simplicity becomes increasingly important as projects grow, where manual processes can hinder productivity and introduce inconsistencies.&lt;/p&gt;
</content:encoded><category>Tooling</category><category>Rust</category><category>Bazel</category></item><item><title>Returning to Rust: A Journey Through Tooling, Performance</title><link>https://sysdev.me/2024/12/10/returning-to-rust-a-journey-through-tooling-performance/</link><guid isPermaLink="true">https://sysdev.me/2024/12/10/returning-to-rust-a-journey-through-tooling-performance/</guid><description>For most non-performance critical scenarios, Go is my default, no-brainer choice.</description><pubDate>Tue, 10 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/astavonin/maelstrom-challenges&quot;&gt;When I started tackling&lt;/a&gt; the &lt;a href=&quot;https://github.com/jepsen-io/maelstrom/tree/main&quot;&gt;Maelstrom challenges&lt;/a&gt;, my initial thought was to use C++. It’s a language I know inside out, and its performance is hard to beat. However, as I contemplated setting up the project, I realized I couldn’t justify fighting with the C++ pipeline for free. Crafting a proper CMake or Bazel configuration might be worthwhile for large-scale projects or when compensated, but for personal experiments? It’s an unnecessary headache.&lt;/p&gt;
&lt;h2 id=&quot;why-go-is-my-default-choice&quot;&gt;Why Go is My Default Choice&lt;/h2&gt;
&lt;p&gt;For most non-performance critical scenarios, Go is my default, no-brainer choice. It has a clean build system, excellent tooling, and a developer experience that doesn’t make me dread the setup process. Go’s simplicity allows me (and &lt;strong&gt;any&lt;/strong&gt; level team) to focus on solving the problem rather than wrestling with the environment. Yet, this time, I decided to take a different path.&lt;/p&gt;
&lt;h2 id=&quot;revisiting-rust&quot;&gt;Revisiting Rust&lt;/h2&gt;
&lt;p&gt;My relationship with Rust has been a journey of highs and lows. I was initially drawn to it during its early days, around Rust 0.1, when the language was brimming with potential. What captivated me back then was its promise to be a kind of “Erlang on steroids”—a language that combined Erlang’s lightweight concurrency model and fault-tolerant programming paradigms with the performance and control of a systems language.&lt;/p&gt;
&lt;p&gt;Unfortunately, with the release of Rust 1.0, much of that original vision shifted. Green threads—a key feature that enabled lightweight concurrency and aligned closely with the “Erlang on steroids” dream—were abandoned. The decision to remove them in favor of direct integration with the OS threading model made sense for speeding up the 1.0 release. Still, it marked a significant departure from the language’s earlier aspirations.&lt;/p&gt;
&lt;p&gt;Adding to the change was a complete overhaul of Rust’s memory model, which moved toward its now-iconic ownership system. While the new model introduced unparalleled safety and performance guarantees, it also made the language more complex and less approachable, particularly for those who were excited about its initial simplicity.&lt;/p&gt;
&lt;p&gt;These shifts left me feeling that Rust had transformed into “just another overcomplicated C++-like language,” albeit with better safety features and tooling. Over time, I moved away from Rust, drawn instead to more straightforward and developer-friendly languages for most of my work.&lt;/p&gt;
&lt;p&gt;But now, years later, I’m giving Rust another shot while having plenty of free time. Its strong presence in emerging fields like Web3 has made it impossible to ignore. The language has clearly matured, and while its evolution may not have aligned with my initial hopes, I’m curious to explore how its modern ecosystem can contribute to my work. Rust may no longer be the “Erlang on steroids” I once envisioned, but it’s carving out a unique space for itself, and I’m eager to see how it fits into my technical journey moving forward.&lt;/p&gt;
&lt;h3 id=&quot;comparing-tooling-and-developer-experience&quot;&gt;Comparing Tooling and Developer Experience&lt;/h3&gt;
&lt;p&gt;My return to Rust brought some pleasant surprises—and a few frustrations. Here’s a breakdown of my observations:&lt;/p&gt;
&lt;p&gt;Cargo vs. Build Systems&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cargo, Rust’s build system and package manager, is undeniably better than CMake—but then again, that’s not a high bar. Let’s be honest: CMake is an exercise of frustration for most developers.&lt;/li&gt;
&lt;li&gt;Comparing Cargo to Bazel: Cargo feels more straightforward and approachable, but it requires more power and flexibility than Bazel. In that sense, Bazel is still the gold standard for me in large, complex projects.&lt;/li&gt;
&lt;li&gt;Go’s build system, however, remains my favorite. Its simplicity and convention-over-configuration philosophy are unmatched.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ides-and-editor-support&quot;&gt;IDEs and Editor Support&lt;/h3&gt;
&lt;p&gt;Tools like RustRover have evolved to deliver an exceptional development experience, far surpassing the likes of CLion or most other C++ IDEs. The level of integration with Cargo, code analysis, and developer assistance is simply stellar.&lt;/p&gt;
&lt;h3 id=&quot;compiler-diagnostics&quot;&gt;Compiler Diagnostics&lt;/h3&gt;
&lt;p&gt;One of Rust’s standout features is its compiler diagnostics. The clarity and detail in Rust’s error messages are leagues ahead of what you get with &lt;em&gt;&lt;strong&gt;Clang&lt;/strong&gt;&lt;/em&gt; or &lt;em&gt;&lt;strong&gt;GCC&lt;/strong&gt;&lt;/em&gt;. It’s an area where Rust truly shines, making debugging and iteration smoother. However, it’s not all perfect.&lt;/p&gt;
&lt;p&gt;When it comes to &lt;strong&gt;borrowing-related errors&lt;/strong&gt;, the story changes. These errors often feel nonintuitive, even for developers familiar with Rust’s ownership model. Despite the compiler’s best efforts to provide explanations, deciphering and resolving these errors frequently requires external help. You’ll find yourself Googling extensively or turning to tools like ChatGPT (model 4.0 preferred) to figure out what’s going wrong and how to fix it. This aspect of the Rust learning curve remains a significant hurdle for newcomers and a source of frustration even for experienced developers.&lt;/p&gt;
&lt;p&gt;While the error diagnostics are still better than those offered by C++, the borrowing system’s complexity can sometimes undermine the excellent developer experience.&lt;/p&gt;
&lt;h2 id=&quot;reflections-and-future-plans&quot;&gt;Reflections and Future Plans&lt;/h2&gt;
&lt;p&gt;Sometimes, stepping back into an ecosystem with a fresh perspective can reveal how much has changed—for better or worse. Rust is no longer just the “overcomplicated” C++ sibling I left behind. It’s still a language with comparable complexity to C++ (which means it’s overcomplicated) but with a critical difference: compile-time sanitizers you need in C++ baked into its Rust core. This feature transforms those complexities into tools that actively guide you toward safer, more robust code.
Last but not least, as I’d mentioned above, I do not want to setup a proper C++ pipeline either :-D&lt;/p&gt;
</content:encoded><category>Rust</category><category>C++</category><category>Bazel</category><category>CMake</category><category>Go</category><category>Tooling</category></item><item><title>Minimal CI for Go library with GitHub actions</title><link>https://sysdev.me/2024/12/05/minimal-ci-for-go-library-with-github-actions/</link><guid isPermaLink="true">https://sysdev.me/2024/12/05/minimal-ci-for-go-library-with-github-actions/</guid><description>Continuous Integration (CI) has become an essential part of modern software development, and for good reason.</description><pubDate>Thu, 05 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/actions_and_go.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/actions_and_go.png&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;Continuous Integration (CI) has become an essential part of modern software development, and for good reason. It ensures code quality, speeds up development, and catches potential issues early. However, you can get started without an elaborate CI setup. Even a minimal CI setup can significantly improve your workflow. Here’s why every project should have at least minimal CI and how to implement it effectively using GitHub Actions.&lt;/p&gt;
&lt;h2 id=&quot;what-constitutes-minimal-ci&quot;&gt;What Constitutes Minimal CI?&lt;/h2&gt;
&lt;p&gt;For a project to benefit from CI without excessive complexity, it should include the following essential components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Project Compilation:&lt;/strong&gt; Verify that the codebase is always in a buildable state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unit Test Execution:&lt;/strong&gt; Ensure the core functionality works as expected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static Code Analysis:&lt;/strong&gt; Catch bugs and enforce coding standards before they become an issue.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;why-github-actions&quot;&gt;Why GitHub Actions?&lt;/h2&gt;
&lt;p&gt;GitHub Actions offers a simple yet powerful CI/CD platform that integrates seamlessly with GitHub repositories. Its ease of use makes it accessible even for developers without extensive DevOps experience. In fact, with just a few workflows, you can have a robust CI pipeline for small projects.&lt;/p&gt;
&lt;p&gt;For example, for my small open-source Go library &lt;a href=&quot;https://github.com/astavonin/gfsm&quot;&gt;GFSM&lt;/a&gt;, three workflows—&lt;strong&gt;build&lt;/strong&gt;, &lt;strong&gt;static-check&lt;/strong&gt;, and &lt;strong&gt;publish&lt;/strong&gt;—are sufficient.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-your-workflows&quot;&gt;Setting Up Your Workflows&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A fully functional GitHub Actions-based CI example from &lt;a href=&quot;https://github.com/astavonin/gfsm/tree/main/.github&quot;&gt;GFSM&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-build-workflow&quot;&gt;1. Build Workflow&lt;/h3&gt;
&lt;p&gt;The build workflow ensures your code compiles and passes tests on every &amp;lt;inl_code&amp;gt;push&amp;lt;/inl_code&amp;gt; or &amp;lt;inl_code&amp;gt;pull_request&amp;lt;/inl_code&amp;gt; to any branch:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;push&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;pull_request&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;prepare-env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./.github/env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;go build -v ./...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;go test -v ./...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-static-code-analysis&quot;&gt;2. Static Code Analysis&lt;/h3&gt;
&lt;p&gt;Static analysis helps maintain code quality by catching potential issues early. The best Go linter at the moment - &lt;a href=&quot;https://staticcheck.dev&quot;&gt;staticcheck&lt;/a&gt;, provides an excellent integration with GitHub actions out of the box. Like the build workflow, this workflow runs on every &lt;code&gt;push&lt;/code&gt; or &lt;code&gt;pull_request&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  ci&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Run CI&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/checkout@v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          fetch-depth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;prepare-env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./.github/env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;dominikh/staticcheck-action@v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;latest&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-publish-workflow&quot;&gt;3. Publish Workflow&lt;/h3&gt;
&lt;p&gt;Publishing a new version to &lt;a href=&quot;https://pkg.go.dev&quot;&gt;pkg.go.dev&lt;/a&gt; ensures that users always have access to the latest updates. The tricky part is triggering the &lt;a href=&quot;https://pkg.go.dev&quot;&gt;pkg.go.dev&lt;/a&gt; proxy to update its cache for new versions. On comparison with &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;static-check&lt;/code&gt; workflows, we should trigger new version publication only on a new tag push in a semantic versioning format. Here’s how you can set up this workflow to track new tags and ping the proxy:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    tags&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;v[0-9]+.[0-9]+.[0-9]+&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  publish&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;pkg.go.dev publishing&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Publishing new version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          curl https://sum.golang.org/lookup/github.com/astavonin/gfsm@${{ github.ref_name }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;reusable-composite-actions&quot;&gt;Reusable Composite Actions&lt;/h2&gt;
&lt;p&gt;Both the &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;static-check&lt;/code&gt; workflows require the same environment setup. To avoid duplicating this logic, use GitHub Actions’ &lt;a href=&quot;https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action&quot;&gt;composite&lt;/a&gt; feature to create a reusable setup action:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;runs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  using&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;composite&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Setup Go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/setup-go@v5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        go-version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;1.23.x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Generate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      shell&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        go get golang.org/x/tools/cmd/stringer@latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        go install golang.org/x/tools/cmd/stringer@latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        go generate ./...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This composite action ensures consistency and simplifies workflow maintenance.&lt;/p&gt;
&lt;h2 id=&quot;looking-ahead&quot;&gt;Looking Ahead&lt;/h2&gt;
&lt;p&gt;Go currently relies on the &lt;code&gt;tools.go&lt;/code&gt; file for dependencies, which I find a too ugly approach and prefer to have some extra &lt;code&gt;go get/install/generate&lt;/code&gt; in my environment setup yaml file. But Go 1.24 promises a much cleaner approach with its &lt;a href=&quot;https://github.com/golang/go/issues/48429&quot;&gt;track tool dependencies in go.mod (#48429)&lt;/a&gt; feature. Once available, it will streamline dependency management much more excellently.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Implementing minimal CI with GitHub Actions requires minimal effort but yields significant benefits. By automating builds, tests, and static analysis, you can ensure your project maintains high quality and is always ready for deployment. For small projects, this setup is a no-brainer, and GitHub Actions makes it easy to get started.&lt;/p&gt;
</content:encoded><category>Go</category><category>CI</category><category>DevOps</category><category>Tooling</category></item><item><title>GFSM: A Simple and Fast Finite State Machine for Go</title><link>https://sysdev.me/2024/11/25/gfsm-a-simple-and-fast-finite-state-machine-for-go/</link><guid isPermaLink="true">https://sysdev.me/2024/11/25/gfsm-a-simple-and-fast-finite-state-machine-for-go/</guid><description>Design patterns are not widely used in Go, as they can often lead to unnecessary complexity in the codebase.</description><pubDate>Mon, 25 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/fsm.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/fsm.png&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;Design patterns are not widely used in Go, as they can often lead to unnecessary complexity in the codebase. However, the Finite State Machine (FSM) is an exception that proves to be incredibly useful. When I set out to design &lt;a href=&quot;https://github.com/astavonin/gfsm&quot;&gt;GFSM&lt;/a&gt;, I aimed to create a fast and straightforward FSM implementation for Go.&lt;/p&gt;
&lt;p&gt;I initially sought a quick and straightforward FSM solution for Go, but I couldn’t find anything that met my needs. Drawing inspiration from the speed-focused and minimalistic principles of C++ while remaining true to Go’s idioms, I developed &lt;a href=&quot;https://github.com/astavonin/gfsm&quot;&gt;GFSM&lt;/a&gt; to fill this gap. The outcome is GFSM—a library that distinguishes itself from alternatives like &lt;a href=&quot;https://github.com/looplab/fsm&quot;&gt;looplab/fsm&lt;/a&gt; by prioritizing speed and simplicity.&lt;/p&gt;
&lt;p&gt;Whether orchestrating microservices, handling distributed systems, or designing embedded systems, &lt;a href=&quot;https://github.com/astavonin/gfsm&quot;&gt;GFSM&lt;/a&gt; brings the reliability and efficiency needed to keep things running smoothly.&lt;/p&gt;
&lt;h2 id=&quot;real-world-use-case-two-phase-commit-protocol&quot;&gt;Real-World Use Case: Two-Phase Commit Protocol&lt;/h2&gt;
&lt;p&gt;One of the best showcases for FSMs is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Two-phase_commit_protocol&quot;&gt;Two-Phase Commit protocol (TPC)&lt;/a&gt;, widely used in distributed systems to ensure atomic transactions. Let’s explore how GFSM can model this protocol step by step.&lt;/p&gt;
&lt;h3 id=&quot;1-define-your-state-machine&quot;&gt;1. Define Your State Machine&lt;/h3&gt;
&lt;p&gt;To build a TPC protocol FSM, we start with a basic state machine:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;flowchart&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    Init&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;Wait&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    Wait&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;Abort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    Wait&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;Commit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This state machine has four states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Init&lt;/code&gt;: The initialization phase.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Wait&lt;/code&gt;: Waiting for votes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Abort&lt;/code&gt;: Aborting the transaction.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Commit&lt;/code&gt;: Committing the transaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-enumerate-all-possible-states&quot;&gt;2. Enumerate All Possible States&lt;/h3&gt;
&lt;p&gt;GFSM allows any &lt;code&gt;comparable&lt;/code&gt; type as state identifiers, but integer-based enumerations keep things clean and fast. For TPC, here’s how the states are defined:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; State&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    Init&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; State&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; iota&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    Wait&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    Abort&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    Commit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-define-state-behaviors&quot;&gt;3. Define State Behaviors&lt;/h3&gt;
&lt;p&gt;Each state in GFSM is represented by an action handler implementing the &lt;code&gt;StateAction&lt;/code&gt; interface:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StateAction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;StateIdentifier&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; comparable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    OnEnter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;smCtx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StateMachineContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    OnExit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;smCtx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StateMachineContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Execute&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;smCtx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StateMachineContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;eventCtx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; EventContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StateIdentifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OnEnter&lt;/code&gt;: Called when entering the state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OnExit&lt;/code&gt;: Called when leaving the state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Execute&lt;/code&gt;: Handles events and determines the next state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, the &lt;code&gt;initState&lt;/code&gt; implementation might look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;initState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Execute&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;smCtx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; gfsm&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StateMachineContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;eventCtx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; gfsm&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;EventContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;State&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cCtx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; smCtx.(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;coordinatorContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    req, ok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; eventCtx.(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;commitRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ok {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Handle invalid event&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Init&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Transition logic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Wait&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-build-the-state-machine&quot;&gt;4. Build the State Machine&lt;/h3&gt;
&lt;p&gt;Using the &lt;code&gt;StateMachineBuilder&lt;/code&gt;, you can define the states, their transitions, and the associated context:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sm &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gfsm.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewBuilder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;State&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]().&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    SetDefaultState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Init).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    SetSmContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;coordinatorContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{partCnt: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    RegisterState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Init, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;initState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}, []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;State&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Wait}).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    RegisterState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Wait, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;waitState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}, []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;State&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Abort, Commit}).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    RegisterState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Abort, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;responseState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        keepResp: Abort,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }, []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;State&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Init}).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    RegisterState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Commit, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;responseState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        keepResp: Commit,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }, []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;State&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Init}).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;coordinatorContext&lt;/code&gt; holds transaction details, such as the number of participants and the commit ID.&lt;/p&gt;
&lt;h3 id=&quot;5-run-the-state-machine&quot;&gt;5. Run the State Machine&lt;/h3&gt;
&lt;p&gt;Once constructed, the FSM is ready to process events. Start the state machine, handle events, and gracefully terminate it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sm.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Start&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;defer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sm.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Stop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sm.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ProcessEvent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;commitRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;commit_1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Handle error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During event processing, the FSM delegates logic to the current state’s &lt;code&gt;Execute&lt;/code&gt; method, determining whether to transition or remain in the current state.&lt;/p&gt;
&lt;h2 id=&quot;learn-more&quot;&gt;Learn More&lt;/h2&gt;
&lt;p&gt;Ready to integrate GFSM into your project? Check out the &lt;a href=&quot;https://pkg.go.dev/github.com/astavonin/gfsm&quot;&gt;documentation on pkg.go.dev&lt;/a&gt; and explore the &lt;a href=&quot;https://github.com/astavonin/gfsm/tree/main/examples&quot;&gt;examples&lt;/a&gt; for hands-on guidance.&lt;/p&gt;
</content:encoded><category>Go</category><category>Software Architecture</category></item><item><title>Reflections on Reading “Introduction to Reliable and Secure Distributed Programming”</title><link>https://sysdev.me/2024/11/18/reflections-on-reading-introduction-to-reliable-and-secure-distributed-programming/</link><guid isPermaLink="true">https://sysdev.me/2024/11/18/reflections-on-reading-introduction-to-reliable-and-secure-distributed-programming/</guid><description>I’ve had Introduction to Reliable and Secure Distributed Programming sitting on my bookshelf for years, silently whispering, “Read me when you have time.” Of course, “later” always seemed like the right time.</description><pubDate>Mon, 18 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/dist_prog.jpg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/dist_prog.jpg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;I’ve had &lt;a href=&quot;https://www.goodreads.com/book/show/10064443-introduction-to-reliable-and-secure-distributed-programming&quot;&gt;Introduction to Reliable and Secure Distributed Programming&lt;/a&gt; sitting on my bookshelf for years, silently whispering, “Read me when you have time.” Of course, “later” always seemed like the right time. As software developers, we often prioritize practical, hands-on books that help solve immediate problems—topics like Kubernetes, Kafka or mastering another layer of C++ intricacies. But in hindsight, neglecting foundational theory is a mistake.&lt;/p&gt;
&lt;p&gt;I began my journey with distributed systems over a decade ago. Back then, I wish someone had handed me this book and insisted I dive in immediately. It’s the kind of resource that can help set the foundation for anyone venturing into the complexities of distributed computing. Instead, I learned through trial, error, and practical exposure, which, while valuable, left gaps that only became apparent when I finally picked up this book.&lt;/p&gt;
&lt;p&gt;The book doesn’t hold back—it dives deeply into the theoretical underpinnings of distributed systems. Despite my experience, I found several topics both fascinating and challenging. The discussions on &lt;strong&gt;randomized consensus with coin&lt;/strong&gt; and h&lt;strong&gt;ierarchical consensus&lt;/strong&gt; stood out to me, offering insights that are as practical as they are thought-provoking. These are concepts that, while grounded in theory, can influence how we design systems in real-world applications.
The chapter on &lt;strong&gt;randomized consensus with coin&lt;/strong&gt; was particularly eye-opening. This approach leverages randomness to break symmetry among processes, a fundamental challenge in achieving consensus in distributed systems. By introducing a probabilistic element—like flipping a virtual “coin”—systems can progress even in scenarios where deterministic methods fail due to asynchronous environments or faults. The beauty of this method lies in its simplicity and elegance; it transforms a seemingly insurmountable problem into something tractable by embracing uncertainty. While not always the fastest approach, it offers a practical fallback in scenarios where other algorithms may struggle.&lt;/p&gt;
&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/wp-content/uploads/2024/11/1.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/wp-content/uploads/2024/11/1.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    The adorable excuse.
  &lt;/figcaption&gt;
&lt;/figure&gt;
**Hierarchical consensus** was another standout topic for me. This technique structures the consensus process across multiple layers or hierarchies, allowing for scalability and fault tolerance in large distributed systems. Instead of forcing a single, global agreement across all nodes, hierarchical consensus breaks the problem into smaller, manageable sub-problems, which are then aggregated into a system-wide decision. This approach is particularly relevant in modern architectures, where systems span multiple data centers or cloud regions. The ability to balance local decisions with global consistency is critical for maintaining performance and reliability at scale, and this chapter provided a clear framework for tackling that challenge.
&lt;p&gt;Reading the book took me about three weeks longer than I’d planned. I’ll admit, I had an adorable excuse for the delay—Molly, my now-5-month-old German Shepherd puppy, decided that my reading sessions were the perfect time to demand attention. Progress was slower but enjoyable between her antics and the material’s complexity.&lt;/p&gt;
&lt;p&gt;For anyone serious about distributed systems, I highly recommend carving out time for this book. It may not solve your immediate programming challenges, but it will fundamentally enhance how you think about distributed systems’ reliability, security, and design. And if you have a Molly of your own, well, consider it a bonus challenge.&lt;/p&gt;
</content:encoded><category>Books and Articles</category><category>Distributed Systems</category><category>Software Architecture</category></item><item><title>Sabbatical and Distributed Systems</title><link>https://sysdev.me/2024/09/30/sabbatical-and-distributed-systems/</link><guid isPermaLink="true">https://sysdev.me/2024/09/30/sabbatical-and-distributed-systems/</guid><description>Working at a unicorn company for four years was an exhilarating experience, though incredibly exhausting.</description><pubDate>Mon, 30 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/img/bishkek.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/img/bishkek.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    View from the rooftop of the apartment, where we settled down. I love running up on the left side of the river—quite a nice trail run.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Working at a unicorn company for four years was an exhilarating experience, though incredibly exhausting. Tackling significant projects that few have ever attempted means Google can’t offer much help. Surrounded by highly talented colleagues, many of whom you may never encounter again in one place, all while facing a relentless pace and high expectations from leadership, it eventually takes a toll. After a while, you just need to pause, rest, and reflect. That’s why I left Motional—to take several months to recharge, spend time with family, and reconnect with my hobbies.&lt;/p&gt;
&lt;p&gt;The irony is, after more than 20 years in IT, I’ve become somewhat of a workaholic, so proper rest eludes me. Just last weekend, I found myself speaking at the &lt;a href=&quot;https://www.linkedin.com/posts/gdg-bishkek_%D0%BE%D0%B1%D1%8A%D1%8F%D0%B2%D0%BB%D1%8F%D0%B5%D0%BC-%D1%81%D0%BF%D0%B8%D0%BA%D0%B5%D1%80%D0%BE%D0%B2-devfest-2024-%D1%81-%D1%80%D0%B0%D0%B4%D0%BE%D1%81%D1%82%D1%8C%D1%8E-activity-7237857767137435651-DpTq/&quot;&gt;DevFest ’24&lt;/a&gt; conference, &lt;a href=&quot;https://docs.google.com/presentation/d/1eusLUNpu1eOugbAPnrL0eoVA1P-U6U0SINnCTSBOkQg/edit&quot;&gt;sharing insights on best practices in project development&lt;/a&gt; with the local IT community. The event exceeded all my expectations. It’s been incredibly gratifying to see how much Kyrgyzstan’s IT sector has grown since I left in 2003. I sincerely hope this growth continues and that, eventually, talented engineers will want to stay here or return to the country instead of trying themselves outside, as I did. However, I’m also acutely aware that I’m now overqualified for most roles in the Kyrgyz job market, which is a bit bittersweet. So, my journey will likely inevitably take me elsewhere after my sabbatical.&lt;/p&gt;
&lt;p&gt;It’s funny (or maybe a bit sad, depending on how you look at it), but my workaholic side insists that taking it easy isn’t enough. Now, with plenty of free time, I’ve decided to tackle something I’ve always wanted to dive deeper into: Distributed Systems theory. Yes, the theory itself—not just the practical implementation that most developers focus on. So, I have a Plan.
I had previously watched Martin Kleppmann’s &lt;a href=&quot;https://www.youtube.com/playlist?list=PLeKd45zvjcDFUEv_ohr_HdUFe97RItdiB&quot;&gt;Distributed Systems lecture series&lt;/a&gt;, but this time, I’m approaching it with more focus, adding the course’s accompanying textbook into the mix. A quick review shows that it covers what I’m looking for, though it doesn’t dive quite deep enough into the theoretical aspects.&lt;/p&gt;
&lt;p&gt;At the same time, I’ve had &lt;a href=&quot;https://www.goodreads.com/book/show/10064443-introduction-to-reliable-and-secure-distributed-programming&quot;&gt;Introduction to Reliable and Secure Distributed Programming&lt;/a&gt; by Christian Cachin and co-authors sitting on my bookshelf for a while, and that’s where I’ll be focusing my efforts. I suspect the Distributed Systems lecture series is grounded in much of this book’s theory. It may not be the most leisurely read on the art of distributed systems, but who ever said learning something worthwhile should be easy?&lt;/p&gt;
&lt;p&gt;However, even theoretical study requires some hands-on practice. That’s where the fantastic &lt;a href=&quot;https://github.com/jepsen-io/maelstrom&quot;&gt;Maelstrom&lt;/a&gt; tool from &lt;a href=&quot;https://jepsen.io&quot;&gt;jepsen.io&lt;/a&gt; comes in. Maelstrom offers a series of challenges, starting with something simple like an Echo and progressing to more complex tasks like Raft. Since Maelstrom is a beautifully designed Clojure-based app with well-defined protocols for each challenge, adding more challenges should be both easy and fun.&lt;/p&gt;
&lt;figure style=&quot;float: left; width: 300px; margin: 0 1em 1em 0;&quot;&gt;
  &lt;a href=&quot;https://sysdev.me/wp-content/uploads/2024/09/1-1.jpeg&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;https://sysdev.me/wp-content/uploads/2024/09/1-1.jpeg&quot; alt=&quot;caption&quot; width=&quot;250&quot;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Bishkek - Osh road. Kyrgyz mountains are magnificent at any time.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That said, most of this work revolves around Centralized Distributed Systems, which makes sense given their dominance. Naturally, I’m also curious about a solid theoretical resource on &lt;strong&gt;De&lt;/strong&gt;centralized Distributed Systems. The best I’ve found so far is the &lt;a href=&quot;https://decentralizedthoughts.github.io&quot;&gt;Decentralized Thoughts&lt;/a&gt; post series. It’s relatively easy to follow and seems to cover many important aspects of decentralized distributed systems theory. I’m unsure if a better resource exists, but it looks like a great starting point.&lt;/p&gt;
&lt;p&gt;Ultimately, this sabbatical has given me the time to reconnect with my passion for learning and exploring the theoretical depths of distributed systems. Whether centralized or decentralized, each layer of complexity opens up new ways to think about how we build and maintain reliable systems. As I continue down this path, I’m excited to see where these studies will lead, and I hope that by sharing my journey, I can inspire others to dive deeper into the technical topics they’ve always wanted to explore.&lt;/p&gt;
</content:encoded><category>Books and Articles</category><category>Software Architecture</category><category>Distributed Systems</category></item><item><title>TIL: how to debug randomly hanging Python applications</title><link>https://sysdev.me/2022/06/29/til-how-to-debug-randomly-hanging-python-applications/</link><guid isPermaLink="true">https://sysdev.me/2022/06/29/til-how-to-debug-randomly-hanging-python-applications/</guid><description>Usually, if a Python-based application hangs, you either read logs or grab one of the PBD-based solutions, attaching to the application, and uses the Python console for investigation.</description><pubDate>Wed, 29 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Usually, if a Python-based application hangs, you either read logs or grab one of the PBD-based solutions, attaching to the application, and uses the Python console for investigation. The approach is straightforward; for example, you installed &amp;lt;inl_code&amp;gt;pdb-attach&amp;lt;/inl_code&amp;gt;, and add a few lines to your application:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pdb_attach&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pdb_attach.listen(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;50000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and expect that “magic” will just works:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;gt; python -m pdb_attach &amp;lt;PID&amp;gt; 50000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(Pdb) YOU HAVE PDB SESSION HERE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But sometimes, magic is broken, and my theory is (I didn’t search for proof) that this is due to GIL. So, sometimes, no PDB prompt after you have attached to the application with PDB. In my case, the application hang in the &lt;code&gt;multiprocessing.Process&lt;/code&gt; call where I used a gRPC server. The gRPC server didn’t react to the termination request, the process cannot stop, and like aggravating circumstances, all these are a part of PyTest that hang 1 of 20 executions.&lt;/p&gt;
&lt;p&gt;This is a general PDB-based debuggers issue, which means all other tools like &lt;code&gt;pyrasite-shell&lt;/code&gt; and PyTest PBD integration also don’t work. The only option here is GDB for Python, which is surprisingly amazing! First of all, you need to install Python extension for GDB.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; python3.9-dbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can connect to your Python application which is a regular Python process with GDB, and explore the call-stack!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;gt; gdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(GDB) attach &amp;lt;PID&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(GDB) py-bt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use not APT-based Linux, search for proper instruction &lt;a href=&quot;https://wiki.python.org/moin/DebuggingWithGdb&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>Python</category><category>Optimizations</category><category>Tooling</category></item></channel></rss>