Skip to content

Instantly share code, notes, and snippets.

@loopyd
Last active October 23, 2023 08:13
Show Gist options
  • Save loopyd/9dd6658f2e0eb3910996fc983ec97d2d to your computer and use it in GitHub Desktop.
Save loopyd/9dd6658f2e0eb3910996fc983ec97d2d to your computer and use it in GitHub Desktop.
[BlAse.ps1] Autonomous Single-File Aseprite Build Tool
################################################################################
#
# BLASE - AUTOMATED AESPRITE BUILDING FOR WINDOWS
#
# Created by loopyd <loopyd@github.com>
# Version: 1.0.0
# License: Unlicense (https://unlicense.org/)
#
# ==============================================================================
# Inferred from Blep - the draconic windows automation tool
# https://gist.github.com/loopyd/ae5f5a50b679b98e0899e4748e8f5ce0
# ==============================================================================
#
# This script is designed to fully automate the building of Aseprite on
# Windows. It will install all dependencies, and then build Aseprite with
# the specified options. It is designed to be run on a fresh Windows
# installation, all the way up to a fully configured development environment.
#
# I couldn't find a plug-and-play solution for building Aseprite on Windows,
# so I decided to make one using parts of blep. This script is the result of
# that effort. This script expands on blep by adding a ton of new features,
# such as a customized LLVM build, a python version manager that doesn't
# depend on chocolatey, and a custom skia build using the Google Depot Tools
# without needing to include MozillaBuild. The resulting build is the most
# performant build of Aseprite you can get, as its built from scratch using
# the latest versions of all the dependencies, and is optimized for your CPU.
#
# From unit testing, the blAse-built Aseprite is 1.63x faster than the latest
# release you can pay $60 for. Making it ideal to work on large projects.
#
# ------------------------
# CHANGES MADE TO THE HOST
# ------------------------
#
# Under transparency, this script makes non-destructive modifications to your
# system. These changes are detailed below:
#
# - The script is required to be run as an Administrator, as it will install
# various packages and read from the registry. It does not modify the
# registry, however, to ensure that it is non-destructive.
#
# Learn more about Powershell 7 here:
# https://docs.microsoft.com/en-us/powershell/scripting/overview?view=powershell-7.1
#
# - If you don't have Chocolatey installed, it will install it for you.
# Chocolatey is a useful package manager for Windows, and is used to
# automate the installation of some dependencies.
#
# You can learn more about Chocolatey here: https://chocolatey.org/
#
# - If you don't have 7zip installed, it will install it for you.
#
# Discover 7zip here: https://www.7-zip.org/
#
# - If you don't have the Windows SDK installed, it will install the
# appropriate version for you. Defaults to 10.0.18362.0, but can be
# changed with the -WindowsSdkVersion parameter.
#
# Learn more about the Windows SDK (Windows API) here:
# https://docs.microsoft.com/en-us/windows/win32/apiindex/windows-apisets
#
# - If you don't have Visual Studio installed, it will install the
# appropriate version for you. Defaults to 16.0, but can be changed
# with the -VisualStudioVersion parameter.
#
# Learn more about Visual Studio here: https://visualstudio.microsoft.com/
#
# - LLVM-blAse is a specialized compiler used to build Aseprite. If you don't
# know, Aesprite's main bottleneck is the fact that it is built with MSVC,
# so blAse uses a customized version of LLVM-clang to build it instead that
# has unnessecary bloat removed, and is optimized for your CPU.
#
# Learn more about LLVM here: https://llvm.org/
#
# - If you don't have Ninja installed, it will install the appropriate
# version for you. Ninja is a specialized build system used to
# facilitate build automation for multilanguage CMake projects
#
# Learn more about Ninja here: https://ninja-build.org/
#
# - If you don't have CMake installed, it will install the appropriate
# version for you. CMake is a specialized build system used to
# automate the build process for C and C++ projects.
#
# Learn more about CMake here: https://cmake.org/
#
# ------------------------
# FAQ
# ------------------------
#
# Q: Why is this script so long?
#
# A: Because it does a lot of things. It installs a lot of dependencies, and
# builds a lot of things from scratch.
#
# Q: Why does this script take so long to run?
#
# A: Becuase you have a slow computer, download more RAM today. The script
# takes approximately 4 hours to cook blAse-Aesprite on an i9-11700k.
#
# If you experience Page Faults or BSODs, you may need to increase your
# virtual memory allocation.
#
# Q: Are you going to make a Linux version?
#
# A: No. Well...maybe. If someone decides to port this entire script to
# bash, I'll consider it. But I'm not going to do it myself.
# writing over 25,000 lines of bash is not my idea of a good time.
#
# Q: Are you going to make a Mac version?
#
# A: You're funny.
#
# Q: What's all this on my system?
#
# A: The script installs a lot of dependencies. Its going to leave
# Visual Studio Community and the Windows SDK on your system, as
# well as Python. Becuase it would take me 24 hours each test
# to run the script where it uninstalls?reinstalls everything,
# and no one's got 500TB of monthly ISP bandwidth to waste on that.
#
# I like to avoid cease and desist letters from my ISP, GitHub, the
# Python Software Foundation, and Microsoft, so I'm not going to automate that
# by adding uninstallation/reinstallation of the big tools to the -Clean
# switch. If you want to uninstall them, you can do it yourself, and
# potentially deal with the consiquences of re-running the script
# many times over and pissing off the afformentioned entities.
#
# Downloads are cached in the configured -CachePath, to avoid repeated
# downloads. If you want to clear the cache, you can do so by passing
# the -Clean switch to the script. But this is NOT recommended on
# a slow internet connection, as it will force the script to re-download
# everything that is to be considered a dependency.
param(
[Parameter(Position = 0, Mandatory = $false)][string]$AseVersion = "v1.3-rc6",
[Parameter(Position = 1, Mandatory = $false)][string]$SkiaVersion = "aseprite-m102",
[Parameter(Position = 2, Mandatory = $false)][string]$WindowsSdkVersion = "10.0.18362",
[Parameter(Position = 3, Mandatory = $false)][string]$VisualStudioVersion = "17.0",
[Parameter(Position = 4, Mandatory = $false)][string]$LLVMVersion = "17.0.1",
[Parameter(Position = 5, Mandatory = $false)][string]$CmakeVersion = "3.28.0-rc2",
[Parameter(Position = 6, Mandatory = $false)][string]$GitVersion = "2.42.0",
[Parameter(Position = 7, Mandatory = $false)][string]$GitSubVersion = "2",
[Parameter(Position = 8, Mandatory = $false)][string]$CachePath = "$PWD\.cache",
[Parameter(Position = 9, Mandatory = $false)][switch]$SkiaUsePrebuilt,
[Parameter(Position = 10, Mandatory = $false)][switch]$Clean
)
<##############################################
# PRE-FLIGHT CHECKS
##############################################>
# Block non-admin execution of the script. We need admin to install chocolatey, winget, and scoop, to make registry changes, and to install some packages.
$IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
if (-not $IsAdmin) {
Write-Error "You must run this script as an Administrator!"
exit 1
}
# Check and ensure the script is running as Powershell 7, if it is not, install it silently and reloaunch the script as Powershell 7. We use a legacy Powershell version to install Powershell 7 if we need to, so we use some old backwards-compatible code to do this.
if ($PSVersionTable.PSVersion.Major -lt 7) {
if (-not (Test-Path pwsh)) {
Write-Host -ForegroundColor Green $("-" * 80)
Write-Warning -Message "Powershell 7 is not installed, and required by the script, attempting to install it now..."
$url = "https://github.com/PowerShell/PowerShell/releases/download/v7.3.6/PowerShell-7.3.6-win-x64.msi"
$output = "$env:TEMP\PPowerShell-7.3.6-win-x64.msi"
$client = New-Object System.Net.WebClient
$client.DownloadFile($url, $output)
$arguments = "/i `"$output`" /quiet /qn /norestart"
Start-Process "msiexec.exe" -ArgumentList $arguments -Wait
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "Powershell 7 installation failed, exiting script"
Write-Host -ForegroundColor Green $("-" * 80)
exit 1
}
}
Write-Warning -Message "This script requires Powershell 7 or higher to execute, attempting to relaunch script as Powershell 7..."
Write-Host -ForegroundColor Green $("-" * 80)
Start-Process pwsh -ArgumentList "-File $PSCommandPath" -Verb RunAs
exit $LASTEXITCODE
}
# Create a Powershell profile file if it doesn't exist.
if (-not $(Test-Path -Path "$PROFILE")) {
New-Item -ItemType File -Path "$PROFILE"
}
##############################################
# DRAGONS HEART - CORE FUNCTIONS
##############################################
<#
.SYNOPSIS
This function displays the splash logo for the script.
.DESCRIPTION
As the description says, this function displays the splash logo for the script. I have made it editable this time. Please be honest with any modifications and provide credit to the original author.
.NOTES
Not providing credit to the original author makes you a Donkey Dick.
I need to update this when I get around to it. If you're proficient with
ASCII art using NerdFonts and Powershell color commands, please reach out!
#>
function Display-Logo {
$logo = @"
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓
▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓ B L A S E | Unilicensed
▓▓▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▓▓▓
▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓ A single-file PSSolution to liberate aesprite
▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓ for Windows, from scratch!
▓▓▓░░░░░░░░▓▓▓░░░░░░░░░░▓▓▓░░░░░░░░▓▓▓
▓▓▓░░░░░░░░▓▓▓░░░░░░░░░░▓▓▓░░░░░░░░▓▓▓ LLVM-blAse | skia from src | Google Depot
▓▓▓░░░░░░░░▓▓▓░░░░░░░░░░▓▓▓░░░░░░░░▓▓▓
▓▓▓░░░░░░░░▓▓▓░░░░░░░░░░▓▓▓░░░░░░░░▓▓▓ The most performant build of Asesprite known to
▓▓▓░░░░░░░░▓▓▓░░░░░░░░░░▓▓▓░░░░░░░░▓▓▓ the Brood using skia built with a custom version
▓▓▓░░░░░░░░▓▓▓░░░░░░░░░░▓▓▓░░░░░░░░▓▓▓ of LLVM-clang optimized for your CPU, and built
▓▓▓░░░░░░░░▒▒▒░░░░░░░░░░▒▒▒░░░░░░░░▓▓▓ from nothing! Asesprite-blAse is GAURENTEED
▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓ to run butter-smooth on your system!
▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓
▓▓▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▓▓▓ -=== + ===-
▓▓▓▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▓▓▓
▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ \. _|_ ./ {Powershell Gods}
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ \. .._.. ./ deitydragon
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ \ __________ /
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
"@
Clear-Host
Write-Host -ForegroundColor Red $logo
}
function Display-Success() {
param(
[Parameter(Mandatory=$true)]
[string]$ArtifactPath
)
Clear-Host
Write-Host -NoNewline -ForegroundColor Yellow -BackgroundColor DarkRed " 🐲💖🔥>~ BUILD SUCCESSFUL! ~<🔥💖🐲 "
Write-Host -NoNewline -ForegroundColor White "`n`n`r Your "
Write-Host -NoNewline -ForegroundColor Red "BlAse"
Write-Host -NoNewline -ForegroundColor Yellow "prite"
Write-Host -NoNewline -ForegroundColor White " build can be found at:`n`n`r"
Write-Host -NoNewline -ForegroundColor Cyan " $ArtifactPath`n`n`r"
Write-Host -NoNewline -ForegroundColor Yellow " 💫 Thank you"
Write-Host -NoNewline -ForegroundColor White " for using "
Write-Host -NoNewline -ForegroundColor Red "BlAse"
Write-Host -NoNewline -ForegroundColor White "!`n`n`r"
}
<#
.SYNOPSIS
Ridiculous Output Augmentation Routine - R. O. A. R.
.DESCRIPTION
This function invokes a command. It displays a timer with resource usage while the command is running. It also displays the command name, and entertains the user with an animation.
.PARAMETER Command
The command to invoke.
.PARAMETER Arguments
The arguments to pass to the command.
.PARAMETER Plain
If this switch is specified, the output will arrive sanitized with no ANSI escape codes.
.EXAMPLE
Invoke-Roar -Command "choco" -Arguments @("install", "7zip")
.NOTES
Roar XD
#>
function Roar {
param (
[Parameter(Mandatory=$true)]
[string]$Command,
[Parameter(Mandatory=$false)]
[string[]]$Arguments,
[Parameter(Mandatory=$false)]
[switch]$Plain
)
# Initialize the queues for the stdout, stderr, and timer
$stdoutQueue = [System.Collections.Queue]::Synchronized((New-Object System.Collections.Queue))
$stderrQueue = [System.Collections.Queue]::Synchronized((New-Object System.Collections.Queue))
$timerQueue = [System.Collections.Queue]::Synchronized((New-Object System.Collections.Queue))
# Create an initial session state with the current environment variables
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$initialSessionState.Variables.Add(
[System.Management.Automation.Runspaces.SessionStateVariableEntry]::new(
"env", [System.Environment]::GetEnvironmentVariables(), "Environment variables"
)
)
# Command execution runspace
$cmdExecRunspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($initialSessionState)
$cmdExecRunspace.Open()
$cmdExecPowershell = [System.Management.Automation.PowerShell]::Create()
$cmdExecPowershell.Runspace = $cmdExecRunspace
$currentDirectory = Get-Location
$cmdExecPowershell = $cmdExecPowershell.AddScript({
param(
[Parameter(Mandatory=$true)]
[string]$Command,
[Parameter(Mandatory=$false)]
[string]$argumentString,
[Parameter(Mandatory=$false)]
[string]$WorkingDirectory
)
$exitCode = $null
try {
if ($null -ne $WorkingDirectory) {
if (-not (Test-Path -Path $WorkingDirectory)) {
Write-Error "Working directory does not exist: $WorkingDirectory"
throw [System.IO.DirectoryNotFoundException]::new("Working directory does not exist: $WorkingDirectory")
}
Set-Location -Path $WorkingDirectory
}
Invoke-Expression "& `"$Command`" $argumentString 2>&1" | Write-Information
$exitCode = $LASTEXITCODE
} catch {
# Do nothing
} finally {
[PSCustomObject]@{
ExitCode = $exitCode
}
}
}).AddArgument($Command).AddArgument($($Arguments -join ' ')).AddArgument($currentDirectory)
# Start the command execution runspace
$cmdExecAsync = $cmdExecPowershell.BeginInvoke()
# Set up the output processing runspace
$outputProcessingRunspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($initialSessionState)
$outputProcessingRunspace.Open()
$outputProcessingPowershell = [System.Management.Automation.PowerShell]::Create()
$outputProcessingPowershell.Runspace = $outputProcessingRunspace
$outputProcessingPowershell = $outputProcessingPowershell.AddScript({
param(
[System.IAsyncResult]$ResultAsync,
[System.Management.Automation.PowerShell]$powershell,
[System.Collections.Queue]$StdoutQueue,
[System.Collections.Queue]$StderrQueue
)
while (-not $ResultAsync.IsCompleted) {
# The only thing that we would format as an error is actually an error.
if ($powershell.Streams.Error.Count -gt 0) {
$powershell.Streams.Error.ReadAll().ForEach({ $_.Exception.Message.ToString() }) | ForEach-Object {
$stderrQueue.Enqueue(($_))
}
$powershell.Streams.Error.Clear()
}
# The rest of it gets put on the standard pipeline.
if ($powershell.Streams.Information.Count -gt 0) {
$powershell.Streams.Information.ReadAll().ForEach({ $_.MessageData }) | ForEach-Object {
$stdoutQueue.Enqueue(($_.ToString() -replace '\x1B\[[0-9;]*m' -replace "`r`n", ""))
}
$powershell.Streams.Information.Clear()
}
if ($powershell.Streams.Verbose.Count -gt 0) {
$powershell.Streams.Verbose.ReadAll().ForEach({ $_.Message }) | ForEach-Object {
$stdoutQueue.Enqueue(($_.ToString() -replace '\x1B\[[0-9;]*m' -replace "`r`n", ""))
}
$powershell.Streams.Verbose.Clear()
}
if ($powershell.Streams.Warning.Count -gt 0) {
$powershell.Streams.Warning.ReadAll().ForEach({ $_.Message }) | ForEach-Object {
$stdoutQueue.Enqueue(($_.ToString() -replace '\x1B\[[0-9;]*m' -replace "`r`n", ""))
}
$powershell.Streams.Warning.Clear()
}
if ($powershell.Streams.Debug.Count -gt 0) {
$powershell.Streams.Debug.ReadAll().ForEach({ $_.Message }) | ForEach-Object {
$stdoutQueue.Enqueue(($_.ToString() -replace '\x1B\[[0-9;]*m' -replace "`r`n", ""))
}
$powershell.Streams.Debug.Clear()
}
}
}).AddArgument($cmdExecAsync).AddArgument($cmdExecPowershell).AddArgument($stdoutQueue).AddArgument($stderrQueue)
# Start the output processing runspace
$outputProcessingAsync = $outputProcessingPowershell.InvokeAsync()
# Set up the display runspace with the timer queue
$displayRunspace = [runspacefactory]::CreateRunspace()
$displayRunspace.Open()
$displayPowershell = [System.Management.Automation.PowerShell]::Create()
$displayPowershell.Runspace = $displayRunspace
$displayPowershell = $displayPowershell.AddScript({
param($Command, $queue)
$executableName = [System.IO.Path]::GetFileName($Command)
$executableName = $executableName.Substring(0, [Math]::Min(32, $executableName.Length))
$executableName = $executableName.PadRight(32)
$cursorStates = '|/-\'
$cursorIndex = 0
$startTime = Get-Date
$dragonCycle = 4000
$fireCycle = 1000
$boomCycle = 300
$sequenceCycle = $dragonCycle + $fireCycle + $boomCycle
while ($true) {
$elapsed = [datetime]::Now - $startTime
$sequencePosition = $elapsed.Milliseconds % $sequenceCycle
$queue.Enqueue(@{
Cursor = $cursorStates[$cursorIndex % $cursorStates.Length]
ElapsedStr = $("{0:D2}:{1:D2}:{2:D2}.{3:D3}" -f $elapsed.Hours, $elapsed.Minutes, $elapsed.Seconds, $elapsed.Milliseconds)
ExecutableName = $executableName
DragonEmoji = if ($sequencePosition % $dragonCycle -lt $dragonCycle / 4 -and $sequencePosition % 500 -lt 250) { "🐉" } else { " " }
FireEmoji = if ($sequencePosition % $fireCycle -lt $fireCycle / 1.25 -and $sequencePosition % 500 -lt 250 ) { "🔥" } else { " " }
BoomEmoji = if ($sequencePosition % $boomCycle -lt $boomCycle / 4 -and $sequencePosition % 250 -lt 125) { "💥" } else { " " }
Roaring = if ($elapsed.Milliseconds % 1000 -lt 500) { "blep!" } else { " " }
})
$cursorIndex++
Start-Sleep -Milliseconds 1
}
}).AddArgument($Command).AddArgument($timerQueue)
# Start the display runspace
$displayPowershellAsync = $displayPowershell.InvokeAsync()
# Main thread (draws the HUD and waits for the command to complete)
Write-Host "`n`n`r" -NoNewline
$Top = [System.Console]::CursorTop - 2
[System.Console]::CursorVisible = $false
while (-not $cmdExecAsync.IsCompleted -or $stdoutQueue.Count -gt 0 -or $stderrQueue.Count -gt 0) {
while ($stdoutQueue.Count -gt 0) {
$output = $stdoutQueue.Dequeue()
if (-not $Plain) {
[System.Console]::SetCursorPosition([System.Console]::CursorLeft, $Top)
Write-Host -NoNewline $("`r" + (" " * ([Console]::BufferWidth - 1)) + "`r")
Write-Host -NoNewline -ForegroundColor Magenta $($output.Substring(0, [Math]::Min($output.Length, [Console]::BufferWidth - 2)) + "`r")
} else {
Write-Host -ForegroundColor Magenta $output
}
}
while ($stderrQueue.Count -gt 0) {
$output = $stderrQueue.Dequeue()
if (-not $Plain) {
[System.Console]::SetCursorPosition([System.Console]::CursorLeft, $Top)
Write-Host -NoNewline $("`r" + (" " * ([Console]::BufferWidth - 1)) + "`r")
Write-Host -NoNewline -ForegroundColor Red $($output.Substring(0, [Math]::Min($output.Length, [Console]::BufferWidth - 2)) + "`r")
} else {
Write-Host -ForegroundColor Red $output
}
}
while ($timerQueue.Count -gt 0) {
$outputHashTable = $timerQueue.Dequeue()
if (-not $Plain) {
[System.Console]::SetCursorPosition([System.Console]::CursorLeft, $Top + 1)
Write-Host -NoNewline -ForegroundColor White -BackgroundColor Red " $([string]::Format("{0,1}", $outputHashTable.Cursor)) "
Write-Host -NoNewline -ForegroundColor Yellow -BackgroundColor DarkRed " $([string]::Format('{0,6}', $outputHashTable.Roaring)) "
Write-Host -NoNewline -ForegroundColor Black -BackgroundColor DarkRed " $([string]::Format('{0,20}', $outputHashTable.ExecutableName)) "
Write-Host -NoNewline -ForegroundColor White -BackgroundColor DarkRed " $([string]::Format('{0,12}', $outputHashTable.ElapsedStr)) "
Write-Host -NoNewline -ForegroundColor Yellow -BackgroundColor Red " $([string]::Format('{0,1}', $outputHashTable.DragonEmoji))"
Write-Host -NoNewline -ForegroundColor Yellow -BackgroundColor Red " $([string]::Format('{0,1}', $outputHashTable.FireEmoji))"
Write-Host -NoNewline -ForegroundColor Yellow -BackgroundColor Red " $([string]::Format('{0,1}', $outputHashTable.BoomEmoji)) `r"
} else {
# No timer updates if in plain mode, we just flush the buffer.
}
}
}
if (-not $Plain) {
[System.Console]::SetCursorPosition([System.Console]::CursorLeft, $Top)
Write-Host -NoNewline $("`r" + (" " * ([Console]::BufferWidth - 1)) + "`r")
[System.Console]::SetCursorPosition([System.Console]::CursorLeft, $Top + 1)
Write-Host -NoNewline $("`r" + (" " * ([Console]::BufferWidth - 1)) + "`r")
[System.Console]::SetCursorPosition([System.Console]::CursorLeft, $Top)
}
[System.Console]::CursorVisible = $true
# Close the output processing runspace
$outputProcessingPowershell.Stop()
$outputProcessingPowershell.Dispose()
$outputProcessingRunspace.Close()
$outputProcessingRunspace.Dispose()
# Close the timer runspace
$displayPowershell.Stop()
$displayPowershell.Dispose()
$displayRunspace.Close()
$displayRunspace.Dispose()
# Stop the command execution runspace
$exitCodeResult = $cmdExecPowershell.EndInvoke($cmdExecAsync)
$exitCode = $null -eq $exitCodeResult.ExitCode ? 0 : $exitCodeResult.ExitCode
# Merge the changed environment variables from the runspace into the current environment
$runspaceEnvironment = $cmdExecPowershell.AddScript('[System.Environment]::GetEnvironmentVariables()').Invoke()
foreach ($key in $runspaceEnvironment[0].Keys) {
[System.Environment]::SetEnvironmentVariable($key, $runspaceEnvironment[0][$key], [System.EnvironmentVariableTarget]::Process)
}
# Close the command execution runspace
$cmdExecPowershell.Dispose()
$cmdExecRunspace.Close()
$cmdExecRunspace.Dispose()
# Destroy the queues
$stdoutQueue.Clear()
$stderrQueue.Clear()
$timerQueue.Clear()
# Send powershell instances to the garbage collector
$cmdExecPowershell = $null
$outputProcessingPowershell = $null
$displayPowershell = $null
[System.GC]::Collect()
# Return the exit code
return $exitCode
}
##############################################
# PACKAGE MANAGER API
##############################################
<#
.SYNOPSIS
This function allows you to install packages from various package managers.
.DESCRIPTION
This function allows you to install packages from various package managers. It will not install a package if it is already installed, instead, it will try to update it. Status will be displayed in the terminal, along with a results summary. This function can install packages from Chocolatey, Winget, and Scoop.
.PARAMETER Packages
A string array of packages to install.
.PARAMETER PackageType
The package manager to use. Valid values are: choco, winget, scoop
.EXAMPLE
$Packages = @(
"7zip",
"git",
"notepadplusplus",
"vscode"
)
Install-Packages -Packages $Packages -PackageType "choco"
.NOTES
This function does not offer the capability to uninstall packages, as it is designed around working non-destructively.
#>
function Install-Packages {
param (
[string[]]$Packages,
[ValidateSet("choco", "winget", "scoop", IgnoreCase = $true)]
[string]$PackageType
)
$result = Install-PackageManager -PackageManager $PackageType
if ($result -eq $false) {
Write-Host -ForegroundColor Red "❌ An error occoured with the package manager check, the script will now exit."
return $false
}
$succeededPackages = @()
$failedPackages = @()
ForEach ($Package in $Packages) {
switch ($PackageType.ToLower()) {
"choco" {
if ($null -eq $(choco list --local-only | Where-Object { $_ -like "$PackageName*" } )) {
Write-Host -ForegroundColor Yellow "⏳ Installing $Package..."
choco install $Package --confirm
} else {
Write-Host -ForegroundColor Yellow "⚠️ Package $Package is already installed, attempting to upgrade it, instead."
choco upgrade $Package --confirm
}
}
"winget" {
if ($null -eq $(winget list | Where-Object { $_ -like "$PackageName*" } )) {
Write-Host -ForegroundColor Yellow "⏳ Installing $Package..."
winget install $Package --disable-interactivity
} else {
Write-Host -ForegroundColor Yellow "⚠️ Package $Package is already installed, attempting to upgrade it, instead."
winget upgrade $Package --disable-interactivity
}
}
"scoop" {
if ($null -eq $(scoop list | Where-Object { $_ -like "$PackageName*" } )) {
Write-Host -ForegroundColor Yellow "⏳ Installing $Package..."
scoop install $Package
} else {
Write-Host -ForegroundColor Yellow "⚠️ Package $Package is already installed, attempting to upgrade it, instead."
scoop update $Package
}
}
}
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Host -ForegroundColor Red "❌ Operation failed for $PackageType package: $Package"
$failedPackages += $Package
continue
}
}
$succeededPackages += $Package
refreshenv
. $PROFILE
}
if ($failedPackages.Count -gt 0) {
$errorString = "Failed to install $($failedPackages.Count) package(s): "
$failedPackages | ForEach-Object { $errorString += $($_ + " ") }
Write-Host -ForegroundColor Red "❌ $errorString"
return $false
}
if ($succeededPackages.Count -gt 0) {
$successString = "Succeeded installing $($succeededPackages.Count) package(s): "
$succeededPackages | ForEach-Object { $successString += $($_ + " ") }
Write-Host -ForegroundColor Green "✅ $successString"
return $true
}
}
<#
.SYNOPSIS
This function is able to install various package managers by using PowerShell.
.DESCRIPTION
This function is able to install various package managers by using PowerShell. It will not install a package manager if it is already installed. Status will be displayed in the terminal, along with a results summary. This function can install Chocolatey, Winget, and Scoop.
.PARAMETER PackageManager
The package manager to install. Valid values are: choco, winget, scoop
.EXAMPLE
Install-PackageManager -PackageManager "choco"
.NOTES
This function does not offer the capability to uninstall package managers, as it is designed around working non-destructively.
#>
function Install-PackageManager {
param (
[ValidateSet("choco", "winget", "scoop", IgnoreCase = $true)]
[string]$PackageManager
)
try {
switch ($PackageManager) {
"scoop" {
if ($null -eq $(Get-Command -Name "scoop" -ErrorAction SilentlyContinue)) {
Write-Host -ForegroundColor Yellow "⏳ Scoop is not installed. Installing Scoop..."
Start-Process pwsh -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command `"& { iwr -useb get.scoop.sh | iex }`""
if ($LASTEXITCODE -ne 0 -or $null -eq $(Get-Command -Name "scoop" -ErrorAction SilentlyContinue)) {
throw "Failed to install Scoop."
}
. $PROFILE
} else {
Write-Host -ForegroundColor Green "✅ Scoop is installed and ready to be used."
return $true
}
}
"choco" {
if ($null -eq $(Get-Command -Name "choco" -ErrorAction SilentlyContinue)) {
Write-Host -ForegroundColor Yellow "⏳ Chocolatey is not installed. Installing Chocolatey..."
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
if ($LASTEXITCODE -ne 0 -or -not $(Test-Path -Path "$env:ChocolateyInstall")) {
throw "Failed to install Chocolatey"
}
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
refreshenv
. $PROFILE
choco feature enable -n allowGlobalConfirmation
} else {
Write-Host -ForegroundColor Green "✅ Chocolatey is installed, proceeding"
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
refreshenv
. $PROFILE
return $true
}
}
"winget" {
if ($null -eq $(Get-Command -Name "winget" -ErrorAction SilentlyContinue)) {
Write-Host -ForegroundColor Yellow "⏳ Winget is not installed. Installing Winget..."
Install-Module -Name Microsoft.WinGet.Client
if ($LASTEXITCODE -ne 0 -or $null -eq $(Get-Command -Name "winget" -ErrorAction SilentlyContinue)) {
throw "Failed to install Winget, the script will now exit."
}
} else {
Write-Host -ForegroundColor Green "✅ Winget is installed and ready to be used."
return $true
}
}
}
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
##############################################
# VISUAL STUDIO TOOLS
##############################################
<#
.SYNOPSIS
Get an available Windows SDK package name from Chocolatey.
.DESCRIPTION
This function searches Chocolatey for a Windows SDK package name that matches the version specified. It will return the package name if it is found, or null if it is not found.
.PARAMETER sdkVersion
The version of the Windows SDK to search for.
.PARAMETER WinDbg
This switch is used to indicate that the Windows SDK package should include WinDbg.
.EXAMPLE
Get-WindowsSdkPackage -sdkVersion "10.0.18362.0" -WinDbg
.NOTES
This function is used to get an available Windows SDK package name from Chocolatey.
#>
function Get-WindowsSdkPackage() {
param(
[string]$sdkVersion,
[switch]$WinDbg
)
$chocoOutput = choco search windows-sdk | Out-String
$versionPattern = [regex]::Escape($sdkVersion)
$winDbgPattern = $WinDbg ? "windbg" : "all"
$pattern = "(windows-sdk-.*($winDbgPattern)?)\s+($versionPattern)\s+\[Approved\].*"
if ($chocoOutput -match $pattern) {
$packageName = $matches[1]
$packageVersion = $matches[3]
Write-Host -ForegroundColor Green "✅ Found package $packageName version $packageVersion"
return $packageName
} else {
Write-Host -ForegroundColor Red "❌ No matching package found for SDK version $sdkVersion"
return $null
}
}
<#
.SYNOPSIS
Find the installation folder of a Windows SDK version.
.DESCRIPTION
This function searches the registry for a Windows SDK version, and returns the installation folder if it is found, or null if it is not found.
.PARAMETER SdkVersion
The version of the Windows SDK to search for.
.EXAMPLE
Find-WindowsSDK -SdkVersion "10.0.18362.0"
.NOTES
The fnction returns $null if the SDK version is not found.
#>
function Find-WindowsSDK() {
param(
[string]$SdkVersion
)
try {
if ($SdkVersion -match "\d+\.\d+\.\d+") {
$SdkVersion = $SdkVersion + ".0"
}
$sdkVersion = [Regex]::Replace($SdkVersion, '(\d+)(?=[^\d]*$)', '0')
$SdkVersions = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots"
$InstallationRoot = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots").KitsRoot10
foreach ($version in $SdkVersions) {
$productVersion = $version.PSChildName
if ($productVersion -like "$sdkVersion*") {
$installationFolder = $("{0}bin\{1}" -f ($InstallationRoot, $productVersion))
if ((Test-Path $installationFolder)) {
Write-Host -ForegroundColor Green "✅ Windows SDK Version found: $productVersion, Installation Folder: $installationFolder, Product Version: $productVersion"
return $installationFolder
}
}
}
Write-Host -ForegroundColor Red "❌ SDK version $sdkVersion not found."
return $null
} catch {
Write-Host -ForegroundColor Red "❌ SDK version $sdkVersion not found: $($_.Exception.Message)"
return $null
}
}
<#
.SYNOPSIS
Find the installation folder of a Visual Studio version.
.DESCRIPTION
This function searches the registry for a Visual Studio version, and returns the installation folder if it is found, or null if it is not found.
.PARAMETER VsVersion
The version of Visual Studio to search for.
.EXAMPLE
Find-VsInstallPath -VsVersion "16.0"
.NOTES
The function returns $null if the Visual Studio version is not found. For versions greater than 15.0, the function will use vswhere.exe to find the installation path.
#>
function Find-VsInstallPath {
param(
[string]$VsVersion
)
$regPaths = @(
"HKLM:\SOFTWARE\Microsoft\VisualStudio\$VsVersion",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\$VsVersion",
"HKLM:\SOFTWARE\Microsoft\VisualStudio\$VsVersion\Setup\VS",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\$VsVersion\Setup\VS"
)
try {
foreach ($regPath in $regPaths) {
if (Test-Path $regPath) {
$vsInstallPath = (Get-ItemProperty -Path $regPath).InstallDir
if ($vsInstallPath) {
return $vsInstallPath
}
}
}
# For VS 2017 and later
if ($VsVersion -ge "15.0") {
if (-not (Test-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"))
{
throw "vswhere.exe not found."
return $null
}
$VsVersionLow = $VsVersion -replace "\d+$", "0"
$VsVersionHigh = $VsVersion -replace "\d+$", "999"
$vswhereOutput = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -version "[${VsVersionLow},${VsVersionHigh}]" -property "installationPath" -nocolor -nologo -format value
if ($vswhereOutput) {
return $vswhereOutput
}
throw "vswhere.exe did not return a valid path."
}
} catch {
Write-Host -ForegroundColor Red "❌ Visual Studio $VsVersion not found: $($_.Exception.Message)"
return $null
}
}
<#
.SYNOPSIS
This function installs Visual Studio components using the Visual Studio Installer.
.DESCRIPTION
This function installs Visual Studio components. It will attempt to install the components, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER components
A string array of components to install.
.EXAMPLE
Install-VSComponents -components @("Microsoft.VisualStudio.Workload.NativeDesktop", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64")
.NOTES
This function does not offer the capability to uninstall Visual Studio components, as it is designed around working non-destructively.
#>
function Install-VSComponents {
param (
[string[]]$Components
)
try {
$output = "$env:ASE_CACHEPATH\vs_community.exe"
if (-not (Test-Path $output)) {
Write-Host -ForegroundColor Yellow "⏳ Downloading Visual Studio Installer..."
Start-BitsTransfer -Source "http://aka.ms/vs/16/release/vs_community.exe" -Destination $output
} else {
Write-Host -ForegroundColor Green "✅ Visual Studio Installer download found in cache."
}
foreach ($component in $Components) {
$arguments = @(
"--quiet",
"--wait",
"--norestart",
"--nocache",
"--installPath `"$env:AES_VISUALSTUDIO`"",
"--add $component"
)
Write-Host -ForegroundColor Yellow "⏳ Installing Visual Studio component $component..."
Start-Process -FilePath "$env:ASE_CACHEPATH\vs_community.exe" -ArgumentList $arguments -Wait -NoNewWindow
if ($LASTEXITCODE -ne 0) {
throw "Visual Studio installation of component $component failed."
}
Write-Host -ForegroundColor Green "✅ Visual Studio component $component installed."
}
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
return $true
}
##############################################
# PYTHON TOOLS
##############################################
<#
.SYNOPSIS
This function checks and returns information about a specific installed Python version
.DESCRIPTION
This function checks and returns information about a specific installed Python version. It will return a hashtable with the following keys: Path, ScriptsPath. If the Python version is not found, it will return $null.
.PARAMETER version
The version of Python to check for.
.EXAMPLE
Check-PythonInstallation -version "3.9"
#>
function Check-Python {
param (
[Parameter(Mandatory=$true)]
[string]$version
)
# if the version includes a patch number, strip it off.
if ($Version -imatch '^\d+\.\d+\.\d+$') {
$SimpleVersion = $Version -replace "\.\d+$", ""
} else {
$SimpleVersion = $Version
}
# Check for the existance of the python registry key
$sourceKey = $null
$keysToSearch = @(
"HKLM:\SOFTWARE\WOW6432Node\Python\PythonCore\$SimpleVersion\InstallPath",
"HKLM:\SOFTWARE\WOW6432Node\Python\$SimpleVersion\InstallPath",
"HKLM:\SOFTWARE\Python\PythonCore\$SimpleVersion\InstallPath",
"HKLM:\SOFTWARE\Python\$SimpleVersion\InstallPath"
"HKCU:\SOFTWARE\WOW6432Node\Python\PythonCore\$SimpleVersion\InstallPath",
"HKCU:\SOFTWARE\WOW6432Node\Python\$SimpleVersion\InstallPath",
"HKCU:\SOFTWARE\Python\PythonCore\$SimpleVersion\InstallPath",
"HKCU:\SOFTWARE\Python\$SimpleVersion\InstallPath"
)
$keysToSearch | ForEach-Object {
if (Test-Path $_) {
$sourceKey = $_
}
}
if ($null -eq $sourceKey) {
Write-Debug "Python $Version registry key not found."
return $null
} else {
Write-Debug "Python $Version registry key found at: $sourceKey"
$pythonInstalled = $true
}
# Check for the existance of the python installation folder and scripts folder.
$pythonPath = (Get-ItemProperty -Path "$sourceKey")."(Default)"
$pythonScriptsPath = Join-Path -Path $pythonPath -ChildPath "Scripts"
$pythonInstalled = ($pythonInstalled -eq $true) -and (Test-Path $pythonPath) -and (Test-Path $pythonScriptsPath)
if ($pythonInstalled -eq $true) {
Write-Debug "Python $Version installation folder and scripts folder exist."
} else {
Write-Debug "Python $Version installation folder and scripts folder do not exist."
return $null
}
# Check that the Python binary version matches the one we are testing for
$pythonVersion = $(Invoke-Expression "& `"$pythonPath\python.exe`" --version 2>&1")
$pythonInstalled = $pythonInstalled -eq $true -and $pythonVersion -like "Python $Version*"
if ($pythonInstalled -eq $true) {
Write-Debug "Python $Version binary exists and matches."
} else {
Write-Debug "Python $Version binary does not exist or does not match version $Version."
}
if ($pythonInstalled) {
return [hashtable]@{
"Path" = $pythonPath
"ScriptsPath" = $pythonScriptsPath
}
} else {
return $null
}
}
<#
.SYNOPSIS
This function installs a specific version of Python.
.DESCRIPTION
This function installs a specific version of Python. It will attempt to install the Python version, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER version
The version of Python to install.
.EXAMPLE
Install-Python -version "3.9"
.NOTES
#>
function Install-Python() {
param(
[Parameter(Mandatory=$true)]
[string]$Version
)
# Attempt to find a Python installer by using different URLs from python's FTP server.
#
$installerPath = $null
$installerPaths = @(
[hashtable]@{
CachePath = "$env:ASE_CACHEPATH\python-$Version-amd64.exe"
URL = "https://www.python.org/ftp/python/$Version/python-$Version-amd64.exe"
Type = "exe"
},
[hashtable]@{
CachePath = "$env:ASE_CACHEPATH\python-$Version-amd64.msi"
URL = "https://www.python.org/ftp/python/$Version/python-$Version-amd64.msi"
Type = "msi"
},
[hashtable]@{
CachePath = "$env:ASE_CACHEPATH\python-$Version.amd64.exe"
URL = "https://www.python.org/ftp/python/$Version/python-$Version.amd64.exe"
Type = "exe"
},
[hashtable]@{
CachePath = "$env:ASE_CACHEPATH\python-$Version.and64.msi"
URL = "https://www.python.org/ftp/python/$Version/python-$Version.amd64.msi"
Type = "msi"
}
)
foreach ($potentialPath in $installerPaths) {
if (Test-Path $potentialPath.CachePath) {
$installerPath = $potentialPath
Write-Host -ForegroundColor Green "✅ Python $Version installer found in cache at: $($installerPath.CachePath)"
break
} else {
Write-Host -ForegroundColor Yellow "⏳ Trying to download Python $Version installer..."
try {
Invoke-WebRequest -Uri $potentialPath.URL -OutFile $potentialPath.CachePath
$installerPath = $potentialPath
Write-Host -ForegroundColor Green "✅ Python $Version installer downloaded to cache at: $($installerPath.CachePath)"
break
} catch {
Write-Host -ForegroundColor Red "❌ Failed to download Python $Version installer: $($_.Exception.Message)"
continue
}
}
}
if ($null -eq $installerPath) {
Write-Host -ForegroundColor Red "❌ Python $Version installer could not be found or downloaded."
return $false
}
# Now we select appropriate command line arguments for the installation to proceed.
switch ($installerPath.Type) {
"exe" {
$command = "$($installerPath.CachePath)"
$arguments = @(
"/quiet",
"InstallAllUsers=1",
"PrependPath=1",
"Include_test=1"
) -join ' '
break
}
"msi" {
$command = "msiexec.exe"
$arguments = @(
"/i"
"`"$($installerPath.CachePath)`""
"/quiet",
"InstallAllUsers=1",
"PrependPath=1",
"Include_test=1"
) -join ' '
break
}
default {
Write-Host -ForegroundColor Red "❌ Python $Version installer type is not supported."
return $false
}
}
# Run the installer, if this fails, return false.
Write-Host -ForegroundColor Yellow "⏳ Installing Python $Version..."
Start-Process -FilePath $command -ArgumentList $arguments -Wait -NoNewWindow
if ($LASTEXITCODE -ne 0) {
Write-Host -ForegroundColor Red "❌ Python $Version installation failed."
return $false
} else {
Write-Host -ForegroundColor Green "✅ Python $Version installed."
return $true
}
}
<#
.SYNOPSIS
This function sets the Python version to use for the build environment.
.DESCRIPTION
This function sets the Python version to use for the build environment. It will attempt to set the Python version, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER version
The version of Python to use.
.EXAMPLE
Set-PythonVersion -version "3.9"
.NOTES
None
#>
function Set-Python {
param (
[string]$Version,
[switch]$Install
)
# Check if the Python version is installed. If we have the -Install switch, then install it
# automatically if it is not installed.
$pythonInfo = Check-Python -version $Version
if ($null -eq $pythonInfo) {
$env:ASE_PYTHONPATH = $null
if (-not $Install) {
Write-Host -ForegroundColor Red "❌ Could not select Python $Version"
return $null
}
$result = Install-Python -version $Version
if ($result -eq $false) {
Write-Host -ForegroundColor Red "❌ Python $Version could not be automatically installed."
return $null
}
}
# Check that the python version is installed properly.
$pythonInfo = Check-Python -version $Version
if ($null -eq $pythonInfo) {
Write-Host -ForegroundColor Red "❌ Could not select Python $Version"
return $null
}
# Set the environment variables, and update the script path.
$currentPath = $env:Path
$existingPythonPaths = $currentPath -split ";" | Where-Object { $_ -match "C:\\Python[0-9.]*" }
Mod-Path -Remove $existingPythonPaths -Add @($pythonInfo.Path, $pythonInfo.ScriptsPath)
$env:ASE_PYTHONPATH = $pythonInfo.Path
Write-Host -ForegroundColor Green "✅ Selected Python $Version."
return $pythonInfo
}
##############################################
# BUILD TOOLS
##############################################
<#
.SYNOPSIS
This function is used to enter or leave the Visual Studio development environment.
.DESCRIPTION
This function is used to enter or leave the Visual Studio development environment. It will attempt to enter or leave the environment, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER Action
The action to perform. Valid values are: enter, leave
.PARAMETER Compiler
The compiler to use. Valid values are: msvc, clang
.EXAMPLE
Toggle-DevelopmentEnvironment -Action "Enter"
.NOTES
None
#>
function Toggle-DevelopmentEnvironment {
param (
[Parameter(Mandatory=$true)]
[ValidateSet("enter", "leave", IgnoreCase = $true)]
[string]$Action,
[Parameter(Mandatory=$true)]
[ValidateSet("msvc", "vsclang", "blaseclang", IgnoreCase = $true)]
[string]$Compiler
)
switch ($Action.ToLower()) {
"enter" {
try {
Import-Module (Get-ChildItem "$env:ASE_VISUALSTUDIOPATH" -Recurse -File -Filter Microsoft.VisualStudio.DevShell.dll).FullName
$devCmdArguments = '-no_logo'
$devCmdArguments += ' -arch=x64'
Enter-VsDevShell -VsInstallPath "$env:ASE_VISUALSTUDIOPATH" -SkipAutomaticLocation -DevCmdArguments $devCmdArguments
if ($Compiler.ToLower() -eq "msvc") {
$env:CC = "cl.exe"
$env:CXX = "cl.exe"
$env:CMAKE_C_COMPILER = "cl.exe"
$env:CMAKE_CXX_COMPILER = "cl.exe"
} elseif ($Compiler.ToLower() -eq "vsclang") {
$env:CC = "clang-cl.exe"
$env:CXX = "clang-cl.exe"
$env:CMAKE_C_COMPILER = "clang-cl.exe"
$env:CMAKE_CXX_COMPILER = "clang-cl.exe"
} elseif ($Compiler.ToLower() -eq "blaseclang") {
$env:CC = "$env:ASE_LLVMROOT\bin\clang-cl.exe"
$env:CXX = "$env:ASE_LLVMROOT\bin\clang-cl.exe"
$env:CMAKE_C_COMPILER = "$env:ASE_LLVMROOT\bin\clang-cl.exe"
$env:CMAKE_CXX_COMPILER = "$env:ASE_LLVMROOT\bin\clang-cl.exe"
}
return $true
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
"leave" {
try {
Remove-Module Microsoft.VisualStudio.DevShell
$env:CC = $null
$env:CXX = $null
$env:CMAKE_C_COMPILER = $null
$env:CMAKE_CXX_COMPILER = $null
return $true
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
}
}
<#
.SYNOPSIS
Modifies the PATH environment variable.
.DESCRIPTION
This function is used to modify the PATH environment variable. It will attempt to modify the PATH, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER Add
A string array of paths to add to the PATH environment variable.
.PARAMETER Remove
A string array of paths to remove from the PATH environment variable.
.EXAMPLE
Mod-Path -Add @("C:\Program Files\Git\bin", "C:\Program Files\Git\cmd") -Remove @("C:\Program Files\Git\bin", "C:\Program Files\Git\cmd")
.NOTES
Doesn't do any error checking, so be careful with it.
#>
function Mod-Path() {
param(
[string[]]$Add,
[string[]]$Remove,
[switch]$WhatIf
)
if ($null -eq $Add) {
$Add = @()
}
if ($null -eq $Remove) {
$Remove = @()
}
$path = $env:Path
$pathElements = $path.Split(";")
$pathElements = $pathElements | Where-Object { $_ -notin $Remove -and $(Test-Path $_)} | Sort-Object
$pathElements = $pathElements + $($Add | Where-Object { $(Test-Path $_) -and ($_ -notin $pathElements) })
$newPath = $pathElements -join ";"
if ($WhatIf) {
Write-Host -ForegroundColor Yellow "🪳 WHATIF: Modifying PATH environment variable from:`m $path`n to:`n $newPath"
}
else {
$env:Path = $newPath
}
Write-Debug $env:Path
}
<#
.SYNOPSIS
This function is a wrapper around git clone, and is used to clone a repository.
.DESCRIPTION
This function is a wrapper around git clone, and is used to clone a repository. It will attempt to clone the repository, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER RepoUrl
The URL of the repository to clone.
.PARAMETER Tag
The tag to checkout after cloning the repository (optional)
.PARAMETER Branch
The branch to checkout after cloning the repository (optional)
.PARAMETER CommitHash
The commit hash to checkout after cloning the repository (optional)
.PARAMETER DeleteExisting
This switch is used to indicate that the existing repository should be deleted before cloning.
.PARAMETER OutputPath
The path to clone the repository to.
.PARAMETER Username
The username to use for the git clone operation (optional)
.PARAMETER Email
The email to use for the git clone operation (optional)
.PARAMETER CloneDepth
The depth to use for the git clone operation (optional)
.EXAMPLE
Invoke-GitClone -RepoUrl "https://loopyd.github.com/aseprite_build.git -Tag "v1.2.40" -OutputPath "C:\aseprite_build"
.NOTES
This code isn't optimized. I'm having ChatGFPT generate some things for
me because its the weekend and I have to work tomorrow. I will probably
go back in and give it a touch up at a later date.
#>
function Invoke-GitClone {
param (
[Parameter(Mandatory = $true)][string]$RepoUrl,
[Parameter(Mandatory = $false)][string]$Tag,
[Parameter(Mandatory = $false)][string]$Branch,
[Parameter(Mandatory = $false)][string]$CommitHash,
[Parameter(Mandatory = $false)][switch]$DeleteExisting,
[Parameter(Mandatory = $false)][string]$OutputPath = "./",
[Parameter(Mandatory = $false)][string]$Username,
[Parameter(Mandatory = $false)][string]$Email,
[Parameter(Mandatory = $false)][int]$CloneDepth
)
try {
# Check if repository already exists at the output path
if ($DeleteExisting -and (Test-Path $OutputPath)) {
Write-Host -ForegroundColor Yellow "⏳ Deleting existing repository at $OutputPath..."
Remove-Item -Path $OutputPath -Recurse -Force | Out-Null
if (Test-Path $OutputPath) {
throw "Failed to delete existing repository at $OutputPath"
}
}
elseif ($CleanExisting -and (Test-Path $OutputPath)) {
Write-Host -ForegroundColor Yellow "⏳ Cleaning existing repository at $OutputPath..."
$CurrentDirectory = Get-Location
Set-Location -Path $OutputPath
$arguments = @(
"reset",
"--hard"
)
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to clean existing repository at $OutputPath"
}
$arguments = @(
"clean",
"-fd"
)
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to clean existing repository at $OutputPath"
}
Set-Location -Path $CurrentDirectory
}
if (-not $CleanExisting) {
# Build the git clone command
$gitArgs = @(
"clone",
"--progress",
"--recurse-submodules",
"--config core.autocrlf=false"
)
if ($CloneDepth) {
$gitArgs += "--depth", "$CloneDepth"
}
$gitArgs += "$RepoUrl", "."
# Execute git clone
Write-Host -ForegroundColor Yellow "⏳ Cloning repository $RepoUrl to $OutputPath..."
New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
$CurrentDirectory = Get-Location
Set-Location -Path $OutputPath
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $gitArgs)
if ($lec -ne 0) {
throw "Failed to clone repository $RepoUrl to $OutputPath"
}
Set-Location -Path $CurrentDirectory
}
# Configure user credentials if provided
if ($Username -and $Email) {
Write-Host -ForegroundColor Yellow "⏳ Configuring git user credentials..."
$CurrentDirectory = Get-Location
Set-Location -Path $OutputPath
$arguments = @(
"config",
"--local",
"user.name",
$Username
)
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to configure git user credentials."
}
$arguments = @(
"config",
"--local",
"user.email",
$Email
)
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to configure git user credentials."
}
Set-Location -Path $CurrentDirectory
Write-Host -ForegroundColor Green "✅ Configured git user credentials."
}
# Checkout the specified tag, branch, or commit hash
if ($Tag) {
Write-Host -ForegroundColor Yellow "⏳ Checking out tag $Tag..."
$CurrentDirectory = Get-Location
Set-Location -Path $OutputPath
$arguments = @(
"checkout",
"tags/$Tag"
)
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to checkout tag $Tag"
}
Set-Location -Path $CurrentDirectory
Write-Host -ForegroundColor Green "✅ Checked out tag $Tag."
}
elseif ($Branch) {
Write-Host -ForegroundColor Yellow "⏳ Checking out branch $Branch..."
$CurrentDirectory = Get-Location
Set-Location -Path $OutputPath
$arguments = @(
"checkout",
$Branch
)
$lec = $(Roar -Command "$env:ASE_GITPATH\bin\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to checkout branch $Branch"
}
Set-Location -Path $CurrentDirectory
Write-Host -ForegroundColor Green "✅ Checked out branch $Branch."
}
elseif ($CommitHash) {
Write-Host -ForegroundColor Yellow "⏳ Checking out commit hash $CommitHash..."
$CurrentDirectory = Get-Location
Set-Location -Path $OutputPath
$arguments = @(
"checkout",
$CommitHash
)
$lec = $(Roar -Command "$env:ASE_GITPATH\git.exe" -Arguments $arguments)
if ($lec -ne 0) {
Set-Location -Path $CurrentDirectory
throw "Failed to checkout commit hash $CommitHash"
}
Set-Location -Path $CurrentDirectory
Write-Host -ForegroundColor Green "✅ Checked out commit hash $CommitHash."
}
return $true
} catch {
Write-Host "Error: $_"
throw
return $false
}
}
<#
.SYNOPSIS
This function creates a build artifact from a source directory.
.DESCRIPTION
This function creates a build artifact from a source directory. It will attempt to create the artifact, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER Version
The version of the artifact.
.PARAMETER Release
The release type of the artifact.
.PARAMETER Name
The name of the artifact.
.PARAMETER Include
The file types to include in the artifact as an array of glob patterns.
.PARAMETER Exclude
The file types to exclude from the artifact as an array of glob patterns.
.PARAMETER SourcePath
The path to the source directory.
.PARAMETER OutputPath
The path to the output directory.
.PARAMETER DeleteAfterComplete
If the switch is enabled, the source directory will be deleted after the artifact is created.
.PARAMETER DeleteExisting
If the switch is enabled, the existing artifact will be deleted before the new artifact is created.
.EXAMPLE
Make-Artifact -Version "1.2.3" -Release "1" -Name "MyArtifact" -Include @("*.dll", "*.exe") -Exclude @("*.pdb") -SourcePath "C:\MySource" -OutputPath "C:\MyOutput" -DeleteAfterComplete -DeleteExisting
.NOTES
Returns the path to the artifact if successful, otherwise returns null.
#>
function Emit-Artifact() {
param(
[Parameter(Mandatory = $true)][String]$Version,
[Parameter(Mandatory = $true)][String]$Release,
[Parameter(Mandatory = $true)][String]$Name,
[Parameter(Mandatory = $false)][String[]]$Include,
[Parameter(Mandatory = $false)][String[]]$Exclude,
[Parameter(Mandatory = $true)][String]$SourcePath,
[Parameter(Mandatory = $true)][String]$OutputPath,
[Parameter(Mandatory = $false)][switch]$DeleteAfterComplete,
[Parameter(Mandatory = $false)][switch]$DeleteExisting
)
if ($(Test-Path -Path "$OutputPath\$Name-$Version-$Release.zip") -eq $true -and -not $DeleteExisting) {
Write-Host -ForegroundColor Green "✅ Artifact already exists, nothing to do for: $OutputPath\$Name-$Version-$Release.zip"
return "$OutputPath\$Name-$Version-$Release.zip"
}
try {
# Produce a list of build artifacts to target based upon the include and exclude parameters
Write-Host -ForegroundColor Yellow "⏳ Validating build..."
$validFiles = @()
foreach ($item in $(Get-ChildItem -Path $SourcePath -Recurse -Force -ErrorAction SilentlyContinue)) {
# skip directories
if ($item.PSIsContainer) {
continue
}
# skip excluded files
$matchesString = $false
foreach ($excludeItem in $Exclude) {
if ($item.FullName -match $excludeItem) {
$matchesString = $true
}
}
if ($matchesString -eq $true) {
continue
}
# skip not included files
$matchesString = $false
foreach ($includeItem in $Include) {
if (-not ($item.FullName -match $includeItem)) {
$matchesString = $true
}
}
if ($matchesString -eq $true) {
continue
}
if (-not (Test-Path $item.FullName)) {
throw "Validation failed: File not found: $($item.FullName)"
}
$validFiles += $($item.FullName -replace [regex]::Escape($SourcePath), "")
}
# Delete existing build artifact if the switch is enabled
if ($DeleteExisting) {
Write-Host -ForegroundColor Yellow "⏳ Deleting existing artifact..."
Remove-Item -Path "$OutputPath\$Name-$Version-$Release.zip" -Force -ErrorAction SilentlyContinue
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete existing artifact: $OutputPath\$Name-$Version-$Release.zip"
}
Write-Host -ForegroundColor Green "✅ Existing artifact deleted successfully."
}
# Append valid files to the artifact
try {
$currentDirectory = Get-Location
Set-Location -Path $SourcePath
foreach ($file in $validFiles) {
Write-Host -ForegroundColor Yellow "⏳ Adding file to build artifact: $file"
& "$env:ASE_ZIPPATH\7z.exe" a -tzip "$OutputPath\$Name-$Version-$Release.zip" $file 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to add file to artifact: $file"
}
Write-Host -ForegroundColor Green "✅ File added to build artifact: $file"
}
Set-Location -Path $currentDirectory
} catch {
throw "$_.Exception.Message"
}
# Delete the source files if the switch is enabled
if ($DeleteAfterComplete) {
Write-Host -ForegroundColor Yellow "⏳ Deleting source: $SourcePath"
Remove-Item -Recurse -Force -Path $SourcePath | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete source: $SourcePath"
}
Write-Host -ForegroundColor Green "✅ Source deleted successfully."
}
Write-Host -ForegroundColor Green "✅ Artifact: $OutputPath\$Name-$Version-$Release.zip emmited successfully."
return "$OutputPath\$Name-$Version-$Release.zip"
}
catch {
Set-Location -Path $SourcePath
Write-Host -ForegroundColor Red "❌ Failed: $($_.Exception.Message)"
return $null
}
}
<#
.SYNOPSIS
This function ensures the existance of a dependency on the host system.
.DESCRIPTION
This function checks for a dependency and sets the environment variable for it. It will attempt to call its sister function Install-Dependency if the dependency is not found, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER DependencyName
The dependency to check for. Valid values are: 7zip, windowssdk, visualstudio, llvm, ninja, cmake, cache, skia, asesprite, git, depot_tools, python2
.PARAMETER PostOperation
This switch is used to indicate that the function should perform a post-operation check. This is used to ensure that the dependency was installed correctly.
.EXAMPLE
CheckDependency -DependencyName "7zip"
.NOTES
Simple wrapper around Install-Dependency, which is used to ensure the existance of a dependency on the host system.
#>
function Check-Dependency {
param (
[ValidateSet("7zip", "windowssdk", "visualstudio", "llvm", "ninja", "cmake", "cache", "skia", "asesprite", "git", "depot_tools", "python", IgnoreCase = $true)]
[string]$DependencyName,
[switch]$PostOperation
)
try {
switch ($DependencyName.ToLower()) {
"7zip" {
$zipRegistryPath = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip' -ErrorAction SilentlyContinue
if ($zipRegistryPath -and (Test-Path $zipRegistryPath.InstallLocation)) {
$env:ASE_ZIPPATH = $zipRegistryPath.InstallLocation
Write-Host -ForegroundColor Green "✅ 7-Zip was found at: $env:ASE_ZIPPATH"
} else {
throw "7-Zip not found in the registry or its installation path is invalid."
}
return $true
}
"windowssdk" {
$windowsSdk = Find-WindowsSDK -SdkVersion $WindowsSdkVersion
if ($windowsSdk -eq $null) {
throw "Windows SDK not found."
}
$env:ASE_WINDOWSSDK = $windowsSdk
return $true
}
"visualstudio" {
$vsInstallPath = Find-VsInstallPath -VsVersion $env:ASE_VISUALSTUDIOVERSION
if ($vsInstallPath -eq $null) {
throw "Visual Studio $env:ASE_VISUALSTUDIOVERSION not found."
}
$env:ASE_VISUALSTUDIOPATH = $vsInstallPath
return $true
}
'llvm' {
if (-not (Test-Path "$env:ASE_LLVMPATH")) {
throw "LLVM source code not found."
}
Write-Host -ForegroundColor Green "✅ LLVM source code found at $env:ASE_LLVMPATH"
return $true
}
'ninja' {
if (-not (Test-Path "$env:ASE_NINJAPATH")) {
throw "Ninja source code not found."
}
Write-Host -ForegroundColor Green "✅ Ninja source code found at $env:ASE_NINJAPATH"
return $true
}
'cmake' {
if (-not (Test-Path "$env:ASE_CMAKEPATH") -or -not (Test-Path "$env:ASE_CMAKEPATH\bin\cmake.exe")) {
throw "Cmake installation not found."
}
Write-Host -ForegroundColor Green "✅ CMake was found at $env:ASE_CMAKEPATH"
return $true
}
'cache' {
if (-not (Test-Path $env:ASE_CACHEPATH)) {
throw "Cache path not found."
} else {
Write-Host -ForegroundColor Green "✅ Cache path found at $env:ASE_CACHEPATH"
if ($env:ASE_CACHECLEAN -eq $true) {
Write-Host -ForegroundColor Yellow "⏳ Cleaning cache..."
Remove-Item -Path $env:ASE_CACHEPATH -Recurse -Force | Out-Null
New-Item -Path $env:ASE_CACHEPATH -ItemType Directory | Out-Null
Write-Host -ForegroundColor Green "✅ Cache cleaned up."
}
}
return $true
}
"git" {
if (-not (Test-Path $env:ASE_GITPATH) -or -not (Test-Path "$env:ASE_GITPATH\bin\git.exe")) {
throw "Git not found."
}
Write-Host -ForegroundColor Green "✅ Git was found at $env:ASE_GITPATH"
return $true
}
"python" {
$versions = @(
"3.6.1"
"3.11.6"
"2.7.18"
)
$result = $true
$versions | ForEach-Object {
$pythonInfo = Check-Python -version $_
if ($pythonInfo) {
Write-Host -ForegroundColor Green "✅ Python $_ was found at $($pythonInfo.Path)"
$result = $result -and $true
} else {
Write-Host -ForegroundColor Red "❌ Python $_ was not found."
$result = $result -and $false
}
}
if ($result -eq $false) {
throw "Python version requirements not met."
}
}
"depot_tools" {
if (-not (Test-Path $env:ASE_DEPOTTOOLSPATH)) {
throw "Google Depot Tools not found."
}
Write-Host -ForegroundColor Green "✅ Google Depot Tools was found at $env:ASE_DEPOTTOOLSPATH"
return $true
}
"skia" {
if (-not (Test-Path $env:ASE_SKIAPATH)) {
throw "Skia source code not found"
}
Write-Host -ForegroundColor Green "✅ Skia was found at $env:ASE_SKIAPATH"
return $true
}
"asesprite" {
if (-not (Test-Path $env:ASE_ASESPRITEPATH)) {
throw "Aseprite source code not found."
}
Write-Host -ForegroundColor Green "✅ Aseprite was found at $env:ASE_ASESPRITEPATH"
return $true
}
}
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
if ($PostOperation) {
Write-Host -ForegroundColor Red "❌ Post-operation dependency check failed."
return $false
}
$result = $(Install-Dependency -DependencyName $DependencyName)
if ($result -eq $false) {
Write-Host -ForegroundColor Red "❌ Dependency installation failed."
return $false
}
$result = Check-Dependency -DependencyName $DependencyName -PostOperation
return $result
}
}
<#
.SYNOPSIS
This function installs a dependency on the host system.
.DESCRIPTION
This function installs a dependency on the host system. It will attempt to install the dependency, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.PARAMETER DependencyName
The dependency to install. Valid values are: 7zip, windowssdk, visualstudio, llvm, ninja, cmake, cache, skia, asesprite, git, depot_tools, python
.EXAMPLE
Install-Dependency -DependencyName "7zip"
#>
function Install-Dependency() {
param(
[ValidateSet("7zip", "windowssdk", "visualstudio", "llvm", "ninja", "cmake", "cache", "skia", "asesprite", "git", "depot_tools", "python", IgnoreCase = $true)]
[string]$DependencyName
)
try {
switch ($DependencyName) {
"7zip" {
Write-Host -ForegroundColor Yellow "⏳ Installing 7-Zip..."
$packageName = "7zip"
$result = $(Install-Packages -Packages @($packageName) -PackageType "choco")
if ($result -eq $false) {
throw "7-Zip installation failed."
}
return $true
}
"windowssdk" {
Write-Host -ForegroundColor Yellow "⏳ Installing Windows SDK..."
$packageName = Get-WindowsSdkPackage -sdkVersion $WindowsSdkVersion -WinDbg
if ($null -eq $packageName) {
throw "Windows SDK package not found."
}
$result = $(Install-Packages -Packages @($packageName) -PackageType "choco")
if ($result -eq $false) {
throw "Windows SDK installation failed."
}
return $true
}
"visualstudio" {
$result = Install-VSComponents -Components @(
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.ATL",
"Microsoft.VisualStudio.Component.VC.CLI.Support",
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Win81",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Win10",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.MSBuild",
"Microsoft.VisualStudio.Component.VC.ATLMFC"
)
if ($result -eq $false) {
throw "Visual Studio installation failed."
}
}
"cmake" {
if (-not $(Test-Path "$env:ASE_CACHEPATH\cmake.zip")) {
Write-Host -ForegroundColor Yellow "⏳ Downloading CMake..."
try {
Start-BitsTransfer -Source "https://github.com/Kitware/CMake/releases/download/v$env:ASE_CMAKEVERSION/cmake-$env:ASE_CMAKEVERSION-windows-x86_64.zip" -Destination "$env:ASE_CACHEPATH\cmake.zip"
} catch {
throw "Failed to download CMake."
}
} else {
Write-Host -ForegroundColor Green "✅ CMake found in cache."
}
if (-not $(Test-Path "$env:ASE_CMAKEPATH")) {
Write-Host -ForegroundColor Yellow "⏳ Unzipping CMake..."
try {
$arguments = @(
"x",
"$env:ASE_CACHEPATH\cmake.zip",
"-o$env:ASE_CMAKEPATH"
) -join " "
Invoke-Expression "& `"$env:ASE_ZIPPATH\7z.exe`" $arguments"
if ($LASTEXITCODE -ne 0) {
throw "Failed to run 7-Zip to unzip CMake."
}
Move-Item -Path "$env:ASE_CMAKEPATH\cmake-$env:ASE_CMAKEVERSION-windows-x86_64\*" -Destination "$env:ASE_CMAKEPATH" -Force
Remove-Item -Path "$env:ASE_CMAKEPATH\cmake-$env:ASE_CMAKEVERSION-windows-x86_64" -Recurse -Force
if (-not $(Test-Path "$env:ASE_CMAKEPATH\bin\cmake.exe")) {
throw "Failed to unzip CMake."
}
} catch {
throw "Failed to unzip CMake."
}
} else {
Write-Host -ForegroundColor Green "✅ CMake found at $env:ASE_CMAKEPATH"
}
return $true
}
"llvm" {
# Clone llvm
if (-not (Test-Path $env:ASE_LLVMPATH)) {
try {
$result = Invoke-GitClone -RepoUrl "https://github.com/llvm/llvm-project.git" -Tag "llvmorg-$env:ASE_LLVMVERSION" -OutputPath $env:ASE_LLVMPATH
if ($result -eq $false) {
throw "Failed to clone LLVM."
}
Write-Host -ForegroundColor Green "✅ LLVM cloned successfully."
return $true
} catch {
throw "$_.Exception.Message"
}
} else {
Write-Host -ForegroundColor Green "✅ LLVM source code found at $env:ASE_LLVMPATH"
return $true
}
}
"ninja" {
if (-not $(Test-Path $env:ASE_NINJAPATH) ) {
try {
$result = Invoke-GitClone -RepoUrl "https://github.com/ninja-build/ninja.git" -OutputPath $env:ASE_NINJAPATH -Branch release
if ($result -eq $false) {
throw "Failed to clone Ninja."
}
} catch {
throw "$_.Exception.Message"
}
} else {
Write-Host -ForegroundColor Green "✅ Ninja source code found at $env:ASE_NINJAPATH"
return $true
}
}
"cache" {
Write-Host -ForegroundColor Yellow "⏳ Creating cache folder..."
New-Item -ItemType Directory -Path $env:ASE_CACHEPATH
return $true
}
"git" {
if (-not $(Test-Path "$env:ASE_CACHEPATH\git.exe")) {
Write-Host -ForegroundColor Yellow "⏳ Downloading Git..."
try {
Start-BitsTransfer -Source "https://github.com/git-for-windows/git/releases/download/v$env:ASE_GITVERSION.windows.$env:ASE_GITSUBVERSION/PortableGit-$env:ASE_GITVERSION.$env:ASE_GITSUBVERSION-64-bit.7z.exe" -Destination "$env:ASE_CACHEPATH\git.exe"
if ($LASTEXITCODE -ne 0) {
throw "Failed to download Git."
}
} catch {
throw "$($_.Exception.Message)"
}
} else {
Write-Host -ForegroundColor Green "✅ Git zip found in cache."
}
if (-not $(Test-Path "$env:ASE_GITPATH") -or -not $(Test-Path "$env:ASE_GITPATH\bin\git.exe")) {
Write-Host -ForegroundColor Yellow "⏳ Unzipping Git..."
try {
$arguments = @(
"x",
"$env:ASE_CACHEPATH\git.exe",
"-o$env:ASE_GITPATH"
) -join " "
Invoke-Expression "& `"$env:ASE_ZIPPATH\7z.exe`" $arguments"
if ($LASTEXITCODE -ne 0) {
throw "Failed to run 7-Zip to unzip Git."
}
if (-not $(Test-Path "$env:ASE_GITPATH\bin\git.exe")) {
throw "Failed to unzip Git."
}
} catch {
throw "Failed to unzip Git."
}
} else {
Write-Host -ForegroundColor Green "✅ Git found at $env:ASE_GITPATH"
}
}
"python" {
Write-Host -ForegroundColor Yellow "⏳ Installing Python versions..."
$versions = @(
"3.6.1"
"3.11.6"
"2.7.18"
)
$versions | ForEach-Object {
$result = Check-Python -version $_
if ($result) {
Write-Host -ForegroundColor Green "✅ Python $_ was found at $($result.Path)"
} else {
Write-Host -ForegroundColor Yellow "⏳ Installing Python $_..."
$result = Install-Python -Version $_
if ($result -eq $false) {
throw "Python $_ installation failed."
}
}
}
return $true
}
"depot_tools" {
if (-not (Test-Path $env:ASE_DEPOTTOOLSPATH)) {
Write-Host -ForegroundColor Yellow "⌛ Google Depot Tools folder was not found."
try {
New-Item -ItemType Directory -Path $env:ASE_DEPOTTOOLSPATH -Force -ErrorAction SilentlyContinue
Write-Host -ForegroundColor Green "✅ Google Depot Tools folder created: $env:ASE_DEPOTTOOLSPATH"
} catch {
throw "Failed to create Google Depot Tools folder."
}
} else {
Write-Host -ForegroundColor Green "✅ Google Depot Tools folder found: $env:ASE_DEPOTTOOLSPATH"
}
if (-not (Test-Path "$env:ASE_DEPOTTOOLSPATH\gclient.bat")) {
Write-Host -ForegroundColor Yellow "⌛ Clone Google Depot Tools from GitHub..."
try {
$result = Invoke-GitClone -RepoUrl 'https://chromium.googlesource.com/chromium/tools/depot_tools.git' -OutputPath $env:ASE_DEPOTTOOLSPATH
if ($result -eq $false) {
throw "Failed to clone Google Depot Tools."
}
Write-Host -ForegroundColor Green "✅ Google Depot Tools cloned to: $env:ASE_DEPOTTOOLSPATH"
} catch {
throw "Failed to clone Google Depot Tools."
}
} else {
Write-Host -ForegroundColor Green "✅ Google Depot Tools path found in cache."
}
return $true
}
"skia" {
if ($env:SKIAUSEPREBUILT -eq $true) {
# Using the prebuilt Skia library
if (-not (Test-Path "$env:ASE_CACHEPATH\skia.zip")) {
Write-Host -ForegroundColor Yellow "⏳ Downloading Skia..."
try {
Start-BitsTransfer -Source $env:ASE_SKIAURL -Destination "$env:ASE_CACHEPATH\skia.zip"
} catch {
throw "Failed to download Skia."
}
} else {
Write-Host "✅ Skia download found in cache."
}
if (-not (Test-Path $env:ASE_SKIAPATH)) {
Write-Host -ForegroundColor Yellow "⌛ Skia folder was not found, unzipping archive..."
try {
New-Item -ItemType Directory -Path $env:ASE_SKIAPATH -Force -ErrorAction SilentlyContinue
& "$env:ASE_ZIPPATH\7z.exe" x "$env:ASE_CACHEPATH\skia.zip" -o"$env:ASE_SKIAPATH"
Write-Host -ForegroundColor Green "✅ Skia unzipped to: $env:ASE_SKIAPATH"
} catch {
throw "Failed to unzip Skia."
}
} else {
Write-Host -ForegroundColor Green "✅ Skia path found in cache."
}
} else {
if (-not (Test-Path $env:ASE_SKIAPATH)) {
Write-Host -ForegroundColor Yellow "⌛ Skia folder was not found, creating folder..."
try {
New-Item -ItemType Directory -Path $env:ASE_SKIAPATH -Force -ErrorAction SilentlyContinue
Write-Host -ForegroundColor Green "✅ Skia folder created: $env:ASE_SKIAPATH"
} catch {
throw "Failed to create Skia folder."
}
} else {
Write-Host -ForegroundColor Green "✅ Skia folder found: $env:ASE_SKIAPATH"
}
if (-not (Test-Path "$env:ASE_SKIAPATH\.git")) {
try {
Write-Host -ForegroundColor Yellow "⏳ Cloning Skia from GitHub..."
# Tested, doesn't work (vanilla skia)
# $result = Invoke-GitClone -RepoUrl 'https://skia.googlesource.com/skia.git' -OutputPath $env:ASE_SKIAPATH -Branch "chrome/m102"
# Tested, doesn't work (has bugs)
# $result = Invoke-GitClone -RepoUrl 'https://github.com/aseprite/skia.git' -OutputPath $env:ASE_SKIAPATH -Branch "$env:ASE_SKIAVERSION"
# Tested, works (fixes bugs, PR pending to skia aesprite-m102)
$result = Invoke-GitClone -RepoUrl 'https://github.com/loopyd/skia.git' -OutputPath $env:ASE_SKIAPATH -Branch "$env:ASE_SKIAVERSION"
if ($result -eq $false) {
throw "Failed to clone Skia."
}
Write-Host -ForegroundColor Green "✅ Skia cloned successfully."
} catch {
throw "$_.Exception.Message"
}
} else {
Write-Host -ForegroundColor Green "✅ Skia found."
}
}
}
"asesprite" {
if (-not (Test-Path $env:ASE_ASESPRITEPATH)) {
try {
New-Item -ItemType Directory -Path $env:ASE_ASESPRITEPATH -Force -ErrorAction SilentlyContinue
Write-Host -ForegroundColor Green "✅ Aseprite path created: $env:ASE_ASESPRITEPATH"
} catch {
throw "Failed to create Aesprite folder"
}
} else {
Write-Host -ForegroundColor Green "✅ Aseprite path found at: $env:ASE_ASESPRITEPATH"
}
if (-not (Test-Path "$env:ASE_ASESPRITEPATH\.git")) {
try {
$result = Invoke-GitClone -RepoUrl 'https://github.com/aseprite/aseprite.git' -Tag $env:ASE_ASEVERSION -OutputPath $env:ASE_ASESPRITEPATH
if ($result -eq $false) {
throw "Failed to clone Aseprite."
}
Write-Host -ForegroundColor Green "✅ Aseprite cloned successfully."
} catch {
throw "$_.Exception.Message"
}
} else {
Write-Host -ForegroundColor Green "✅ Aseprite download found in cache."
}
}
}
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
##############################################
# BUILDS
##############################################
<#
.SYNOPSIS
This function builds Ninja using the Blep API. It requires the Visual Studio development environment to be entered first.
.DESCRIPTION
This function builds Ninja using the Blep API. It will attempt to build Ninja, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.EXAMPLE
Build-Ninja
.NOTES
None
#>
function Build-Ninja() {
try {
if ($(Test-Path "$env:ASE_NINJAPATH") -and $(Test-Path "$env:ASE_NINJAPATH\build\Release\ninja.exe")) {
Write-Host -ForegroundColor Green "✅ Ninja already built! Skipping."
return $true
}
$result = Toggle-DevelopmentEnvironment enter -Compiler msvc
if ($result -eq $false) {
throw "Cannot enter development environment, so cannot build Ninja."
}
$result = Set-Python -Version "3.6.1"
if ($result -eq $false) {
throw "Cannot set Python version."
}
$currentDirectory = Get-Location
if ((Test-Path "$env:ASE_NINJAPATH\build")) {
Remove-Item -Path "$env:ASE_NINJAPATH\build" -Recurse -Force | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete existing Ninja build directory."
}
Write-Host -ForegroundColor Green "✅ Existing Ninja build directory removed."
}
New-Item -ItemType Directory -Path "$env:ASE_NINJAPATH\build" -Force | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to create new Ninja build directory."
}
Write-Host -ForegroundColor Yellow "⏳ Configuring Ninja build..."
Set-Location "$env:ASE_NINJAPATH"
$arguments = @(
"-B`"$env:ASE_NINJAPATH\build`""
"-DCMAKE_INSTALL_PREFIX=`"$env:ASE_CMAKEPATH`""
"-DCMAKE_MSVC_RUNTIME_LIBRARY=`"MultiThreaded`""
"-DCMAKE_SYSTEM_NAME=Windows"
"."
)
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\cmake.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Failed to configure Ninja build."
}
Write-Host -ForegroundColor Green "✅ Ninja build configured successfully."
Write-Host -ForegroundColor Yellow "⏳ Building Ninja..."
$arguments = @(
"--build `"$env:ASE_NINJAPATH\build`""
"--config Release"
"--target install"
)
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\cmake.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Failed to build Ninja."
}
Set-Location -Path $currentDirectory
Write-Host -ForegroundColor Green "✅ Ninja built successfully."
return $true
} catch {
Set-Location -Path $currentDirectory
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
<#
.SYNOPSIS
This function builds LLVM using the Blep API. It requires the Visual Studio development environment to be entered first.
.DESCRIPTION
This function builds LLVM using the Blep API. It will attempt to build LLVM, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.EXAMPLE
Build-LLVM
.NOTES
None
#>
function Build-LLVM() {
try {
if ((Test-Path "$env:ASE_LLVMPATH\build") -and $(Test-Path "$env:ASE_LLVMROOT") -eq $true) {
Write-Host -ForegroundColor Green "✅ LLVM-blAse already built! Skipping."
return $true
}
$result = Toggle-DevelopmentEnvironment enter -Compiler msvc
if ($result -eq $false) {
throw "Cannot enter development environment, so cannot build LLVM-blAse."
}
$result = Set-Python -Version "3.6.1"
if ($result -eq $false) {
throw "Cannot set Python version."
}
$currentDirectory = Get-Location
if ((Test-Path "$env:ASE_LLVMPATH\build")) {
Remove-Item -Path "$env:ASE_LLVMPATH\build" -Recurse -Force | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete existing LLVM build directory."
}
Write-Host -ForegroundColor Green "✅ Existing LLVM build directory removed."
}
New-Item -ItemType Directory -Path "$env:ASE_LLVMPATH\build" -Force | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to create new LLVM build directory."
}
Write-Host -ForegroundColor Yellow "⏳ Configuring LLVM-blAse build..."
Set-Location "$env:ASE_LLVMPATH\build"
$arguments = @(
"-G `"Ninja`""
"-S `"$env:ASE_LLVMPATH\llvm`""
"-B `"$env:ASE_LLVMPATH\build`""
"-DCLANG_BUILD_EXAMPLES=Off"
"-DCLANG_ENABLE_ARCMT=Off"
"-DCLANG_ENABLE_STATIC_ANALYZER=On"
"-DCLANG_INCLUDE_DOCS=Off"
"-DCLANG_INCLUDE_TESTS=Off"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_INSTALL_PREFIX=`"$env:ASE_LLVMROOT`""
"-DCMAKE_MSVC_RUNTIME_LIBRARY=`"MultiThreaded`""
"-DCMAKE_SYSTEM_NAME=Windows"
"-DCOMPILER_RT_BUILD_BUILTINS=Off"
"-DCOMPILER_RT_BUILD_GWP_ASAN=Off"
"-DCOMPILER_RT_BUILD_LIBFUZZER=On"
"-DCOMPILER_RT_BUILD_MEMPROF=Off"
"-DCOMPILER_RT_BUILD_ORC=Off"
"-DCOMPILER_RT_BUILD_PROFILE=On"
"-DCOMPILER_RT_BUILD_SANITIZERS=On"
"-DCOMPILER_RT_BUILD_XRAY_NO_PREINIT=Off"
"-DCOMPILER_RT_BUILD_XRAY=Off"
"-DCOMPILER_RT_ENABLE_CET=OFF"
"-DLLVM_BUILD_BENCHMARKS=Off"
"-DLLVM_BUILD_DOCS=Off"
"-DLLVM_BUILD_EXAMPLES=Off"
"-DLLVM_BUILD_LLVM_C_DYLIB=Off"
"-DLLVM_BUILD_SHARED_LIBS=Off"
"-DLLVM_BUILD_TESTS=Off"
"-DLLVM_ENABLE_ASSERTIONS=Off"
"-DLLVM_ENABLE_BACKTRACES=Off"
"-DLLVM_ENABLE_BINDINGS=Off"
"-DLLVM_ENABLE_CRASH_DUMPS=Off"
"-DLLVM_ENABLE_CRASH_OVERRIDES=Off"
"-DLLVM_ENABLE_CURL=Off"
"-DLLVM_ENABLE_DIA_SDK=1"
"-DLLVM_ENABLE_HTTPLIB=Off"
"-DLLVM_ENABLE_IDE=Off"
"-DLLVM_ENABLE_LIBEDIT=Off"
"-DLLVM_ENABLE_LIBEDIT=Off"
"-DLLVM_ENABLE_LIBPFM=Off"
"-DLLVM_ENABLE_LIBXML2=Off"
"-DLLVM_ENABLE_OCAMLDOC=Off"
"-DLLVM_ENABLE_PEDANTIC=Off"
"-DLLVM_ENABLE_PLUGINS=Off"
"-DLLVM_ENABLE_PROJECTS=`"clang;lld;lldb;compiler-rt;clang-tools-extra;polly`""
"-DLLVM_ENABLE_RTTI=0"
"-DLLVM_ENABLE_TERMINFO=Off"
"-DLLVM_ENABLE_UNWIND_TABLES=Off"
"-DLLVM_ENABLE_WARNINGS=Off"
"-DLLVM_ENABLE_WERROR=Off"
"-DLLVM_ENABLE_Z3_SOLVER=Off"
"-DLLVM_ENABLE_ZLIB=Off"
"-DLLVM_ENABLE_ZSTD=Off"
"-DLLVM_INCLUDE_BENCHMARKS=Off"
"-DLLVM_INCLUDE_DOCS=Off"
"-DLLVM_INCLUDE_EXAMPLES=Off"
"-DLLVM_INCLUDE_TESTS=Off"
"-DLLVM_TARGETS_TO_BUILD=`"AArch64;ARM;WebAssembly;X86`""
)
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\cmake.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Failed to configure LLVM-blAse build. Process returned exit code $lec."
}
Write-Host -ForegroundColor Green "✅ LLVM-blAse build configured successfully"
Write-Host -ForegroundColor Yellow "⏳ Compiling LLVM-blAse..."
$arguments = @(
"-j $env:ASE_CPUCOUNT"
"--verbose"
)
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\ninja.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Failed to compile LLVM-blAse: Process exited with code $lec."
}
Write-Host -ForegroundColor Green "✅ LLVM-blAse compiled successfully"
Write-Host -ForegroundColor Yellow "⏳ Installing LLVM-blAse..."
$arguments = @(
"install"
)
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\ninja.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Failed to install LLVM-blAse: Process exited with code $lec."
}
Write-Host -ForegroundColor Green "✅ LLVM-blAse installed successfully"
Set-Location $currentDirectory
$result = Toggle-DevelopmentEnvironment leave -Compiler vsclang
if ($result -eq $false) {
throw "Cannot leave development environment."
}
Write-Host -ForegroundColor Green "🎉 LLVM-blAse built successfully."
return $true
} catch {
Set-Location $currentDirectory
$result = Toggle-DevelopmentEnvironment leave -Compiler msvc
if ($result -eq $false) {
Write-Host -ForegroundColor Red "Cannot leave development environment, script in unexpected state."
return $false
}
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
<#
.SYNOPSIS
This function builds Google Depot Tools.
.DESCRIPTION
This function builds Google Depot Tools. It will attempt to build Google Depot Tools, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.EXAMPLE
Build-GoogleDepot
.NOTES
None
#>
function Build-GoogleDepot() {
try {
$result = Toggle-DevelopmentEnvironment -Action enter -Compiler msvc
if ($result -eq $false) {
throw "Cannot enter development environment"
}
$currentDirectory = Get-Location
Set-Location $env:ASE_DEPOTTOOLSPATH
Mod-Path -Add @(
"$env:ASE_DEPOTTOOLSPATH"
"$env:ASE_CMAKEPATH\bin"
)
Write-Host -ForegroundColor Yellow "⏳ Updating Google Depot Tools..."
try {
$arguments = @(
"sync"
)
$lec = $(Roar -Command "gclient" -Arguments $arguments)
if ($lec -ne 0 -and $lec -ne 1) {
throw "Failed to update Google Depot Tools. Process returned exit code $lec."
}
} catch {
throw "$($_.Exception.Message)"
}
Mod-Path -Remove @(
"$env:ASE_DEPOTTOOLSPATH"
"$env:ASE_CMAKEPATH\bin"
)
Set-Location $currentDirectory
$result = Toggle-DevelopmentEnvironment -Action leave -Compiler msvc
if ($result -eq $false) {
throw "Cannot leave development environment"
}
Write-Host -ForegroundColor Green "✅ Google Depot Tools updated successfully."
return $true
} catch {
Mod-Path -Remove @(
"$env:ASE_DEPOTTOOLSPATH"
"$env:ASE_CMAKEPATH\bin"
)
Set-Location $currentDirectory
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
return $false
}
}
<#
.SYNOPSIS
This function builds Skia using the Blep API. The skia graphics library used by Aseprite.
.DESCRIPTION
This function builds Skia using the Blep API. It will attempt to build Skia, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.EXAMPLE
Build-Skia
.NOTES
None
#>
function Build-Skia() {
param(
[ValidateSet("Release", "Debug", IgnoreCase = $true)]
[string]$BuildType = "Release"
)
try {
if ((Test-Path "$env:ASE_SKIAPATH\out") -and $(Test-Path "$env:ASE_SKIAPATH\out\$BuildType-x64") -eq $true) {
Write-Host -ForegroundColor Green "✅ Skia already built! Skipping."
return $true
}
# Enter Visual Studio development environment
$result = Toggle-DevelopmentEnvironment -Action enter -Compiler blaseclang
if ($result -eq $false) {
throw "Failed to enter development environment."
}
Mod-Path -Add @(
"$env:ASE_DEPOTTOOLSPATH"
"$env:ASE_CMAKEPATH\bin"
"$env:ASE_LLVMROOT\bin"
)
$env:GIT_EXECUTABLE = "$env:ASE_DEPOTTOOLSPATH\git.bat"
$env:EMSDK_POWERSHELL = "1"
# Update Skia dependencies
$currentLocation = Get-Location
Set-Location $env:ASE_SKIAPATH
Write-Host -ForegroundColor Yellow "⏳ Updating Skia..."
try {
$arguments = @(
"tools/git-sync-deps"
)
$lec = $(Roar -Command "$env:ASE_DEPOTTOOLSPATH\python3.bat" -Arguments $arguments)
if ($lastExitCode -ne 0) {
throw "Failed to update Skia. Process returned exit code $lastExitCode."
}
} catch {
throw "$($_.Exception.Message)"
}
Write-Host -ForegroundColor Green "✅ Skia updated successfully."
# Clean build directory
try {
if (-not (Test-Path "$env:ASE_SKIAPATH\out")) {
New-Item -ItemType Directory -Path "$env:ASE_SKIAPATH\out" -Force | Out-Null
} else {
Write-Host -ForegroundColor Yellow "⏳ Removing existing build directory..."
Remove-Item -Path "$env:ASE_SKIAPATH\out" -Recurse -Force | Out-Null
Write-Host -ForegroundColor Green "✅ Existing build directory removed."
New-Item -ItemType Directory -Path "$env:ASE_SKIAPATH\out" -Force | Out-Null
switch ($BuildType) {
"Release" {
New-Item -ItemType Directory -Path "$env:ASE_SKIAPATH\out\Release-x64" -Force | Out-Null
break
}
"Debug" {
New-Item -ItemType Directory -Path "$env:ASE_SKIAPATH\out\Debug-x64" -Force | Out-Null
break
}
default {
throw "Invalid build type: $BuildType"
}
}
}
} catch {
throw "Failed to clean build directory: $($_.Exception.Message)"
}
# Configure skia build
try {
switch ($BuildType) {
"Release" {
Write-Host -ForegroundColor Yellow "⏳ Configuring Release build using $env:ASE_DEPOTTOOLSPATH\gn.py..."
$arguments = @(
"is_debug=false",
"is_official_build=true",
"skia_use_system_expat=false",
"skia_use_system_icu=false",
"skia_use_system_libjpeg_turbo=false",
"skia_use_system_libpng=false",
"skia_use_system_libwebp=false",
"skia_use_system_zlib=false",
"skia_use_sfntly=false",
"skia_use_freetype=true",
"skia_use_harfbuzz=true",
"skia_pdf_subset_harfbuzz=true",
"skia_use_system_freetype2=false",
"skia_use_system_harfbuzz=false",
"target_cpu=""""x64""""",
"cc=""""$env:ASE_LLVMROOT\bin\clang.exe""""",
"cxx=""""$env:ASE_LLVMROOT\bin\clang++.exe""""",
"win_vc=""""$env:ASE_VISUALSTUDIOPATH\VC"""""
) -join " "
$packedArguments = @(
"`"$env:ASE_DEPOTTOOLSPATH\gn.py`""
"gen",
"out/Release-x64",
"--args=`'$arguments`'"
)
$lec = $(Roar -Command "$env:ASE_DEPOTTOOLSPATH\python3.bat" -Arguments $packedArguments)
if ($lec -ne 0) {
throw "Configure failed."
}
Write-Host -ForegroundColor Green "✅ Build configured."
break
}
"Debug" {
Write-Host -ForegroundColor Yellow "⏳ Configuring Debug build using $env:ASE_DEPOTTOOLSPATH\gn.py..."
$arguments = @(
"is_debug=true",
"is_official_build=false",
"skia_use_system_expat=false",
"skia_use_system_icu=false",
"skia_use_system_libjpeg_turbo=false",
"skia_use_system_libpng=false",
"skia_use_system_libwebp=false",
"skia_use_system_zlib=false",
"skia_use_sfntly=false",
"skia_use_freetype=true",
"skia_use_harfbuzz=true",
"skia_pdf_subset_harfbuzz=true",
"skia_use_system_freetype2=false",
"skia_use_system_harfbuzz=false",
"target_cpu=""""x64""""",
"cc=""""$env:ASE_LLVMROOT\bin\clang.exe""""",
"cxx=""""$env:ASE_LLVMROOT\bin\clang++.exe""""",
"win_vc=""""$env:ASE_VISUALSTUDIOPATH\VC"""""
) -join " "
$packedArguments = @(
"""$env:ASE_DEPOTTOOLSPATH\gn.py"""
"gen",
"out/Debug-x64",
"--args=`'$arguments`'"
)
$lec = $(Roar -Command "$env:ASE_DEPOTTOOLSPATH\python3.bat" -Arguments $packedArguments)
if ($lec -ne 0) {
throw "Configure failed."
}
Write-Host -ForegroundColor Green "✅ Build configured."
break
}
default {
throw "Invalid build type: $BuildType"
}
}
} catch {
throw "Configure failed: $($_.Exception.Message)"
}
# Compile
try {
switch ($BuildType) {
"Release" {
Write-Host -ForegroundColor Yellow "⏳ Compiling Release build using $env:ASE_NINJAPATH..."
$arguments = @(
"-j",
"$env:ASE_CPUCOUNT",
"-C",
"out/Release-x64",
"skia",
"modules"
)
$lastExitCode = $(Roar -Command "$env:ASE_CMAKEPATH\bin\ninja.exe" -Arguments $arguments)
if ($lastExitCode -ne 0) {
throw "Failed to build skia Release."
}
Write-Host -ForegroundColor Green "✅ Build Release succeeded."
break
}
"Debug" {
Write-Host -ForegroundColor Yellow "⏳ Compiling Debug build using $env:ASE_NINJAPATH..."
$arguments = @(
"-j",
"$env:ASE_CPUCOUNT",
"-C",
"out/Debug-x64",
"skia",
"modules"
)
$lastExitCode = $(Roar -Command "$env:ASE_CMAKEPATH\bin\ninja.exe" -Arguments $arguments)
if ($lastExitCode -ne 0) {
throw "Failed to build skia Debug."
}
Write-Host -ForegroundColor Green "✅ Build Debug succeeded."
break
}
default {
throw "Invalid build type: $BuildType"
}
}
} catch {
throw "Build failed: $($_.Exception.Message)"
}
Write-Host -ForegroundColor Magenta "🎉 Skia build succeeded."
Set-Location $currentLocation
Mod-Path -Remove @(
"$env:ASE_DEPOTTOOLSPATH"
"$env:ASE_CMAKEPATH\bin"
"$env:ASE_LLVMROOT\bin"
)
$env:GIT_EXECUTABLE = $null
$result = Toggle-DevelopmentEnvironment -Action leave -Compiler blaseclang
if ($result -eq $false) {
throw "Failed to leave development environment"
}
return $true
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
Set-Location $currentLocation
Mod-Path -Remove @(
"$env:ASE_DEPOTTOOLSPATH"
"$env:ASE_CMAKEPATH\bin"
"$env:ASE_LLVMROOT\bin"
)
$env:GIT_EXECUTABLE = $null
if ($result -eq $true)
{
$result = Toggle-DevelopmentEnvironment -Action leave -Compiler blaseclang
if ($result -eq $false) {
Write-Host -ForegroundColor Red "❌ Failed to leave development environment, the script is in an unexpected state."
}
}
return $false
}
}
<#
.SYNOPSIS
This function builds Aseprite using the Blep API. It requires the Skia library to be built first.
.DESCRIPTION
This function builds Aseprite using the Blep API. It will attempt to build Aseprite, and then re-evaluate wth the -PostOperation switch enabled. If it then fails, it will return false.
.EXAMPLE
Build-Aseprite
.NOTES
None
#>
function Build-Aseprite() {
try {
if ((Test-Path "$env:ASE_ASESPRITEPATH\build") -and $(Test-Path "$env:ASE_ASESPRITEPATH\build\bin\aseprite.exe") -eq $true) {
Write-Host -ForegroundColor Green "✅ Aseprite already built! Skipping."
return $true
}
# Enter Visual Studio development environment
$result = Toggle-DevelopmentEnvironment -Action enter -Compiler msvc
if ($result -eq $false) {
throw "Failed to enter development environment."
}
# Enter build directory
$currentLocation = Get-Location
try {
$currentLocation = Get-Location
Set-Location -Path "$env:ASE_ASESPRITEPATH"
if (-not (Test-Path "build")) {
New-Item -ItemType Directory -Path "build" -Force | Out-Null
} else {
Remove-Item -Path "build" -Recurse -Force
New-Item -ItemType Directory -Path "build" -Force | Out-Null
Write-Host -ForegroundColor Green "✅ New Asesprite-blAse build directory created."
}
Set-Location -Path "$env:ASE_ASESPRITEPATH\build"
} catch {
throw "Failed to enter build directory: $($_.Exception.Message)"
}
# Configure build
try {
Write-Host -ForegroundColor Yellow "⏳ Configuring Asesprite-blAse..."
$arguments = @(
"-G Ninja"
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
"-DLAF_BACKEND=skia"
"-DENABLE_NEWS=OFF"
"-DENABLE_PSD=ON"
"-DENABLE_TAR=ON"
"-DENABLE_CAT=ON"
"-DENABLE_GIF=ON"
"-DENABLE_WEBP=ON"
"-DENABLE_APNG=ON"
"-DENABLE_UPDATER=OFF"
"-DGIFLIB_UTILS=ON"
"-DSKIA_DIR=""""$env:ASE_SKIAPATH"""""
"-DSKIA_LIBRARY_DIR=""""$env:ASE_SKIAPATH\out\Release-x64"""""
"-DSKIA_LIBRARY=""""$env:ASE_SKIAPATH\out\Release-x64\skia.lib"""""
"-DSKIA_OUT_DIR=""""$env:ASE_SKIAPATH\out\Release-x64"""""
".."
) -join " "
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\cmake.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Configure failed."
}
Write-Host -ForegroundColor Green "✅ Asesprite-blAse configured."
} catch {
Write-Host -ForegroundColor Red "❌ Configure failed: $($_.Exception.Message)"
throw "Configure failed: $($_.Exception.Message)"
}
# Compile
try {
Write-Host -ForegroundColor Yellow "⏳ Compiling Asesprite-blAse..."
$arguments = @(
"-j $env:ASE_CPUCOUNT"
"aseprite"
) -join " "
$lec = $(Roar -Command "$env:ASE_CMAKEPATH\bin\ninja.exe" -Arguments $arguments)
if ($lec -ne 0) {
throw "Failed to compile Asesprite-blAse."
}
Write-Host -ForegroundColor Green "✅ Asesprite-blAse compiled succeesfully."
} catch {
throw "Build failed: $($_.Exception.Message)"
}
Write-Host -ForegroundColor Magenta "🎉 Asesprite-blAse build succeeded."
Set-Location $currentLocation
$result = Toggle-DevelopmentEnvironment -Action leave -Compiler msvc
if ($result -eq $false) {
throw "Failed to leave development environment"
}
return $true
} catch {
Write-Host -ForegroundColor Red "❌ $($_.Exception.Message)"
Set-Location $currentLocation
if ($result -eq $true)
{
$result = Toggle-DevelopmentEnvironment -Action leave -Compiler msvc
if ($result -eq $false) {
Write-Host -ForegroundColor Red "❌ Failed to leave development environment, the script is in an unexpected state."
}
}
return $false
}
}
##############################################
# ENVIRONMENT VARIABLES
##############################################
# Version numbers
$env:ASE_WINSDKVERSION = $WindowsSdkVersion
$env:ASE_SKIAVERSION = $SkiaVersion
$env:ASE_ASEVERSION = $AseVersion
$env:ASE_VISUALSTUDIOVERSION = $VisualStudioVersion
$env:ASE_LLVMVERSION = $LlvmVersion
$env:ASE_CMAKEVERSION = $CmakeVersion
$env:ASE_NINJAVERSION = $NinjaVersion
$env:ASE_GITVERSION = $GitVersion
$env:ASE_GITSUBVERSION = $GitSubVersion
# Paths
$env:ASE_CACHEPATH = $CachePath
$env:ASE_ASESPRITEPATH = "$CachePath\aesprite"
$env:ASE_SKIAPATH = "$CachePath\skia"
$env:ASE_DEPOTTOOLSPATH = "$CachePath\depot_tools"
$env:ASE_LLVMPATH = "$CachePath\llvm"
$env:ASE_LLVMROOT = "$CachePath\llvm\build\Release"
$env:ASE_NINJAPATH = "$CachePath\ninja"
$env:ASE_CMAKEPATH = "$CachePath\cmake"
$env:ASE_GITPATH = "$CachePath\git"
# Flags
$env:ASE_SKIAUSEPREBUILT = $SkiaUsePrebuilt
$env:ASE_CACHECLEAN = $Clean
# URLs (probably should move these....)
$env:ASE_SKIAURL = "https://github.com/aseprite/skia/releases/download/$env:ASE_SKIAVERSION/Skia-Windows-Release-x64.zip"
$env:ASE_AESURL = "https://github.com/aseprite/aseprite/releases/download/v$env:ASE_ASEVERSION/Aseprite-v$env:ASE_ASEVERSION-Source.zip"
# COMPILE TIME CPU COUNT
#
# The percentage below is used to calculate the number of logical cores used during compile
# time operations. This is to prevent the system from running out of resources during the build
# process, while still allowing the larger builds to complete in a reasonable amount of time.
#
# If you find yourself experiencing BSODs related to page faults during compile time operations,
# please reduce the percentage in the calculation below. Every host is different.
#
# WHY IS THIS HARDCODED?
#
# Because this is a setting that can potentially crash a lot of computers, its not been made
# a command-line argument like many others. If you want to change this value, you can do so
# by editing the "0.70" to reflect another percentage. For example, if you want to use 50% of
# your logical cores, you would change the value to "0.50". And for 30%, you would change the
# value to "0.30".
#
# ⚠️ It is HIGHLY DISCOURAGED to set this value >= 0.75 (75-100%), as this will cause system
# instability. If you don't like seeing smiley faces and memory dump screens, I highly
# recommend that you don't do that. :-) If you do, you're on your own with support.
#
$env:ASE_CPUCOUNT = [int][math]::Floor(((Get-CimInstance -ClassName Win32_ComputerSystem).NumberOfLogicalProcessors) * 0.50)
##############################################
# LOGO
##############################################
Display-Logo
##############################################
# DEPENDENCY VALIDATION
##############################################
Write-Host -ForegroundColor Cyan "⚙️ Checking dependencies..."
$Dependencies = @(
"cache",
'7zip',
"git",
"visualstudio",
"windowssdk",
"cmake",
"ninja",
"python",
"depot_tools",
"skia",
"asesprite",
"llvm"
)
$result = $true
ForEach ($Dependency in $Dependencies) {
$result = $result && $(Check-Dependency -DependencyName $Dependency)
}
if ($result -eq $false) {
Write-Host -ForegroundColor Red "⛔ One or more dependencies were not able to be met, the script will now exit."
exit 1
}
Write-Host -ForegroundColor Cyan "✨ All dependencies were met, proceeding with the build."
$result = Build-Ninja
if ($result -eq $false) {
Write-Host -ForegroundColor Red "⛔ Ninja build failed, the script will now exit."
exit 1
}
$result = Build-LLVM
if ($result -eq $false) {
Write-Host -ForegroundColor Red "⛔ LLVM build failed, the script will now exit."
exit 1
}
$result = Build-GoogleDepot
if ($result -eq $false) {
Write-Host -ForegroundColor Red "⛔ Google Depot Tools build failed, the script will now exit."
exit 1
}
$result = Build-Skia
if ($result -eq $false) {
Write-Host -ForegroundColor Red "⛔ Skia build failed, the script will now exit."
exit 1
}
$result = Build-Aseprite
if ($result -eq $false) {
Write-Host -ForegroundColor Red "⛔ Aseprite build failed, the script will now exit."
exit 1
}
Write-Host -ForegroundColor Magenta "🎉 All builds succeeded!"
##############################################
# POST-OPERATION
##############################################
$artifactPath = $(Emit-Artifact -Version "$env:ASE_ASEVERSION" -Release "Release" -Name "blASeprite" -Include @('.*$') -Exclude @('.*\.pdb$') -SourcePath "$env:ASE_ASESPRITEPATH\build\bin\" -OutputPath "$PWD")
if ($null -eq $artifactPath) {
Write-Host -ForegroundColor Red "⛔ Failed to emit artifact."
exit 1
}
Display-Success -ArtifactPath $artifactPath
exit 0
@loopyd
Copy link
Author

loopyd commented Oct 23, 2023

blAse - Single-File Aseprite Build Tool

Changelog

1.0.0

Release Notes

  1. This is the first release that produces a working build. After a month of testing and research into producing a working build of Skia and Aesprite compiled completely from source, from nothing, in PowerShell, its now public! 🎉
  2. Some parts of the API are unused. I realize that. These pieces where all added during testing. Some of them are deprecated. I left them in for snippet-sake, and because I didn't feel right destroying perfectly working code I can use to expand the usefulness of the Blep API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment