Ogravius Dev Blog

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

Last updated: 2026/04/19

[Powershell script]

Referenced: >[#1 - Powershelling with ninja]<

Calling script (build.ps1)

$ProjectName = "hello_world"
$ProjectType = "exe"
$hello_world = @{
    ProjectName = $ProjectName
    ProjectSrcFiles = "$ProjectName/src"
    ProjectIncludeFiles = "$ProjectName/include"
    ProjectType = $ProjectType
}

$ProjectName = "static_lib"
$ProjectType = "lib"
$static_lib = @{
    ProjectName = $ProjectName
    ProjectSrcFiles = "$ProjectName/src"
    ProjectIncludeFiles = "$ProjectName/include"
    ProjectType = $ProjectType
}

$ProjectName = "dynamic_lib"
$ProjectType = "dll"
$dynamic_lib = @{
    ProjectName = $ProjectName
    ProjectSrcFiles = "$ProjectName/src"
    ProjectIncludeFiles = "$ProjectName/include"
    ProjectType = $ProjectType
}

# Run the deployment script
.\.build/per_project.ps1 @hello_world
.\.build/per_project.ps1 @static_lib
.\.build/per_project.ps1 @dynamic_lib

Receiving script (per_project.ps1)

Param(
    [Parameter(Mandatory=$true)]
    [string]$ProjectName,
    [string]$ProjectSrcFiles,
    [string]$ProjectIncludeFiles,
    [string]$ProjectType
)

Write-Host "--- Attempting to build $ProjectName --- " -ForegroundColor DarkGreen

# PowerShell settings
$ErrorActionPreference = "Stop" # This will stop executing the script immediately

# Global
$CurrentDirectory = (Get-Location).ToString().Replace('\', '/')
$Out = ".out"
$ProjectSubFolder = "projects"
$ProjectFolder = "$Out/$ProjectName"
$ProjectSource = "../../$ProjectSubFolder/$ProjectSrcFiles"
$ProjectInclude = "../../$ProjectSubFolder/$ProjectIncludeFiles"
$NinjaFile = "$ProjectFolder/build.ninja"

# Support old legacy PowerShell
$RunningOnWindows = $env:OS -match "Windows" -or $IsWindows
if ($RunningOnWindows) {
    # On Windows - Run this ps in dedicated PowerShell for VS so that compiler/linker can find everything it needs
    # Check if compiler path exists
    if (!(Get-Command cl.exe -ErrorAction SilentlyContinue)) {
    Write-Host "Error: cl.exe not found. Please run this in a Developer PowerShell." -ForegroundColor Red
    exit 1
    }
    # Check if link path exists
    if (!(Get-Command link.exe -ErrorAction SilentlyContinue)) {
    Write-Host "Error: link.exe not found. Please run this in a Developer PowerShell." -ForegroundColor Red
    exit 1
    }
    #We are on Windows!
    Write-Host "Detected Windows - Using MSVC" -ForegroundColor Cyan

    #Define ninja variables here
    $NinjaCompilerCommands = "/showIncludes /c"
    $NinjaDebugOut = "Debug"
    $NinjaReleaseOut = "Release"

    $NinjaDebugBin = "$NinjaDebugOut/bin"
    $NinjaReleaseBin = "$NinjaReleaseOut/bin"

    $NinjaDebugLib = "$NinjaDebugOut/lib"
    $NinjaReleaseLib = "$NinjaReleaseOut/lib"

    $NinjaDebugObj = "$NinjaDebugOut/obj"
    $NinjaReleaseObj = "$NinjaReleaseOut/obj"

    $NinjaDeps = "msvc"

    #Executable name
    $ExecutableName = "$ProjectName.exe"
    $LibName = "$ProjectName.lib"
    $DllName = "$ProjectName.dll"

    #Compiler
    $Compiler = "cl.exe"
    # Use /FS as the compiler will be writing to the same .pdb simulatenously (Thanks Ninja!)
    # Use /Fa to generate assembly files
    # Use /EHsc - From Microsoft manual = When you use /EHs or /EHsc, the compiler assumes that exceptions can only occur at a throw statement or at a function call. This assumption allows the compiler to eliminate code for tracking the lifetime of many unwindable objects 
    $DebugCompilerFlags = "/nologo /MDd /Zi /Ob0 /Od /RTC1 /W4 /EHsc /FS "
    $ReleaseCompilerFlags = "/nologo /MD /Zi /O2 /Ob2 /Zo /FAs /DNDEBUG /EHsc /FS "
    # Separate include directories as those will need to be bundled in the future
    $DebugCompilerFlags += "/I$ProjectInclude"
    $ReleaseCompilerFlags += "/I$ProjectInclude"
    $CompilerObjectOut = "/Fo"

    #Linker
    $Linker = "link.exe"
    $DebugLinkerFlags = "/NOLOGO /DEBUG:FULL /OPT:REF /OPT:ICF /INCREMENTAL:NO"
    $ReleaseLinkerFlags = "/NOLOGO /DEBUG:FULL /OPT:REF /OPT:ICF /INCREMENTAL:NO"
    $LinkerObjectOut = "/OUT:"

    if ($ProjectType -eq "dll"){
        $DebugCompilerFlags = "$DebugCompilerFlags /LD /D_USRDLL /D_WINDLL"
        $ReleaseCompilerFlags += "$ReleaseCompilerFlags /LD /D_USRDLL /D_WINDLL"
        $DebugLinkerFlags = "$DebugLinkerFlags /DLL /OPT:NOICF"
        $ReleaseLinkerFlags = "$ReleaseLinkerFlags /DLL /OPT:NOICF"
    }

    #Librarian
    $Archiver = "lib.exe"
    $ArchiverFlags = "/NOLOGO"
    $ArchiverObjectOut = "/OUT:"
}
else {
     # Linux / MacOS is currently not supported
     Write-Host "--- Cannot run on Non-Windows platform ---" -ForegroundColor Red
     exit 1
}

# Attempt to remove the file
Remove-Item -Path $NinjaFile -Force -ErrorAction SilentlyContinue

# If the .out doesn't exist, create it
if (!(Test-Path $ProjectFolder)) {
    New-Item -ItemType Directory -Path $ProjectFolder
}

# So it begins...
Write-Host "--- Starting Build ---" -ForegroundColor DarkGreen

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

rule $("$ProjectName")_release_cc
    command = $($Compiler) $($NinjaCompilerCommands) $($CompilerObjectOut)`$out $($ReleaseCompilerFlags) `$in 
    description = Compiling `$in 
    deps = $($NinjaDeps)

rule $("$ProjectName")_debug_lk
    command = $($Linker) $($LinkerObjectOut)`$out $($DebugLinkerFlags) `$in 
    description = Linking `$out

rule $("$ProjectName")_release_lk
    command = $($Linker) $($LinkerObjectOut)`$out $($ReleaseLinkerFlags) `$in 
    description = Linking `$out

rule $("$ProjectName")_lib
    command = $($Archiver) $($ArchiverObjectOut)`$out $($ArchiverFlags) `$in 

rule $("$ProjectName")_debug_dll
    command = $($Linker) $($LinkerObjectOut)`$out $($DebugLinkerFlags) `$in 
    description = Linking `$out

rule $("$ProjectName")_release_dll
    command = $($Linker) $($LinkerObjectOut)`$out $($ReleaseLinkerFlags) `$in 
    description = Linking `$out    
"@

# Get list of source files first. This gets all files without .cpp extension
$ListOfSourceFiles = (Get-ChildItem -Path "$ProjectSubFolder/$ProjectSrcFiles" -File).BaseName
$ListOfDebugObj = ""
$ListOfReleaseObj = ""
foreach ($File in $ListOfSourceFiles) {
    #Now we need to build object files
    $NinjaContent += "`n"
    $NinjaContent += "`nbuild $($NinjaDebugObj)/$($File).obj: $("$ProjectName")_debug_cc $($ProjectSource)/$($File).cpp"
    $NinjaContent += "`nbuild $($NinjaReleaseObj)/$($File).obj: $("$ProjectName")_release_cc $($ProjectSource)/$($File).cpp"

    # Add this to the growing list for the final piece
    $ListOfDebugObj += "$($NinjaDebugObj)/$($File).obj "
    $ListOfReleaseObj += "$($NinjaReleaseObj)/$($File).obj "
}

$NinjaContent += "`n"
if ($ProjectType -eq "exe") {
    $NinjaContent += "`nbuild $($NinjaDebugBin)/$($ExecutableName): $("$ProjectName")_debug_lk $($ListOfDebugObj)"
    $NinjaContent += "`nbuild $($NinjaReleaseBin)/$($ExecutableName): $("$ProjectName")_release_lk $($ListOfReleaseObj)"
}
elseif ($ProjectType -eq "lib") {
    $NinjaContent += "`nbuild $($NinjaDebugLib)/$($LibName): $("$ProjectName")_lib $($ListOfDebugObj)"
    $NinjaContent += "`nbuild $($NinjaReleaseLib)/$($LibName): $("$ProjectName")_lib $($ListOfReleaseObj)"
}
elseif ($ProjectType -eq "dll") {
    $NinjaContent += "`nbuild $($NinjaDebugLib)/$($DllName) | $($NinjaDebugLib)/$($LibName): $("$ProjectName")_debug_dll $($ListOfDebugObj)"
    $NinjaContent += "`nbuild $($NinjaReleaseLib)/$($DllName) | $($NinjaReleaseLib)/$($LibName): $("$ProjectName")_release_dll $($ListOfReleaseObj)"
}    

# Save the file
$NinjaContent | Out-File -FilePath $NinjaFile -Encoding ascii

# Call Ninja
# @args forwards things like: .\build.ps1 -t clean
ninja -C "$CurrentDirectory/$ProjectFolder"

# Post-Build
# This only runs if Ninja exits successfully (Exit Code 0)
if ($LASTEXITCODE -eq 0) {
    Write-Host "--- Build Complete! ---" -ForegroundColor DarkGreen
}