<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adamansky Anton</title>
    <description>The latest articles on DEV Community by Adamansky Anton (@adamansky).</description>
    <link>https://dev.to/adamansky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F591874%2F604c8bf5-b9a7-4a38-bd17-5adfd9b24857.jpeg</url>
      <title>DEV Community: Adamansky Anton</title>
      <link>https://dev.to/adamansky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adamansky"/>
    <language>en</language>
    <item>
      <title>Autark: Rethinking build systems – Integrate, Don’t Outsource</title>
      <dc:creator>Adamansky Anton</dc:creator>
      <pubDate>Wed, 05 Nov 2025 07:19:28 +0000</pubDate>
      <link>https://dev.to/adamansky/autark-rethinking-build-systems-integrate-dont-outsource-1pc1</link>
      <guid>https://dev.to/adamansky/autark-rethinking-build-systems-integrate-dont-outsource-1pc1</guid>
      <description>&lt;p&gt;I enjoy programming in C. Over the years, I’ve built a couple dozen projects of varying complexity – from small toy&lt;br&gt;
programs to game engines, key-value storage systems, and even algorithmic trading platforms. Each time I start a new&lt;br&gt;
project, before I can begin the actual work, I have to go through a somewhat tedious phase: setting up the build system&lt;br&gt;
and defining dependencies on other projects and libraries.&lt;/p&gt;
&lt;h2&gt;
  
  
  CMake
&lt;/h2&gt;

&lt;p&gt;Until recently, I used CMake. For each new project, this usually meant copying the build rules from one of my previous&lt;br&gt;
projects and adapting them to the current needs. While that approach made it easier to get started, the process was&lt;br&gt;
always painful. The imperative, BASIC-like scripting logic of CMake is too cumbersome for simple reuse or quick&lt;br&gt;
adaptation to changing build requirements.&lt;/p&gt;

&lt;p&gt;Another inconvenience is that CMake requires you to constantly keep up with new features and changes in its vast&lt;br&gt;
ecosystem. If you ignore them for too long, one day your project simply stops building with the current stable CMake&lt;br&gt;
version – and you’ll need to enable some CMake policy to restore compatibility. Even if we ignore the scripting&lt;br&gt;
complexity, there’s still the long-term issue: eventually, after the project is released, it will stop building exactly&lt;br&gt;
as described in its own documentation. &lt;/p&gt;

&lt;p&gt;As we all know, CMake doesn’t actually build your project –  it’s a transpiler that converts CMakeLists.txt scripts into&lt;br&gt;
rules for lower-level tools like Make or Ninja. In practice, that means you depend on two separate tools (and their&lt;br&gt;
compatible versions) just to get a working build, not to mention the debugging headaches in complex setups.&lt;/p&gt;

&lt;p&gt;I won’t go into a detailed analysis of CMake’s pros and cons here – it’s a popular system, and it earned that popularity&lt;br&gt;
for a reason. It’s far from perfect, but as my colleagues like to say, “Bad. But there’s nothing better yet.” Personally,&lt;br&gt;
I decided to pursue a different concept for a build system – one that aligns more closely with my own values:&lt;br&gt;
simplicity, stability, and lightness.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make
&lt;/h2&gt;

&lt;p&gt;So why not just use plain Make or even autotools? Take a look at the folks from &lt;a href="https://suckless.org" rel="noopener noreferrer"&gt;suckless.org&lt;/a&gt;.&lt;br&gt;
They write great software using pure POSIX mode Make. Though, amusingly, they forgot to include Make itself in their list of&lt;br&gt;
software that rocks. I have my own reasons for not being too fond of Make, and I’m pretty sure I’m not alone. My&lt;br&gt;
short-term memory isn’t great – if I don’t write Make scripts every day, I can never remember all the quirky mechanics&lt;br&gt;
and syntax variations. Consider constructs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;%.d&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;%.c&lt;/span&gt;
        &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="p"&gt;$(&lt;/span&gt;CC&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CPPFLAGS&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt;.&lt;span class="nv"&gt;$$$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s,\($*\)\.o[ :]*,\1.o $@ : ,g'&lt;/span&gt; &amp;lt; &lt;span class="nv"&gt;$@&lt;/span&gt;.&lt;span class="nv"&gt;$$$$&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt;.&lt;span class="nv"&gt;$$$$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;EXECS&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;exec..."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="p"&gt;$(&lt;/span&gt;MEM_CHECKER&lt;span class="p"&gt;)&lt;/span&gt; ./&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let’s not forget the &lt;em&gt;Automatic Variables&lt;/em&gt; list from section &lt;code&gt;10.5.3&lt;/code&gt; of the GNU Make manual – the one you’re&lt;br&gt;
apparently not allowed to ignore if you dare to use Make. Please don’t tell me “it’s all simple”. I know there are people&lt;br&gt;
for whom it is, but as I said earlier, for me and many colleagues with short memory spans, it’s not.&lt;/p&gt;

&lt;p&gt;There’s another point: I believe that when a professional uses a tool, they should understand and be able to use all of&lt;br&gt;
its features. But the more complex the tool, the harder it is to claim you truly master it. (That’s why I’ll never call&lt;br&gt;
myself a C++ expert.)&lt;/p&gt;

&lt;p&gt;In practice, we all end up sticking to a small set of build recipes that feel comfortable and just copy-paste them&lt;br&gt;
between projects. Make, however, is too low-level to express build logic easily and gracefully, especially when it needs&lt;br&gt;
to account for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes in a Makefile should trigger a rebuild of all dependent targets.&lt;/li&gt;
&lt;li&gt;Changes in environment variables like &lt;code&gt;$CC&lt;/code&gt; or &lt;code&gt;$CFLAGS&lt;/code&gt; should cause dependent modules to rebuild.&lt;/li&gt;
&lt;li&gt;Modifications in system library headers should trigger rebuilds of the affected parts of the project.&lt;/li&gt;
&lt;li&gt;There are several flavors of Make: POSIX Make, GNU Make, BSD Make and you must decide what functionality to sacrifice
in order to stay compatible across environments.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s just the beginning. Sure, you can make Make handle all of this, but at what cost? I’ve yet to see a handmade&lt;br&gt;
Make project that does it all correctly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Autark
&lt;/h2&gt;

&lt;p&gt;Realizing that it’s impossible to create a &lt;em&gt;perfect&lt;/em&gt; build system, I focused on defining a few key characteristics&lt;br&gt;
I wanted to see in the final product even if that meant sacrificing some functionality elsewhere.&lt;/p&gt;

&lt;p&gt;Here’s what I needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A simple, memorable syntax for build scripts.&lt;/li&gt;
&lt;li&gt;Deeper dependency tracking compared to Make (see above).&lt;/li&gt;
&lt;li&gt;Portability – ideally, the project shouldn’t require any preinstalled build system. It should be able to build itself
using only the most basic tools, like a &lt;code&gt;C&lt;/code&gt; compiler and a system shell.&lt;/li&gt;
&lt;li&gt;The ability to verify and adapt the project before building (a &lt;em&gt;configure&lt;/em&gt; phase).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Dependencies
&lt;/h3&gt;

&lt;p&gt;To get started, I needed some inspiration and I found it in the &lt;a href="https://cr.yp.to/redo.html" rel="noopener noreferrer"&gt;Redo build tool&lt;/a&gt;.&lt;br&gt;
There are several Redo implementations available on GitHub, though the concept never became particularly&lt;br&gt;
popular. I suspect that’s because it feels rather academic: a talented mathematician proposed a simple, elegant idea,&lt;br&gt;
but for it to work in real-world projects, it needed to be wrapped in something more developer-friendly.&lt;/p&gt;

&lt;p&gt;While experimenting with Redo, I discovered that building a project often required a large set of shell scripts&lt;br&gt;
scattered across many directories. In many of them, I had to reimplement utility functions that really belonged inside&lt;br&gt;
the build system itself. However, I did take away one important concept: the idea of managing dependencies through individual files and&lt;br&gt;
developed it further.&lt;/p&gt;
&lt;h3&gt;
  
  
  Portability
&lt;/h3&gt;

&lt;p&gt;To achieve true portability, I took a fairly radical approach: during the first build, the project compiles its own&lt;br&gt;
build system from &lt;code&gt;C&lt;/code&gt;, and from then on, that build system compiles the project. The build system is a small C program&lt;br&gt;
embedded directly inside a &lt;code&gt;build.sh&lt;/code&gt; script, which acts as its entry point. It only requires a C99-compatible compiler&lt;br&gt;
(flags compatible with clang or gcc). The entire system is about 10K lines of code, compiles in under a second, and&lt;br&gt;
caches its build artifacts. This way, everything related to the build process is already part of the project itself – no&lt;br&gt;
need to worry about toolchain versions or environment differences across machines.&lt;/p&gt;
&lt;h3&gt;
  
  
  Script Syntax
&lt;/h3&gt;

&lt;p&gt;Here I had to deal with a rather contradictory set of requirements. On one hand, I wanted a powerful declarative syntax;&lt;br&gt;
on the other, it needed to be simple, and the parser had to remain minimal to allow fast compilation of the build system&lt;br&gt;
during a cold start. I eventually settled on the following syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RULE:

  rule_name { RULE | LITERAL ... }

LITERAL:

  word | 'single quoted words' | "double quoted words"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A script consists of a set of rules and literals.&lt;/li&gt;
&lt;li&gt;Rules and literals are separated by whitespaces.&lt;/li&gt;
&lt;li&gt;Every rule has a body enclosed in curly braces &lt;code&gt;{}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rules form lists, similar to syntactic structures in Scheme or Lisp. The AST of a script can change dynamically
depending on conditions. I was amused to find that a trace of Scheme somehow made its way into this project without me
realizing it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A formal syntax description in PEG format can be found here:&lt;br&gt;
&lt;a href="https://github.com/Softmotions/autark/blob/master/scriptx.leg" rel="noopener noreferrer"&gt;https://github.com/Softmotions/autark/blob/master/scriptx.leg&lt;/a&gt;.&lt;br&gt;
Special thanks to &lt;a href="https://piumarta.com/cv/" rel="noopener noreferrer"&gt;Ian Piumarta&lt;/a&gt; for his excellent &lt;a href="https://piumarta.com/software/peg/" rel="noopener noreferrer"&gt;PEG parser generator&lt;/a&gt; &lt;br&gt;
 which I’ve used in many of my projects.&lt;/p&gt;

&lt;p&gt;Here’s a build script example from the &lt;a href="https://github.com/Softmotions/autark-sample-project" rel="noopener noreferrer"&gt;demo project&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cc {
  hello.c 
  ${CFLAGS}
  ${CC}
  consumes {
    # hello.h is a product of the `configure` rule,
    # so we declare our dependency on it here.
    hello.h
  }
}

# Builds the static library libhello.a
run {
  exec { ${AR} rcs libhello.a ${CC_OBJS} }
  consumes {
    ${CC_OBJS}
  }
  produces {
    ${LIBHELLO_A}
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Current Status of Autark
&lt;/h2&gt;

&lt;p&gt;The build system &lt;a href="https://autark.dev" rel="noopener noreferrer"&gt;autark.dev&lt;/a&gt;  was implemented some time ago, and since then I’ve migrated most of&lt;br&gt;
my C/C++ projects to it, fixing issues and polishing things along the way.&lt;/p&gt;

&lt;p&gt;Here are a few open-source projects that have already been migrated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Softmotions/iwnet" rel="noopener noreferrer"&gt;github.com/Softmotions/iwnet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Softmotions/iowow" rel="noopener noreferrer"&gt;github.com/Softmotions/iowow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And even a fork of the third-party project &lt;a href="https://github.com/Softmotions/protobuf-c" rel="noopener noreferrer"&gt;protobuf-c&lt;/a&gt; where I &lt;a href="https://github.com/Softmotions/protobuf-c/blob/master/t/Issues.autark" rel="noopener noreferrer"&gt;used macros&lt;/a&gt; to build and run test cases.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Autark started as an experiment – a personal attempt to make the build process less painful and more predictable. Over&lt;br&gt;
time, it grew into something I now use daily across nearly all my C projects. It’s not meant to compete with CMake or&lt;br&gt;
reinvent the industry standard. Instead, it tries to do one thing well: give developers a lightweight, self-contained,&lt;br&gt;
and reliable way to describe how their projects are built.&lt;/p&gt;

&lt;p&gt;I don’t claim that Autark is the best build system it’s simply the one that aligns with my values: simplicity,&lt;br&gt;
transparency, and long-term stability. If you’ve ever struggled with build scripts that felt like they were fighting&lt;br&gt;
you, maybe you’ll find something familiar in this approach.&lt;/p&gt;

&lt;p&gt;The project is still evolving, and feedback is very welcome. You can learn more or try it yourself at&lt;br&gt;
&lt;a href="https://autark.dev" rel="noopener noreferrer"&gt;autark.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cmake</category>
      <category>make</category>
      <category>c</category>
      <category>cpp</category>
    </item>
  </channel>
</rss>
