Ogravius Dev Blog

Learn. Document. Forget. Repeat. ¯\_(ツ)_/¯

Last updated: 2026/04/19

[#1 - Powershelling with ninja]

TL;DR

Script: >[Powershell script]<

Why?

To be honest, I’ve always been more keen to learn how to build code myself rather than just hearing the theory of compiling and linking objects. Abstractions like IDEs usually hide these details “behind the curtain,” and I simply wanted to have direct control over the compiler and linker. Now that Ninja has effectively replaced slow makefiles, that desire for control is more relevant than ever! Plus, I finally found a perfect excuse to learn some PowerShell.

The real reason for this approach is so I can create an automated pipeline for building game engine assets. It might sound crazy to some that I would skip CMake (and yes, I’ve used it more than once), but what makes it interesting and fun is that I am doing what CMake does myself. At the moment, the script only works on Windows because I am using MSVC; to make it more portable, I would have to tell Ninja to use different compilers and—funnily enough—run PowerShell on Linux or macOS. I am fully aware that this would not sit well with most, but thankfully I am the only one to suffer and have fun with this!

Yes—it might sound funny that I am using bloated PowerShell to generate executables and libraries, and it is even funnier to do this on Linux or macOS, but the true power is in being able to create one source of truth that can be deployed anywhere and is capable of doing more than just code generation.

How does it work?

Folder structure

projects/
├── dynamic_lib/
│   ├── include/
│   │   └── dynamic_lib.h
|   |   └── dynamic_utils.h
│   └── src/
│       └── dynamic_lib.cpp
|       └── dynamic_utils.cpp
├── hello_world/
│   ├── include/
│   │   └── hello_world.h
│   └── src/
│       ├── hello_world.cpp
│       └── main.cpp
└── static_lib/
    ├── include/
    │   └── static_lib.h
    │   └── static_utils.h    
    └── src/
        └── static_lib.cpp
        └── static_utils.cpp        

As a constraint to keep things simple, I don’t create multiple sub-folders within the ‘include’ and ‘src’ directories of each project. For the simplicity of the script, instead of src/graphics/utils.cpp and src/physics/utils.cpp, I treat these as graphics_utils.cpp and physics_utils.cpp. This approach could potentially allow for unity builds; however, the goal is not to create the most amazingly organised meta-build system, as this blueprint serves as a simple, straightforward, and working example.

The ‘out’ folder almost mirrors this setup, but each project directory contains its own Ninja files, assembly outputs, and separate debug/release folders.

Platform check

# Support old legacy PowerShell
$RunningOnWindows = $env:OS -match "Windows" -or $IsWindows
if ($RunningOnWindows) {
    # Windows Specific Code here
}

Whenever you compile or link code, you generally do it with the tools provided and recommended by the platform vendor. While Windows doesn’t restrict you to the MSVC toolchain, it’s good practice to use it for your final product to ensure the code makes the most of the OS.

As you can see, I had to manually tell Ninja which compiler, linker, or archiver I wanted to use, along with the specific flags for each configuration. It’s important to note that these change depending on the toolchain. You can’t expect MSVC flags to work on Linux (GCC) or macOS (Clang), as they use their own flavours of tools. Supporting those was out of scope for my blueprint—I’d much rather enjoy coding the game engine than spend ages perfecting a meta-build system for a product I don’t even have yet!

I won’t go into great detail about what every MSVC flag does (I’d basically be rewriting the manual), but here are a few useful links:

MSVC compiler flags:

https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=msvc-170

MSVC linker flags:

https://learn.microsoft.com/en-us/cpp/build/reference/linker-options?view=msvc-170

MSVC lib flags:

https://learn.microsoft.com/en-us/cpp/build/reference/overview-of-lib?view=msvc-170

Ninja

$NinjaContent = @"
rule $("$ProjectName")_debug_cc
    command = $($Compiler) $($NinjaCompilerCommands) $($CompilerObjectOut)`$out $($DebugCompilerFlags) `$in 
    description = Compiling `$in 
    deps = $($NinjaDeps)

build $($NinjaDebugObj)/$($File).obj: $("$ProjectName")_debug_cc $($ProjectSource)/$($File).cpp
build $($NinjaDebugBin)/$($ExecutableName): $("$ProjectName")_debug_lk $($ListOfDebugObj)
"@

Once everything is ready, we can essentially start creating the build.ninja file from scratch.

The first step is to create rules that will run the compiler, linker, and archiver. These rules pass the flags and allow Ninja to provide the filenames via $in and $out. After that, you create the build statements that specify how to compile the .obj files and, eventually, how to link those objects into an executable or a library.

Ninja manual:

https://ninja-build.org/manual.html

Reality check

Don’t mistake this for a magical, all-in-one replacement for CMake. This script is literally the first thing I whipped up over a weekend for my “future” pipeline. It is far from optimised and still requires a ton of work!

Current limitations include:

Regarding subninja: using a “once per project” approach isn’t necessarily wrong. In fact, it’s ideal for me when I’m not changing or updating libraries. If I only change code in the executable project, it’s much faster to just recompile that project using the existing libs. Sometimes you might want to recompile a single library just to check the assembly output before you rebuild everything. Still, having the option for a complete rebuild definitely helps with the “panic attacks”.

Despite all the caveats above, automating the build process like this teaches you how software is actually built. More importantly, PowerShell is fantastic for scripting this high-level pipeline. In the future, once the discovery phase for the project is stable and PowerShell’s speed becomes the bottleneck, I can replace it with a binary equivalent! (That’s what he said, but he will continue to use 10k lines of PowerShell script!)

Interesting fact

Did you know you can use Ninja for custom tooling, and not just for code generation? Well, neither did I, but I am certainly going to try it at some point with HLSL shaders and texture manipulation!

What next?

I will take this blueprint and try to have more fun experimenting whilst coding the game engine, as this script is capable of serving as the executable generator. I might touch on memory arenas next.