Building C and C++ projects shouldn't be more complicated than writing the code itself.
If you're a C or C++ developer, you've probably experienced what I call CMake fatigue.
You start with a simple project. A few source files, some headers, and perhaps a library.
Then suddenly, you're dealing with configuration scripts, build generators, dependency graphs, and platform-specific linker behavior.
CMake is powerful. Ninja is fast. Both have earned their place in modern software development.
But what if you could build your project without managing all that complexity yourself?
Meet Satria, formerly known as rbot (Rupa Builder Of Target).
Satria is a lightweight, declarative build system for C and C++ projects, built around one philosophy:
A simple builder for you.
It aims to make project configuration straightforward, workspace orchestration practical, and incremental builds fast—without sacrificing the actual work required to produce correct build artifacts.
Let's explore what makes it different.
1. Start Simple: Zero Boilerplate
Imagine a typical C project:
myproject/
├── src/
│ ├── main.c
│ └── utils.c
└── Buildfile
With Satria, your configuration can be as simple as:
use project
That's it.
Satria follows the principle of convention over configuration.
Instead of requiring you to explicitly enumerate every source file and compiler command, it can discover sources from the conventional project layout.
To initialize a project:
satria init
Then build it:
satria
Satria handles source discovery, compilation, dependency tracking, and linking.
You can still customize the configuration when necessary.
For example:
use project
compiler = gcc, clang
std = gnu11
flags = Wall, Wextra
The goal isn't to eliminate configuration.
It's to make configuration optional when reasonable and explicit when necessary.
C++ Support
Satria also supports C++ projects.
Interactive initialization makes getting started easier:
satria init -p
You can select the project language and let Satria generate the initial project structure.
For a C++ project, a Buildfile might look like:
use project
std = c++17
Satria also supports compilation database generation for development tools such as clangd.
This allows you to use modern editor features without maintaining a separate build configuration just for your language server.
2. Workspaces Without Configuration Chaos
Single-project builds are relatively straightforward.
Workspaces are where build systems become more interesting.
Consider a project containing several interconnected components:
workspace/
├── Buildfile.ws
├── library/
├── rupamod/
├── rupa/
│ ├── include/
│ └── src/
└── ruka/
├── include/
└── src/
Some projects may produce libraries.
Others may produce executables.
Another project might package resources into an archive that gets embedded into a binary.
And sometimes, dependencies between components are circular.
The DAG Problem
Ninja is designed around a Directed Acyclic Graph (DAG).
This is a sensible architecture for a low-level build executor, but circular dependencies require careful modeling.
For example:
Rupa ──────► Ruka
▲ │
└───────────┘
A naive project-level dependency graph would contain a cycle.
Ninja cannot directly execute a cyclic build graph. The build configuration must represent the actual operations through an acyclic dependency structure.
This can involve separating compilation from linking, adjusting dependency edges, or handling circular static-library references at link time.
Satria approaches the problem at a higher level.
Workspace-Aware Build Execution
A Satria workspace begins with:
use workspace
projects = rupamod, rupa, ruka
Rather than treating the workspace as a collection of unrelated commands, Satria understands its projects and their output artifacts.
Its workspace execution architecture separates important build phases, allowing project libraries to be prepared before dependent executable linking.
Conceptually:
Workspace
│
▼
Source Discovery
│
▼
Compile Project Objects
│
▼
Build Project Libraries
│
▼
Link Final Executables
│
▼
Package Releases
This architecture helps Satria manage complex project relationships without requiring developers to manually flatten every project into individual object-level build rules.
It doesn't mean circular dependencies disappear.
The linker must still receive the correct objects and libraries.
The difference is that Satria takes responsibility for coordinating those operations.
3. Real Builds, Not Benchmark Shortcuts
One of Satria's design principles is simple:
A faster build is meaningful only when the required work is actually performed.
A workspace may need to:
- Compile C and C++ sources.
- Generate object files.
- Create static and shared libraries.
- Package resources into archives.
- Embed archives into binaries.
- Link executables.
- Produce distribution packages and checksums.
Skipping necessary work isn't an optimization.
It's an incomplete build.
This principle matters when comparing Satria against tools such as Ninja.
A benchmark that measures only compilation cannot automatically be considered equivalent to a build that also creates libraries, embeds resources, and packages releases.
Both tools should be evaluated against the same required outputs whenever possible.
Satria's workspace architecture is designed around performing those real operations efficiently.
4. Incremental Builds: Content Matters
Traditional timestamp-based build systems often rebuild files when their modification times change.
But a modification timestamp doesn't necessarily mean the contents changed.
Consider:
touch src/main.c
The file's timestamp changes.
Its contents do not.
A timestamp-based dependency check may decide that recompilation is necessary.
Satria uses content-based fingerprinting to distinguish actual content changes from timestamp-only updates.
When the relevant content remains unchanged, unnecessary compilation can be avoided.
Why This Matters
Imagine a workspace containing hundreds or thousands of source files.
A script updates timestamps across a directory.
A conventional timestamp-driven build may schedule substantial work.
Satria can recognize unchanged content and avoid recompiling it.
This is particularly useful for:
- Generated files.
- Source synchronization.
- Large workspaces.
- Repeated development builds.
- Environments with unusual filesystem behavior.
Of course, hashing isn't free.
The build system still needs to determine whether the relevant inputs changed.
The challenge is making that verification cheaper than unnecessary compilation.
Satria optimizes for doing the minimum necessary work—not merely the minimum number of commands.
5. Portable Builds: GNU ld Isn't Everywhere
Portability is another area where build systems face practical challenges.
On Linux, GNU binutils provides tools such as GNU ld.
One common technique for embedding binary resources is:
ld -r -b binary -o resource.o resource.tar
This creates a relocatable object containing the binary data.
However, not every platform's linker supports the same options.
For example, Android Termux commonly uses LLVM's linker infrastructure.
During a real CMake+Ninja workspace test on Termux, the build failed while attempting to embed an archive:
ld: error: target emulation unknown:
-m or at least one .o file required
The C compilation itself was not the problem.
The embed command assumed GNU linker behavior.
Satria's Approach
Satria checks whether a suitable GNU ld is available.
When it is, Satria can use the GNU linker embedding strategy.
When it isn't, the normal embed pipeline can use a C-array fallback.
Conceptually:
Embed Archive
│
▼
GNU ld available?
/ \
Yes No
│ │
▼ ▼
GNU ld C-array
embed fallback
│ │
└────┬─────┘
▼
Build Continues
Satria also caches the linker availability result to avoid repeating unnecessary detection work.
This behavior matters because developers shouldn't have to redesign their build process every time they switch environments.
Honest Tooling
There is an interesting distinction between normal builds and profiling.
On Termux, requesting:
satria profile embed=rupamod
can report:
GNU ld tidak tersedia
(jalur fallback C-array tidak diprofilkan)
In other words, the normal build supports the fallback, but that particular profiling operation does not yet measure it.
Instead of reporting a misleading measurement, Satria explicitly states the limitation.
Correctness and transparency matter more than pretending every feature is complete.
6. Performance: Numbers From Real Tests
Performance claims should come from measurements, not marketing.
Satria has been tested in several environments, including Linux, Android Termux, and proot-distro.
One particularly interesting test used a real multi-project workspace containing C sources, libraries, embedded resources, executables, and release packaging.
Termux Native — Eight Workers
The following results were obtained using Satria v0.2.3.109 on an eight-core Android device running Termux.
Each run started with a workspace clean:
satria -w clean
Followed by:
satria -w -j8
Build output was redirected to /dev/null to reduce terminal-output overhead.
| Run | Cold Build |
|---|---|
| 1 | 25.807 s |
| 2 | 24.599 s |
| 3 | 23.796 s |
| 4 | 25.018 s |
| 5 | 25.884 s |
| Median | 25.018 s |
The fastest run completed in 23.796 seconds.
These builds included the workspace's release pipeline, producing .tar.gz and .deb packages along with SHA-256 checksums.
Comparing Against CMake+Ninja
In a separate Termux native cold-build test, CMake+Ninja completed its configured workspace pipeline in:
36.501 seconds
Compared with Satria's five-run median of 25.018 seconds, this is a substantial observed difference.
However, there are important qualifications.
The CMake configuration used for this comparison did not reproduce every Satria output, particularly the complete release pipeline.
Additionally, the CMake+Ninja result came from a single measured cold build rather than a five-run median.
Therefore, these measurements demonstrate promising performance, but they should not be presented as a perfectly controlled, equal-work comparison.
Scaling Across Workers
The same Termux device was also tested using different worker counts.
| Workers | Cold Build |
|---|---|
| 2 | 65.790 s |
| 4 | 50.890 s |
| 8 | 27.739 s |
| 12 | 27.016 s |
These are individual measurements rather than medians.
The results show substantial improvement when increasing parallelism from two to eight workers.
Increasing the worker count beyond the device's eight cores produced only a small additional improvement in that particular run.
This is expected: more workers do not create more physical CPU capacity.
The best worker count depends on the hardware, compiler, workload, and system conditions.
7. Linux, Termux, and Toolchain Flexibility
Satria isn't designed around a single compiler or operating environment.
Its development bootstrap can adapt to the available toolchain.
For example:
./install.sh --dev
On Termux native, Satria can be built using Clang.
On Linux systems with GCC available, GCC can be used.
This flexibility helps keep the development experience straightforward across different environments.
The build tool itself should not require users to install unnecessary platform-specific dependencies merely to support an optional optimization.
That principle also influenced Satria's development.
An experimental global compiler-pool design was explored, but the original scheduler remained attractive because it already demonstrated strong parallel performance without introducing an additional Android semaphore dependency.
Sometimes the better optimization is keeping the architecture simpler.
8. Profiling and Understanding Build Costs
A fast build system should also help developers understand where time is being spent.
Satria includes profiling functionality for selected build operations.
For example:
satria profile library=core
This can expose the time spent in phases such as discovery, collection, and archiving.
That information is useful because optimizing the wrong operation rarely improves overall performance.
If a library archive takes hundreds of milliseconds while source discovery takes only a few milliseconds, optimizing a small loop in discovery probably won't produce a meaningful improvement.
The same principle applies to workspace scheduling.
The important question isn't simply how many operations the scheduler performs.
It's what those operations actually cost.
Satria's performance work focuses on measuring those costs and reducing unnecessary overhead without skipping required outputs.
9. Satria Is Not a Package Manager
Satria's purpose is intentionally focused.
It is a build system.
It isn't trying to become a general-purpose package registry or dependency marketplace.
Its responsibilities center on building projects, coordinating workspace artifacts, and producing the outputs developers need.
This distinction helps prevent feature growth from overwhelming the core build engine.
A tool doesn't need to do everything.
It needs to do its intended job well.
10. Benchmarking Without Illusions
Ninja is an excellent build executor.
CMake is a mature and capable project configuration system.
Satria doesn't need to pretend otherwise.
Different architectures have different strengths.
Ninja excels at executing a well-defined acyclic build graph.
CMake offers extensive platform and toolchain configuration capabilities.
Satria focuses on providing a compact, declarative build experience with integrated project and workspace orchestration.
The important benchmark questions are:
Did both systems perform the same work?
Did both produce the required artifacts?
Were compiler settings and parallelism comparable?
Were measurements repeated under controlled conditions?
A benchmark that ignores those questions may produce impressive numbers without meaningful conclusions.
For Satria, losing a benchmark is not a reason to hide the result.
It's an opportunity to investigate where the time went.
And winning a benchmark is only meaningful when the comparison is fair.
Conclusion: A Simple Builder for You
Satria started from a straightforward idea:
Building software should be simpler than configuring the tools used to build it.
That philosophy extends beyond minimal configuration files.
It influences how Satria handles workspaces, incremental compilation, toolchain compatibility, profiling, and performance optimization.
With Satria, developers can start from:
use project
And grow into a multi-project workspace when necessary:
use workspace
projects = rupamod, rupa, ruka
Without abandoning the same build system.
Satria is still evolving.
There are optimizations to explore, compatibility improvements to make, and features that need further development.
But its direction remains clear:
Keep configuration simple. Perform the real work. Measure performance honestly.
Because a build system should help you build software—not become another project you have to maintain.
Satria — A Simple Builder for You
GitHub: https://github.com/aidomx/satria.git
What matters most to you in a build system: simplicity, portability, incremental performance, or workspace support?
Top comments (0)