Common/Ninja.ps1

#----------------------------------------------------------------------------------------------------------------------
# MIT License
#
# Copyright (c) 2021 Mark Schofield
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#----------------------------------------------------------------------------------------------------------------------
#Requires -PSEdition Core

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

. $PSScriptRoot/Common.ps1
. $PSScriptRoot/Console.ps1

<#
 .Synopsis
  Tries to parse the specified Ninja log.
 
 .Outputs
 The entries from the Ninja log.
#>

function TryParseNinjaLog {
    [CmdletBinding()]
    param(
        [string] $NinjaLogPath
    )
    if (Test-Path -Path $NinjaLogPath -PathType Leaf) {
        Get-Content $NinjaLogPath |
            Where-Object {$_[0] -ne '#'} |
            ForEach-Object {
                $Tokens = $_ -split "\t"
                [int]$StartTime = $Tokens[0]
                [int]$EndTime = $Tokens[1]
                [pscustomobject]@{
                    StartTime=$StartTime
                    EndTime=$EndTime
                    WriteTime=([long]$Tokens[2])
                    File=$Tokens[3]
                    CommandHash=$Tokens[4]
                    Duration=($EndTime - $StartTime)
                }
            }
    }
}

$FileTimeOffset = [long]12622770400 * [long]10000000

<#
 .Synopsis
  Converts the specified Ninja log time representation into a [datetime].
 
 .Notes
 This function is currently Windows-only.
#>

function ConvertFrom-NinjaTime {
    param(
        $NinjaTime
    )
    [datetime]::FromFileTime([long]$NinjaTime + $FileTimeOffset)
}

<#
 .Synopsis
  Converts the specified [datetime] into Ninja log time representation.
 
 .Notes
 This function is currently Windows-only.
#>

function ConvertTo-NinjaTime {
    param(
        [datetime]$Time
    )
    $Time.ToFileTime() - $FileTimeOffset;
}

function Report-NinjaBuild {
    param(
        [string] $NinjaLogPath,
        [datetime] $BuildStartTime
    )
    $BuildStartNinjaTime = ConvertTo-NinjaTime $BuildStartTime
    $Entries = (TryParseNinjaLog $NinjaLogPath) |
        Where-Object {$_.WriteTime -ge $BuildStartNinjaTime}
    $Statistics = $Entries | Measure-Object -Property Duration -Maximum
    if (-not $Statistics) {
        return
    }
    $MaximumDuration = $Statistics.Maximum
    [System.Drawing.Color]$WorstColor = "#ff0000"
    [System.Drawing.Color]$BestColor = "#00ff00"
    $Entries |
        Sort-Object -Property Duration |
        ForEach-Object {
            $Color = ColorInterpolation $BestColor $WorstColor ($_.Duration / $MaximumDuration)
            [string]$_.Duration + ' ' + (ColorToControlCode $Color) + $_.File + (ResetForegroundControlCode)
        }
}

function Download-Ninja {
    param(
        [string] $OutputPath,
        $NinjaVersion = '1.11.1',
        $NinjaArchiveSha256Hash = '524B344A1A9A55005EAF868D991E090AB8CE07FA109F1820D40E74642E289ABC'
    )
    $NinjaArchiveUrl = "https://github.com/ninja-build/ninja/releases/download/v$NinjaVersion/ninja-win.zip"
    $NinjaArchivePath = Join-Path -Path $OutputPath -ChildPath 'ninja-win.zip'
    $NinjaExecutablePath = Join-Path -Path $OutputPath -ChildPath 'ninja.exe'

    if (-not (IsUpToDate $NinjaExecutablePath $NinjaArchivePath)) {
        Write-Verbose "Installing Ninja $NinjaVersion"
        if (-not (IsUpToDate $NinjaArchivePath)) {
            DownloadFile $NinjaArchiveUrl $NinjaArchivePath
            if ($NinjaArchiveSha256Hash -ne (Get-FileHash -Path $NinjaArchivePath -Algorithm SHA256).Hash) {
                Remove-Item -Force -Path $NinjaArchivePath
                Write-Error "Invalid Hash"
            }
        }
        Expand-Archive -Path $NinjaArchivePath -DestinationPath $OutputPath -Force
        Touch $NinjaExecutablePath
    }
    Get-Item -Path $NinjaExecutablePath
}