← Back to writing

I Finally Understood C++ Build Systems (After Being Confused for Way Too Long)

January 202612 min read

For a long time, C++ build systems felt like magic in the worst way.

Things worked sometimes, and when they didn't, the errors felt completely disconnected from what I had actually written. I knew how to write C++, but the moment build tools were involved—Visual Studio, CMake, Ninja, MSBuild—I felt like I was missing something fundamental.

Turns out, I was.

The Mistake I Was Making

I used to think the compiler was the build system.

If I ran cl or g++ and got an executable, that was the end of the story. Everything else just felt like extra ceremony. But once projects got bigger—or I moved between Windows and Linux—that mental model completely fell apart.

The real issue was that I hadn't separated the layers in my head.

The Mental Model That Changed Everything

What finally clicked for me was realizing that a modern C++ build usually has three distinct layers:

Once I stopped lumping everything together, the ecosystem stopped feeling random.

CMake Isn't a Build System (and That Surprised Me)

For a while, I thought CMake was the build system.

It's not.

CMake is a build generator. It doesn't compile anything. It just writes instructions for other tools.

That's why the same CMake project can turn into:

That realization alone explained a huge amount of confusion I'd had about why CMake "worked differently" depending on the generator.

Build Systems: The Middle Layer I Ignored

The next piece was understanding what tools like MSBuild, Ninja, and Make actually do.

They just run commands.

Ninja especially made this obvious to me. It doesn't care if it's running MSVC, Clang, or GCC—it just executes whatever commands CMake gave it. That's why it feels so fast and simple.

MSBuild, on the other hand, is tightly integrated with Visual Studio. It works great for large Windows projects, but it also explains why .sln and .vcxproj files feel so "Visual Studio–specific".

MSVC Felt Easy… Until I Left Visual Studio

MSVC was my first serious Windows toolchain, and honestly, it felt very smooth inside Visual Studio.

Everything was bundled:

But the moment I tried using it from the command line, I hit the Developer Command Prompt requirement. That was my first hint that there was more going on behind the scenes than just "run the compiler".

Visual Studio was doing a lot of environment setup for me.

Clang Taught Me What a Toolchain Really Is

Clang (LLVM) was where things finally clicked at a deeper level.

I learned that Clang by itself isn't a full toolchain. It doesn't ship with:

It relies on whatever the platform provides.

That explained why Clang feels "cleaner" but also harder to set up, especially on Windows. It also made me appreciate why MSVC feels so plug-and-play by comparison.

Why CMake + Ninja Became My Default

Once I understood the layers, CMake + Ninja made perfect sense.

It let me:

That flexibility felt empowering. Builds stopped feeling like magic and started feeling like infrastructure I could reason about.