Ogravius Dev Blog

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

Last updated: 2026/07/26

[#1 - Powershelling with ninja]

TL;DR

Referenced in: >[Powershell script]<

Why?

To be honest, I have 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.

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 have used it more than once), but I simply do not need it here, because I want to build the graph myself. At the moment, the script only works on Windows; to make it more portable, I would have to use different compilers and install PowerShell on Linux or macOS. I am fully aware that this would not sit well with most, but as a quick prototype it works pretty well.

How does it work?

Folder structure

project/
├── core/
│   ├── dll/
│   │   ├── debug/
│   │   │   └── dynamic_lib.dll
│   │   └── release/
│   │       └── dynamic_lib.dll
│   ├── include/
│   │   ├── vendor/
│   │   │   ├── dynamic_lib/
│   │   │   │   └── dynamic_lib.h
│   │   │   └── static_lib/
│   │   │       └── static_lib.h
│   │   └── hello_world.h
│   ├── lib/
│   │   ├── debug/
│   │   │   ├── dynamic_lib.lib
│   │   │   └── static_lib.lib
│   │   └── release/
│   │       ├── dynamic_lib.lib
│   │       └── static_lib.lib
│   └── src/
│       ├── hello_world.cpp
│       └── main.cpp
├── dynamic_lib/
│   ├── dll/
│   │   ├── debug/
│   │   └── release/
│   ├── include/
│   │   ├── vendor/
│   │   └── dynamic_lib.h
│   ├── lib/
│   │   ├── debug/
│   │   └── release/
│   └── src/
│       └── dynamic_lib.cpp
└── static_lib/
    ├── dll/
    │   ├── debug/
    │   └── release/
    ├── include/
    │   ├── vendor/
    │   └── static_lib.h
    ├── lib/
    │   ├── debug/
    │   └── release/
    └── src/
        └── static_lib.cpp

The above shows my preferred folder structure that I work with for my own game engine project.

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

Note: I prefer to include prefix for files in the src/include, for example “graphics_common” or “physics_common” to avoid nested folder structure. You are welcome to disagree with me, but that is how I prefer to work on projects I own. Vendor folder is meant for 3rd party headers and libs obviously go in the libs folder.

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 does not restrict you to the MSVC toolchain, it is usually a good practice to stick with what is recommended before diverging. You must know what you are targeting and trying to achieve at that point if you decide to move away from MSVC. At the end of the day, the right tool is the one that works for your target and whose quirks you understand.

This approach requires explicit configuration of compiler, linker, or librarian that needs to be used, along with the specific flags for each build configuration. It is important to note that these change depending on the toolchain. You cannot expect MSVC flags to work on Linux (GCC) or macOS (Clang), as they use their own flavours of tools.

I will not go into great detail about what every MSVC flag does (I would 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 librarian. 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. I have made use of ‘rsp’ files that Ninja provides to avoid problems when you filled the command arguemnts with hundreds of files.

Ninja manual:

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

Reality check

Do not mistake this for a magical, all-in-one replacement for CMake. This works because the folder structure is driving the architecture of it, and assumes no mistakes were made anywhere.

Current limitations include:

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, it will be replaced with the custom build generator written in C/C++. After all, the PowerShell script has done most of the design work so all that remains is to take the working blueprint and build the bridge.

Indeed, it is a very manual and abstraction-free way of working, but I enjoy controlling every aspect of the build.

Note: This script builds each project independently. Dependency staging is intentionally outside its scope and my actual build script does far more.

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 might touch on memory arenas next.