Movimiento de "Cuadrado" veloz en mapa blanco
This commit is contained in:
commit
b695b60153
55 files changed
+9391
No files matched your search
Executable
+81
@@ -0,0 +1,81 @@
|
||||
# Shared helpers for scripts/*.sh. Sourced, never run directly (hence the
|
||||
# leading underscore and no executable bit); every script sources it after
|
||||
# it has cd'd to the project root, because both helpers below run
|
||||
# `cmake -P cmake/<something>.cmake` relative to that.
|
||||
#
|
||||
# There is deliberately no Windows twin of this file: scripts/*.bat each keep
|
||||
# their own copy of these two small wrappers as labels (:read_config and
|
||||
# :resolve_build_dir), because sharing labels between .bat files is more
|
||||
# fragile than repeating them. What the wrappers *ask* is answered by the
|
||||
# same cmake scripts either way, so no logic is duplicated -- only the glue.
|
||||
|
||||
# Reads one value out of Configuration.cmake -- this project's single
|
||||
# source of truth -- via cmake/PrintConfig.cmake, instead of hardcoding a
|
||||
# second copy of a default that can silently drift out of sync. See that
|
||||
# file for exactly what this strips off of its output.
|
||||
_pmn_read_config() {
|
||||
cmake "-DPRINT_VARS=$1" -P cmake/PrintConfig.cmake | sed -E 's/^-- [^=]*=//'
|
||||
}
|
||||
|
||||
# _pmn_resolve_build_dir [<name>]
|
||||
#
|
||||
# Prints the build directory to use. With a <name> (a preset, or "default")
|
||||
# that is simply BUILD_OUTPUT_DIR/<name>; without one it is discovered: the
|
||||
# directory scripts/build.sh built last, else the only one that exists. If
|
||||
# neither works, the reason goes to stderr and the return status is 1.
|
||||
#
|
||||
# The logic itself is cmake/BuildDir.cmake's -- see it for the details and
|
||||
# the one-line answer protocol parsed here.
|
||||
_pmn_resolve_build_dir() {
|
||||
local answer
|
||||
# run.sh never needed cmake before it had to ask which build directory to
|
||||
# use, so say what's wrong instead of letting the shell's own "command not
|
||||
# found" and an empty answer speak for it.
|
||||
if ! command -v cmake >/dev/null 2>&1; then
|
||||
echo "error: cmake not found on PATH -- it is needed to work out which build directory to use." >&2
|
||||
echo " Put it on PATH, or set BUILD_DIR to the directory explicitly, e.g. BUILD_DIR=out/build/<preset>." >&2
|
||||
return 1
|
||||
fi
|
||||
answer="$(cmake "-DPMN_NAME=${1:-}" -P cmake/BuildDir.cmake | sed -E 's/^-- //')"
|
||||
case "$answer" in
|
||||
BUILD_DIR=*)
|
||||
printf '%s\n' "${answer#BUILD_DIR=}"
|
||||
;;
|
||||
ERROR=*)
|
||||
echo "error: ${answer#ERROR=}" >&2
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
echo "error: cmake/BuildDir.cmake did not answer as expected: ${answer}" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# _pmn_record_build_dir <name>
|
||||
#
|
||||
# Remembers <name> as the build directory built last, so the next
|
||||
# `scripts/run.sh` / `install.sh` / `docs.sh` without a --preset uses it.
|
||||
_pmn_record_build_dir() {
|
||||
cmake "-DPMN_NAME=$1" -DPMN_RECORD=ON -P cmake/BuildDir.cmake >/dev/null
|
||||
}
|
||||
|
||||
# On macOS, Homebrew's llvm formula -- the one that ships clang-tidy -- is
|
||||
# keg-only: installed, but deliberately not linked into PATH, so a script
|
||||
# looking for `clang-tidy` can't find it. Add its bin directory to the END of
|
||||
# PATH (never the front: Apple's clang must stay the one builds use). A
|
||||
# no-op everywhere else, and when llvm isn't installed.
|
||||
_pmn_extend_tool_path() {
|
||||
[ "$(uname -s)" = Darwin ] || return 0
|
||||
local d
|
||||
for d in /opt/homebrew/opt/llvm/bin /usr/local/opt/llvm/bin; do
|
||||
if [ -d "$d" ]; then
|
||||
case ":$PATH:" in
|
||||
*":$d:"*) ;;
|
||||
*) PATH="$PATH:$d" ;;
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
Executable
+581
@@ -0,0 +1,581 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Interactive installer for the tools this project builds with. Windows;
|
||||
called by scripts\setup.bat (Linux and macOS have scripts/_tools.sh).
|
||||
|
||||
.DESCRIPTION
|
||||
Checks each tool, and for every one that is missing asks -- ONE TOOL AT
|
||||
A TIME -- whether to install it, showing the exact command first. Nothing
|
||||
is installed without an explicit yes (or -Yes / -All).
|
||||
|
||||
Required Git, CMake 3.25+, and a C++ compiler: at least one of
|
||||
Visual Studio Build Tools (MSVC) or MinGW-w64 (via MSYS2)
|
||||
Optional LLVM (clang-cl, Clang, clang-format, clang-tidy), and the
|
||||
other MSYS2 toolchains (32-bit MinGW, CLANG64)
|
||||
|
||||
Installs go through winget (Windows Package Manager, part of the App
|
||||
Installer that ships with Windows 10 1809+ and Windows 11). Two things
|
||||
winget does not do for you, and this script does:
|
||||
|
||||
* put CMake and LLVM on PATH -- their installers leave it to you, and
|
||||
every guide that runs `winget install Kitware.CMake` ends with "now
|
||||
add C:\Program Files\CMake\bin to PATH by hand". A tool found in its
|
||||
usual folder but not on PATH is added to this session's PATH (which
|
||||
is handed back to setup.bat through -PathOut so the rest of setup
|
||||
can use it) and, if you agree, to your user PATH permanently.
|
||||
* update a fresh MSYS2, whose package databases are stale (twice, as
|
||||
MSYS2 itself recommends -- updating its runtime asks for a restart)
|
||||
before installing MinGW-w64 into it.
|
||||
|
||||
Written for Windows PowerShell 5.1 as much as for PowerShell 7: no ??,
|
||||
no ternary, no && / ||, nested Join-Path (5.1 has no -AdditionalChildPath),
|
||||
and plain ASCII only (5.1 reads a BOM-less file as the ANSI code page).
|
||||
|
||||
.PARAMETER Yes
|
||||
Install the REQUIRED tools without asking. For scripts and CI. Without a
|
||||
terminal and without this, nothing is asked and nothing is installed.
|
||||
|
||||
.PARAMETER All
|
||||
Also install the optional tools without asking (implies -Yes).
|
||||
|
||||
.PARAMETER Check
|
||||
Only report what is and is not installed; change nothing. Exits 1 if a
|
||||
required tool is missing.
|
||||
|
||||
.PARAMETER PathOut
|
||||
File to write the directories that were added to this session's PATH to,
|
||||
one per line, so the calling batch file can add them to its own.
|
||||
|
||||
.NOTES
|
||||
Exit status: 0 if every required tool is present at the end, 1 if not.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Yes,
|
||||
[switch]$All,
|
||||
[switch]$Check,
|
||||
[string]$PathOut = ''
|
||||
)
|
||||
|
||||
if ($All) { $Yes = $true }
|
||||
|
||||
# Oldest CMake this project supports (cmake_minimum_required in
|
||||
# CMakeLists.txt, and the presets file's cmakeMinimumRequired).
|
||||
$script:MinCmake = New-Object System.Version 3, 25
|
||||
|
||||
# The Visual Studio Build Tools package. 'Microsoft.VisualStudio.BuildTools'
|
||||
# (no year) is the newest release; the 2022 one is the long-established,
|
||||
# documented path, so it is the default -- change the id here to prefer
|
||||
# the newer one.
|
||||
$script:MsvcWingetId = 'Microsoft.VisualStudio.2022.BuildTools'
|
||||
$script:MsvcOverride = '--passive --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended'
|
||||
|
||||
# Answers that carry over from one prompt to the next.
|
||||
$script:AnsAll = $false # "a": yes to everything still to come
|
||||
$script:AnsSkipOptional = $false # "s": no to every OPTIONAL tool still to come
|
||||
$script:AnsQuit = $false # "q": stop asking altogether
|
||||
|
||||
# Directories added to this session's PATH, for setup.bat to pick up.
|
||||
$script:PathAdds = New-Object System.Collections.ArrayList
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Small helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Say([string]$Text = '') { Write-Host $Text }
|
||||
function Note([string]$Text) { Write-Host $Text -ForegroundColor DarkGray }
|
||||
function Good([string]$Text) { Write-Host $Text -ForegroundColor Green }
|
||||
function Warn([string]$Text) { Write-Host $Text -ForegroundColor Yellow }
|
||||
function Bad([string]$Text) { Write-Host $Text -ForegroundColor Red }
|
||||
|
||||
# Nested Join-Path: Windows PowerShell 5.1's Join-Path takes only two parts.
|
||||
function Join-Parts {
|
||||
$p = [string]$args[0]
|
||||
for ($i = 1; $i -lt $args.Count; $i++) { $p = Join-Path $p ([string]$args[$i]) }
|
||||
return $p
|
||||
}
|
||||
|
||||
function Get-ProgramFiles { return $env:ProgramFiles }
|
||||
function Get-ProgramFilesX86 { return ${env:ProgramFiles(x86)} }
|
||||
|
||||
# MSYS2's install folder: C:\msys64 unless MSYS2_ROOT says otherwise.
|
||||
function Get-Msys2Root {
|
||||
if ($env:MSYS2_ROOT) { return $env:MSYS2_ROOT }
|
||||
$drive = $env:SystemDrive
|
||||
if (-not $drive) { $drive = 'C:' }
|
||||
return (Join-Path ($drive + '\') 'msys64')
|
||||
}
|
||||
|
||||
# The first executable called $Name on PATH, or one in $FallbackDirs (the
|
||||
# folder the installer puts it in even when it doesn't touch PATH), or $null.
|
||||
function Find-Exe([string]$Name, [string[]]$FallbackDirs) {
|
||||
$cmd = Get-Command $Name -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($cmd) { return $cmd.Source }
|
||||
foreach ($dir in $FallbackDirs) {
|
||||
if (-not $dir) { continue }
|
||||
foreach ($ext in @('.exe', '')) {
|
||||
$candidate = Join-Path $dir ($Name + $ext)
|
||||
if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function New-Status([string]$State, [string]$Detail = '') {
|
||||
return [pscustomobject]@{ State = $State; Detail = $Detail }
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PATH (session and user)
|
||||
#
|
||||
# Wrapped in functions so a test can replace the two that touch the real
|
||||
# registry.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-UserPathValue { return [Environment]::GetEnvironmentVariable('Path', 'User') }
|
||||
function Set-UserPathValue([string]$Value) { [Environment]::SetEnvironmentVariable('Path', $Value, 'User') }
|
||||
function Get-MachinePathValue { return [Environment]::GetEnvironmentVariable('Path', 'Machine') }
|
||||
|
||||
function Split-PathList([string]$List) {
|
||||
if (-not $List) { return @() }
|
||||
$sep = [System.IO.Path]::PathSeparator
|
||||
$items = @()
|
||||
foreach ($part in $List.Split($sep)) {
|
||||
$trimmed = $part.Trim().TrimEnd('\', '/')
|
||||
if ($trimmed) { $items += $trimmed }
|
||||
}
|
||||
return $items
|
||||
}
|
||||
|
||||
function Test-InList([string]$List, [string]$Dir) {
|
||||
$want = $Dir.TrimEnd('\', '/')
|
||||
foreach ($item in (Split-PathList $List)) {
|
||||
if ($item -ieq $want) { return $true }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-OnSessionPath([string]$Dir) { return (Test-InList $env:Path $Dir) }
|
||||
|
||||
function Test-OnPersistentPath([string]$Dir) {
|
||||
return ((Test-InList (Get-UserPathValue) $Dir) -or (Test-InList (Get-MachinePathValue) $Dir))
|
||||
}
|
||||
|
||||
function Add-ToSessionPath([string]$Dir) {
|
||||
if (Test-OnSessionPath $Dir) { return }
|
||||
$sep = [System.IO.Path]::PathSeparator
|
||||
$current = $env:Path
|
||||
if (-not $current) { $current = '' }
|
||||
$env:Path = $current.TrimEnd($sep) + $sep + $Dir
|
||||
[void]$script:PathAdds.Add($Dir)
|
||||
}
|
||||
|
||||
function Add-ToUserPath([string]$Dir) {
|
||||
$current = Get-UserPathValue
|
||||
$sep = [System.IO.Path]::PathSeparator
|
||||
if ($current) { $new = $current.TrimEnd($sep) + $sep + $Dir } else { $new = $Dir }
|
||||
Set-UserPathValue $new
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-WingetStatus {
|
||||
if (Get-Command winget -ErrorAction SilentlyContinue) { return (New-Status 'ok' 'winget') }
|
||||
return (New-Status 'missing')
|
||||
}
|
||||
|
||||
function Get-GitStatus {
|
||||
$exe = Find-Exe 'git' @((Join-Parts (Get-ProgramFiles) 'Git' 'cmd'))
|
||||
if (-not $exe) { return (New-Status 'missing') }
|
||||
$ver = ''
|
||||
try { $ver = (& $exe --version 2>$null | Select-Object -First 1) -replace '^git version ', '' } catch { }
|
||||
return (New-Status 'ok' $ver)
|
||||
}
|
||||
|
||||
function Get-CmakeStatus {
|
||||
$exe = Find-Exe 'cmake' @((Join-Parts (Get-ProgramFiles) 'CMake' 'bin'))
|
||||
if (-not $exe) { return (New-Status 'missing') }
|
||||
$ver = ''
|
||||
try {
|
||||
$first = & $exe --version 2>$null | Select-Object -First 1
|
||||
if ($first -match '(\d+\.\d+(\.\d+)?)') { $ver = $Matches[1] }
|
||||
} catch { }
|
||||
if (-not $ver) { return (New-Status 'ok' '') }
|
||||
$parsed = $null
|
||||
if ([System.Version]::TryParse($ver, [ref]$parsed) -and $parsed -lt $script:MinCmake) {
|
||||
return (New-Status 'old' $ver)
|
||||
}
|
||||
return (New-Status 'ok' $ver)
|
||||
}
|
||||
|
||||
# MSVC: the Visual Studio Installer ships vswhere.exe, which knows whether
|
||||
# the C++ x86/x64 tools are installed in ANY Visual Studio or Build Tools.
|
||||
function Get-MsvcStatus {
|
||||
$pf86 = Get-ProgramFilesX86
|
||||
if (-not $pf86) { return (New-Status 'missing') }
|
||||
$vswhere = Join-Parts $pf86 'Microsoft Visual Studio' 'Installer' 'vswhere.exe'
|
||||
if (-not (Test-Path -LiteralPath $vswhere -PathType Leaf)) { return (New-Status 'missing') }
|
||||
$where = $null
|
||||
try {
|
||||
$where = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null | Select-Object -First 1
|
||||
} catch { }
|
||||
if ($where) { return (New-Status 'ok' ([string]$where)) }
|
||||
return (New-Status 'missing')
|
||||
}
|
||||
|
||||
function Get-LlvmStatus {
|
||||
$exe = Find-Exe 'clang-cl' @((Join-Parts (Get-ProgramFiles) 'LLVM' 'bin'))
|
||||
if (-not $exe) { return (New-Status 'missing') }
|
||||
return (New-Status 'ok' (Split-Path -Parent $exe))
|
||||
}
|
||||
|
||||
function Get-Msys2Status {
|
||||
$bash = Join-Parts (Get-Msys2Root) 'usr' 'bin' 'bash.exe'
|
||||
if (Test-Path -LiteralPath $bash -PathType Leaf) { return (New-Status 'ok' (Get-Msys2Root)) }
|
||||
return (New-Status 'missing')
|
||||
}
|
||||
|
||||
function Get-MingwStatus([string]$Subdir) {
|
||||
$gpp = Join-Parts (Get-Msys2Root) $Subdir 'bin' 'g++.exe'
|
||||
if (Test-Path -LiteralPath $gpp -PathType Leaf) { return (New-Status 'ok' (Split-Path -Parent $gpp)) }
|
||||
return (New-Status 'missing')
|
||||
}
|
||||
|
||||
function Get-Clang64Status {
|
||||
$clang = Join-Parts (Get-Msys2Root) 'clang64' 'bin' 'clang++.exe'
|
||||
if (Test-Path -LiteralPath $clang -PathType Leaf) { return (New-Status 'ok' (Split-Path -Parent $clang)) }
|
||||
return (New-Status 'missing')
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Asking
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Test-CanAsk { return (-not [Console]::IsInputRedirected) }
|
||||
|
||||
# One line from the user, or $null at end of input. A function of its own so
|
||||
# a test can stand in for the console.
|
||||
function Read-Answer { return [Console]::ReadLine() }
|
||||
|
||||
# Would an OPTIONAL tool be offered (asked about, or installed by -All) now?
|
||||
function Test-OptionalWanted {
|
||||
if ($script:AnsQuit -or $script:AnsSkipOptional) { return $false }
|
||||
if ($All -or $script:AnsAll) { return $true }
|
||||
return (Test-CanAsk)
|
||||
}
|
||||
|
||||
# Returns $true for "install it". Sets $script:Ans* for answers that outlast
|
||||
# one prompt.
|
||||
function Confirm-Install([string]$Kind, [string]$Question) {
|
||||
if ($script:AnsQuit) { return $false }
|
||||
if ($script:AnsAll) { return $true }
|
||||
if ($Kind -eq 'optional' -and $script:AnsSkipOptional) { return $false }
|
||||
|
||||
# Flags answer for you...
|
||||
if ($Kind -eq 'required' -and $Yes) { return $true }
|
||||
if ($All) { return $true }
|
||||
|
||||
# ...and with no terminal there is nobody to ask.
|
||||
if (-not (Test-CanAsk)) {
|
||||
Note ' (not asking: no terminal. Re-run interactively, or pass --yes / --all.)'
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Kind -eq 'required') { $prompt = " $Question [Y/n/a/q] " } else { $prompt = " $Question [y/N/a/s/q] " }
|
||||
while ($true) {
|
||||
Write-Host $prompt -NoNewline
|
||||
$answer = Read-Answer
|
||||
if ($null -eq $answer) {
|
||||
# End of input (Ctrl-Z): stop asking rather than loop on it.
|
||||
Write-Host ''
|
||||
$script:AnsQuit = $true
|
||||
return $false
|
||||
}
|
||||
switch ($answer.Trim().ToLowerInvariant()) {
|
||||
{ $_ -in @('y', 'yes') } { return $true }
|
||||
{ $_ -in @('n', 'no') } { return $false }
|
||||
'' { return ($Kind -eq 'required') }
|
||||
{ $_ -in @('a', 'all') } { $script:AnsAll = $true; return $true }
|
||||
{ $_ -in @('s', 'skip') } { $script:AnsSkipOptional = $true; return $false }
|
||||
{ $_ -in @('q', 'quit') } { $script:AnsQuit = $true; return $false }
|
||||
default {
|
||||
if ($Kind -eq 'required') {
|
||||
Note ' y = install n = not this one a = install everything left q = stop asking'
|
||||
} else {
|
||||
Note ' y = install n = not this one a = install everything left s = skip the rest of the optional tools q = stop asking'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Installing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Invoke-Winget([string]$Id, [string]$Override = '') {
|
||||
$winget = @('install', '--id', $Id, '-e', '--source', 'winget',
|
||||
'--accept-package-agreements', '--accept-source-agreements')
|
||||
$shown = 'winget ' + ($winget -join ' ')
|
||||
if ($Override) {
|
||||
$winget += @('--override', $Override)
|
||||
$shown += ' --override "' + $Override + '"'
|
||||
}
|
||||
Write-Host (' $ ' + $shown) -ForegroundColor DarkGray
|
||||
& winget @winget
|
||||
return ($LASTEXITCODE -eq 0)
|
||||
}
|
||||
|
||||
# MSYS2's own advice for a fresh install: its package databases are stale, so
|
||||
# update the base system first, twice (updating the runtime asks for a
|
||||
# restart of the shell), then install into it.
|
||||
function Invoke-Msys2Packages([string[]]$Packages) {
|
||||
$bash = Join-Parts (Get-Msys2Root) 'usr' 'bin' 'bash.exe'
|
||||
if (-not (Test-Path -LiteralPath $bash -PathType Leaf)) {
|
||||
Bad ' MSYS2 is not installed at the expected place -- install it first.'
|
||||
return $false
|
||||
}
|
||||
Write-Host (' $ ' + $bash + ' -lc "pacman --noconfirm -Syuu" (twice; a fresh MSYS2 needs it)') -ForegroundColor DarkGray
|
||||
& $bash -lc 'pacman --noconfirm -Syuu'
|
||||
& $bash -lc 'pacman --noconfirm -Syuu'
|
||||
$list = $Packages -join ' '
|
||||
Write-Host (' $ ' + $bash + ' -lc "pacman -S --needed --noconfirm ' + $list + '"') -ForegroundColor DarkGray
|
||||
& $bash -lc ('pacman -S --needed --noconfirm ' + $list)
|
||||
return ($LASTEXITCODE -eq 0)
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The catalog
|
||||
#
|
||||
# Ordered as they are asked. Each row: a stable Id, whether it is Required or
|
||||
# optional (msvc is decided at run time -- see below), a label, why it
|
||||
# matters, how to tell if it is there, how to install it, and the folder
|
||||
# that has to be on PATH for it to be usable (or $null).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-Catalog {
|
||||
$pf = Get-ProgramFiles
|
||||
$msys = Get-Msys2Root
|
||||
return @(
|
||||
@{ Id = 'git'; Kind = 'required'; Label = 'Git';
|
||||
Why = 'FetchContent clones dependencies (fmt) with it';
|
||||
Status = { Get-GitStatus }; Install = { Invoke-Winget 'Git.Git' };
|
||||
PathDir = (Join-Parts $pf 'Git' 'cmd'); PathDefault = $true },
|
||||
|
||||
@{ Id = 'cmake'; Kind = 'required'; Label = 'CMake 3.25 or newer';
|
||||
Why = 'configures and drives every build';
|
||||
Status = { Get-CmakeStatus }; Install = { Invoke-Winget 'Kitware.CMake' };
|
||||
PathDir = (Join-Parts $pf 'CMake' 'bin'); PathDefault = $true },
|
||||
|
||||
# A compiler: at least one of MSVC or MinGW-w64 is required. Which
|
||||
# kind msvc is depends on whether one is already there (see Main).
|
||||
@{ Id = 'msvc'; Kind = 'compiler'; Label = 'Visual Studio Build Tools (MSVC)';
|
||||
Why = 'the windows-*-msvc presets, and the headers/libraries the windows-*-clang-cl ones need';
|
||||
Status = { Get-MsvcStatus };
|
||||
Install = { Invoke-Winget $script:MsvcWingetId $script:MsvcOverride };
|
||||
PathDir = $null },
|
||||
|
||||
@{ Id = 'llvm'; Kind = 'optional'; Label = 'LLVM (clang-cl, Clang, clang-format, clang-tidy)';
|
||||
Why = 'the windows-*-clang-cl presets, scripts\refactor.bat and the pre-commit hook';
|
||||
Status = { Get-LlvmStatus }; Install = { Invoke-Winget 'LLVM.LLVM' };
|
||||
PathDir = (Join-Parts $pf 'LLVM' 'bin'); PathDefault = $true },
|
||||
|
||||
@{ Id = 'msys2'; Kind = 'optional'; Label = 'MSYS2';
|
||||
Why = 'the home of MinGW-w64 on Windows (needed for the windows-*-mingw* presets)';
|
||||
Status = { Get-Msys2Status }; Install = { Invoke-Winget 'MSYS2.MSYS2' };
|
||||
PathDir = $null },
|
||||
|
||||
@{ Id = 'mingw64'; Kind = 'optional'; Label = 'MinGW-w64 GCC, 64-bit';
|
||||
Why = 'the windows-x64-mingw64 preset'; Needs = 'msys2';
|
||||
Status = { Get-MingwStatus 'mingw64' };
|
||||
Install = { Invoke-Msys2Packages @('mingw-w64-x86_64-gcc') };
|
||||
PathDir = (Join-Parts $msys 'mingw64' 'bin'); PathDefault = $false },
|
||||
|
||||
@{ Id = 'mingw32'; Kind = 'optional'; Label = 'MinGW-w64 GCC, 32-bit';
|
||||
Why = 'the windows-x86-mingw32 preset'; Needs = 'msys2';
|
||||
Status = { Get-MingwStatus 'mingw32' };
|
||||
Install = { Invoke-Msys2Packages @('mingw-w64-i686-gcc') };
|
||||
PathDir = (Join-Parts $msys 'mingw32' 'bin'); PathDefault = $false },
|
||||
|
||||
@{ Id = 'clang64'; Kind = 'optional'; Label = 'MSYS2 CLANG64 (GNU-style Clang)';
|
||||
Why = 'the windows-x64-clang preset'; Needs = 'msys2';
|
||||
Status = { Get-Clang64Status };
|
||||
Install = { Invoke-Msys2Packages @('mingw-w64-clang-x86_64-clang') };
|
||||
PathDir = (Join-Parts $msys 'clang64' 'bin'); PathDefault = $false }
|
||||
)
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Show-ManualHelp {
|
||||
Say ' Install these yourself instead:'
|
||||
Say ' Git https://git-scm.com/download/win'
|
||||
Say ' CMake https://cmake.org/download/ (tick "Add CMake to the system PATH")'
|
||||
Say ' MSVC https://visualstudio.microsoft.com/visual-cpp-build-tools/ (workload: Desktop development with C++)'
|
||||
Say ' LLVM https://github.com/llvm/llvm-project/releases'
|
||||
Say ' MSYS2 https://www.msys2.org/'
|
||||
}
|
||||
|
||||
function Invoke-Main {
|
||||
$catalog = Get-Catalog
|
||||
$wingetStatus = Get-WingetStatus
|
||||
$haveWinget = ($wingetStatus.State -eq 'ok')
|
||||
|
||||
Say ('==> Checking the tools this project builds with (Windows, winget: ' + $(if ($haveWinget) { 'found' } else { 'NOT found' }) + ')')
|
||||
|
||||
# ---- report ---------------------------------------------------------
|
||||
$status = @{}
|
||||
foreach ($row in $catalog) { $status[$row.Id] = (& $row.Status) }
|
||||
|
||||
$haveCompiler = ($status['msvc'].State -eq 'ok') -or ($status['mingw64'].State -eq 'ok') -or ($status['mingw32'].State -eq 'ok')
|
||||
foreach ($row in $catalog) {
|
||||
$st = $status[$row.Id]
|
||||
$kind = $row.Kind
|
||||
if ($kind -eq 'compiler') { if ($haveCompiler) { $kind = 'optional' } else { $kind = 'required' } }
|
||||
$row.EffKind = $kind
|
||||
switch ($st.State) {
|
||||
'ok' {
|
||||
if ($st.Detail) { Write-Host (' [ ok ] ' + $row.Label.PadRight(50) + ' ') -NoNewline -ForegroundColor Green; Note $st.Detail }
|
||||
else { Good (' [ ok ] ' + $row.Label) }
|
||||
}
|
||||
'old' { Warn (' [old ] ' + $row.Label.PadRight(50) + ' ' + $st.Detail + ' (needs ' + $script:MinCmake.ToString() + '+)') }
|
||||
default {
|
||||
if ($kind -eq 'required') { Bad (' [ !! ] ' + $row.Label.PadRight(50) + ' missing (required)') }
|
||||
else { Note (' [ -- ] ' + $row.Label.PadRight(50) + ' missing (optional)') }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $haveCompiler) {
|
||||
Warn ' A C++ compiler is required: Visual Studio Build Tools, or MinGW-w64 through MSYS2 (either one).'
|
||||
}
|
||||
|
||||
# ---- -Check: report only --------------------------------------------
|
||||
if ($Check) {
|
||||
$missing = ($status['git'].State -ne 'ok') -or ($status['cmake'].State -ne 'ok') -or (-not $haveCompiler)
|
||||
if ($missing) { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
$anythingToInstall = $false
|
||||
foreach ($row in $catalog) { if ($status[$row.Id].State -ne 'ok') { $anythingToInstall = $true } }
|
||||
if ($anythingToInstall -and -not $haveWinget) {
|
||||
Say ''
|
||||
Warn ' winget (Windows Package Manager) was not found, so this script cannot install anything.'
|
||||
Say ' It comes with the App Installer: https://aka.ms/getwinget (or the Microsoft Store).'
|
||||
Show-ManualHelp
|
||||
}
|
||||
|
||||
# ---- offer installs, one at a time ----------------------------------
|
||||
$skipped = @()
|
||||
$failed = @()
|
||||
foreach ($row in $catalog) {
|
||||
if ($script:AnsQuit) { break }
|
||||
$st = & $row.Status # re-check: an earlier install may have provided it
|
||||
if ($st.State -eq 'ok') { continue }
|
||||
if (-not $haveWinget) { continue }
|
||||
|
||||
# MinGW rows live inside MSYS2.
|
||||
if ($row.Needs) {
|
||||
$needRow = $catalog | Where-Object { $_.Id -eq $row.Needs } | Select-Object -First 1
|
||||
$needed = & $needRow.Status
|
||||
if ($needed.State -ne 'ok') { continue }
|
||||
}
|
||||
|
||||
# After the msvc row, "a compiler exists" may have changed the kind
|
||||
# of the rows still to come.
|
||||
$kind = $row.EffKind
|
||||
if ($kind -eq 'required' -and $row.Id -eq 'msvc') {
|
||||
$now = ((Get-MingwStatus 'mingw64').State -eq 'ok') -or ((Get-MingwStatus 'mingw32').State -eq 'ok')
|
||||
if ($now) { $kind = 'optional' }
|
||||
}
|
||||
|
||||
if ($kind -eq 'optional' -and -not (Test-OptionalWanted)) {
|
||||
$skipped += $row.Label
|
||||
continue
|
||||
}
|
||||
|
||||
Say ''
|
||||
Write-Host (' ' + $row.Label) -NoNewline -ForegroundColor White
|
||||
Say (' -- ' + $row.Why)
|
||||
if ($st.State -eq 'old') { Note (' (installed: ' + $st.Detail + ', but too old)') }
|
||||
|
||||
if (Confirm-Install $kind ('Install ' + $row.Label + '?')) {
|
||||
$ok = & $row.Install
|
||||
$after = & $row.Status
|
||||
if ($after.State -eq 'ok') {
|
||||
Good ' installed'
|
||||
} elseif ($after.State -eq 'old') {
|
||||
Warn (' CMake ' + $after.Detail + ' is older than the ' + $script:MinCmake.ToString() + ' this project needs.')
|
||||
$failed += $row.Label
|
||||
} elseif (-not $ok) {
|
||||
Bad ' install failed -- see the output above.'
|
||||
$failed += $row.Label
|
||||
} else {
|
||||
Warn (' the install finished, but ' + $row.Label + ' still is not found -- open a new terminal, or check the output above.')
|
||||
$failed += $row.Label
|
||||
}
|
||||
} elseif ($kind -eq 'optional') {
|
||||
$skipped += $row.Label
|
||||
}
|
||||
}
|
||||
if ($skipped.Count -gt 0) {
|
||||
Say ''
|
||||
Note (' Optional, not installed: ' + ($skipped -join ', '))
|
||||
Note ' (scripts\setup.bat, run interactively, offers each one; --all installs them all.)'
|
||||
}
|
||||
|
||||
# ---- PATH ------------------------------------------------------------
|
||||
# A tool that is installed but whose folder is not on PATH is unusable
|
||||
# from a plain terminal -- and, for CMake, from the rest of setup.bat.
|
||||
foreach ($row in $catalog) {
|
||||
if (-not $row.PathDir) { continue }
|
||||
$st = & $row.Status
|
||||
if ($st.State -ne 'ok') { continue }
|
||||
if (-not (Test-Path -LiteralPath $row.PathDir -PathType Container)) { continue }
|
||||
if (Test-OnSessionPath $row.PathDir) { continue }
|
||||
|
||||
# This session, always: what follows in setup needs it.
|
||||
Add-ToSessionPath $row.PathDir
|
||||
if (Test-OnPersistentPath $row.PathDir) { continue }
|
||||
|
||||
Say ''
|
||||
Say (' ' + $row.Label + ' is installed, but its folder is not on your PATH:')
|
||||
Say (' ' + $row.PathDir)
|
||||
if ($row.PathDefault) { $kind = 'required' } else { $kind = 'optional' }
|
||||
if (-not $row.PathDefault) {
|
||||
Note ' (Adding MSYS2 toolchain folders puts their g++/gcc on PATH for every program; the MinGW presets use'
|
||||
Note ' the prefixed names, so you can also leave this out and use an MSYS2 shell instead.)'
|
||||
}
|
||||
if (Confirm-Install $kind 'Add it to your user PATH (new terminals will see it)?') {
|
||||
Add-ToUserPath $row.PathDir
|
||||
Good ' added -- open a new terminal to use it there.'
|
||||
}
|
||||
}
|
||||
|
||||
# ---- hand this session's PATH additions back to setup.bat ------------
|
||||
if ($PathOut -and $script:PathAdds.Count -gt 0) {
|
||||
Set-Content -LiteralPath $PathOut -Value ($script:PathAdds.ToArray()) -Encoding ASCII
|
||||
}
|
||||
|
||||
# ---- verdict ---------------------------------------------------------
|
||||
$exit = 0
|
||||
$needGit = (Get-GitStatus).State -ne 'ok'
|
||||
$needCmake = (Get-CmakeStatus).State -ne 'ok'
|
||||
$needCompiler = -not (((Get-MsvcStatus).State -eq 'ok') -or ((Get-MingwStatus 'mingw64').State -eq 'ok') -or ((Get-MingwStatus 'mingw32').State -eq 'ok'))
|
||||
if ($needGit) { Bad ' still missing (required): Git'; $exit = 1 }
|
||||
if ($needCmake) { Bad ' still missing (required): CMake 3.25 or newer'; $exit = 1 }
|
||||
if ($needCompiler) { Bad ' still missing (required): a C++ compiler (Visual Studio Build Tools, or MinGW-w64 via MSYS2)'; $exit = 1 }
|
||||
if ($failed.Count -gt 0 -and $exit -eq 0) { Warn (' These did not install: ' + ($failed -join ', ')) }
|
||||
return $exit
|
||||
}
|
||||
|
||||
# Run only when executed, not when dot-sourced (a test dot-sources this file
|
||||
# to replace the functions that touch the registry).
|
||||
if ($MyInvocation.InvocationName -ne '.') {
|
||||
$code = Invoke-Main
|
||||
exit $code
|
||||
}
|
||||
Executable
+626
@@ -0,0 +1,626 @@
|
||||
# Interactive installer for the tools this project builds with. Sourced by
|
||||
# scripts/setup.sh (Linux and macOS -- Windows has scripts/_tools.ps1); never
|
||||
# run directly, hence the leading underscore and no executable bit.
|
||||
#
|
||||
# What it does: works out what OS and package manager it's on, checks each
|
||||
# tool in a catalog, and for every one that's missing asks -- ONE TOOL AT A
|
||||
# TIME -- whether to install it, showing the exact command first. Nothing is
|
||||
# installed without an explicit yes (or --yes / --all, see below).
|
||||
#
|
||||
# Written for bash 3.2, which is what macOS ships as /bin/bash: no
|
||||
# associative arrays, no mapfile, no ${var,,}, and every array that can be
|
||||
# empty is expanded with the ${arr[@]+"${arr[@]}"} idiom.
|
||||
#
|
||||
# Package managers: apt (Debian/Ubuntu), dnf (Fedora/RHEL), pacman (Arch) on
|
||||
# Linux; Homebrew on macOS. Anything else is told what the tools are and
|
||||
# left to install them itself. The cross-compilers, MinGW and emulators
|
||||
# are offered on apt systems only: that is the package layout
|
||||
# cmake/toolchains/ was written and tested against (a cross toolchain's
|
||||
# target files under /usr/<triplet>), and offering a package that would
|
||||
# then not work is worse than not offering it.
|
||||
#
|
||||
# Entry point: _pmn_tools_run
|
||||
# Environment in (set by setup.sh from its flags):
|
||||
# PMN_TOOLS_YES=1 install REQUIRED tools without asking
|
||||
# PMN_TOOLS_ALL=1 also install OPTIONAL tools without asking
|
||||
# PMN_TOOLS_CHECK=1 only report; change nothing
|
||||
# Returns 0 if every required tool is present at the end, 1 if not.
|
||||
#
|
||||
# Without a terminal on stdin and without --yes/--all it does not prompt (a
|
||||
# CI job must never hang on a question nobody can answer): it reports and
|
||||
# leaves everything alone.
|
||||
|
||||
# Oldest CMake this project supports (cmake_minimum_required in
|
||||
# CMakeLists.txt, and the presets file's cmakeMinimumRequired).
|
||||
PMN_CMAKE_MIN_MAJOR=3
|
||||
PMN_CMAKE_MIN_MINOR=25
|
||||
|
||||
PMN_TOOLS_YES="${PMN_TOOLS_YES:-0}"
|
||||
PMN_TOOLS_ALL="${PMN_TOOLS_ALL:-0}"
|
||||
PMN_TOOLS_CHECK="${PMN_TOOLS_CHECK:-0}"
|
||||
|
||||
# Answers that carry over from one prompt to the next.
|
||||
_PMN_ANS_ALL=0 # "a": yes to everything still to come
|
||||
_PMN_ANS_SKIP_OPT=0 # "s": no to every OPTIONAL tool still to come
|
||||
_PMN_ANS_QUIT=0 # "q": stop asking altogether
|
||||
_PMN_APT_UPDATED=0 # apt-get update runs once, before the first apt install
|
||||
|
||||
if [ -t 1 ]; then
|
||||
_PMN_BOLD=$'\033[1m'; _PMN_DIM=$'\033[2m'; _PMN_GREEN=$'\033[32m'
|
||||
_PMN_YELLOW=$'\033[33m'; _PMN_RED=$'\033[31m'; _PMN_RESET=$'\033[0m'
|
||||
else
|
||||
_PMN_BOLD=''; _PMN_DIM=''; _PMN_GREEN=''; _PMN_YELLOW=''; _PMN_RED=''; _PMN_RESET=''
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_pmn_have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# The two directories Homebrew's llvm formula is keg-only in (Apple Silicon,
|
||||
# Intel). Keg-only: installed, but deliberately not linked into PATH, which
|
||||
# is where clang-tidy comes from.
|
||||
_pmn_brew_llvm_bin() {
|
||||
local d
|
||||
for d in /opt/homebrew/opt/llvm/bin /usr/local/opt/llvm/bin; do
|
||||
if [ -d "$d" ]; then printf '%s\n' "$d"; return 0; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Sets PMN_OS (linux|macos|other), PMN_ARCH (x64|arm64|arm32|""), PMN_PM
|
||||
# (apt|dnf|pacman|brew|none) and PMN_SUDO ("" or "sudo").
|
||||
_pmn_tools_detect() {
|
||||
local machine
|
||||
case "$(uname -s)" in
|
||||
Linux) PMN_OS=linux ;;
|
||||
Darwin) PMN_OS=macos ;;
|
||||
*) PMN_OS=other ;;
|
||||
esac
|
||||
|
||||
machine="$(uname -m)"
|
||||
# An x86_64 shell under Rosetta 2 on an Apple Silicon Mac still reports
|
||||
# x86_64 -- ask about the machine (see cmake/HostArch.cmake).
|
||||
if [ "$PMN_OS" = macos ] && [ "$machine" = x86_64 ] \
|
||||
&& [ "$(sysctl -in hw.optional.arm64 2>/dev/null || true)" = 1 ]; then
|
||||
machine=arm64
|
||||
fi
|
||||
case "$machine" in
|
||||
x86_64|amd64) PMN_ARCH=x64 ;;
|
||||
aarch64|arm64) PMN_ARCH=arm64 ;;
|
||||
arm*) PMN_ARCH=arm32 ;;
|
||||
*) PMN_ARCH="" ;;
|
||||
esac
|
||||
|
||||
PMN_PM=none
|
||||
if [ "$PMN_OS" = macos ]; then
|
||||
# Homebrew is often installed but not on PATH in a fresh shell.
|
||||
if ! _pmn_have brew; then
|
||||
local b
|
||||
for b in /opt/homebrew/bin/brew /usr/local/bin/brew; do
|
||||
if [ -x "$b" ]; then eval "$("$b" shellenv)"; break; fi
|
||||
done
|
||||
fi
|
||||
if _pmn_have brew; then PMN_PM=brew; fi
|
||||
elif [ "$PMN_OS" = linux ]; then
|
||||
if _pmn_have apt-get; then PMN_PM=apt
|
||||
elif _pmn_have dnf; then PMN_PM=dnf
|
||||
elif _pmn_have pacman; then PMN_PM=pacman
|
||||
fi
|
||||
fi
|
||||
|
||||
# Root is only a question for Linux's system package managers. Homebrew
|
||||
# refuses to run as root and never needs it -- and decided HERE, before
|
||||
# a fresh Homebrew exists, this would otherwise be computed against
|
||||
# PMN_PM=none.
|
||||
PMN_SUDO=""
|
||||
if [ "$PMN_OS" = linux ] && [ "$(id -u)" != 0 ]; then
|
||||
if _pmn_have sudo; then PMN_SUDO=sudo; else PMN_SUDO="?"; fi
|
||||
fi
|
||||
}
|
||||
|
||||
# "3.28.3" for the installed cmake, or "" if there isn't one.
|
||||
_pmn_cmake_version() {
|
||||
_pmn_have cmake || return 0
|
||||
cmake --version 2>/dev/null | sed -n '1s/^[^0-9]*\([0-9][0-9.]*\).*/\1/p'
|
||||
}
|
||||
|
||||
# 0 if the installed cmake is at least the minimum.
|
||||
_pmn_cmake_new_enough() {
|
||||
local v major minor
|
||||
v="$(_pmn_cmake_version)"
|
||||
[ -n "$v" ] || return 1
|
||||
major="${v%%.*}"; minor="${v#*.}"; minor="${minor%%.*}"
|
||||
if [ "$major" -gt "$PMN_CMAKE_MIN_MAJOR" ]; then return 0; fi
|
||||
[ "$major" -eq "$PMN_CMAKE_MIN_MAJOR" ] && [ "$minor" -ge "$PMN_CMAKE_MIN_MINOR" ]
|
||||
}
|
||||
|
||||
# macOS: /usr/bin/git, /usr/bin/clang and friends exist on every Mac as
|
||||
# stubs that pop up an installer dialog when run without the Command Line
|
||||
# Tools -- `command -v` finds them regardless. The developer directory is
|
||||
# what says whether real tools sit behind them.
|
||||
_pmn_clt_installed() {
|
||||
local dir
|
||||
dir="$(xcode-select -p 2>/dev/null)" || return 1
|
||||
[ -x "$dir/usr/bin/clang" ]
|
||||
}
|
||||
|
||||
# _pmn_tool_status <id>: prints "ok <detail>", "old <detail>" or "missing".
|
||||
_pmn_tool_status() {
|
||||
local v p
|
||||
case "$1" in
|
||||
clt)
|
||||
if _pmn_clt_installed; then echo "ok $(xcode-select -p)"; else echo missing; fi ;;
|
||||
brew)
|
||||
if _pmn_have brew; then echo "ok $(command -v brew)"; else echo missing; fi ;;
|
||||
git)
|
||||
if _pmn_have git; then echo "ok $(git --version | sed 's/^git version //')"; else echo missing; fi ;;
|
||||
cmake)
|
||||
v="$(_pmn_cmake_version)"
|
||||
if [ -z "$v" ]; then echo missing
|
||||
elif _pmn_cmake_new_enough; then echo "ok $v"
|
||||
else echo "old $v"; fi ;;
|
||||
cxx)
|
||||
if _pmn_have g++; then echo "ok g++ $(g++ -dumpversion 2>/dev/null)"
|
||||
elif _pmn_have clang++; then echo "ok clang++"
|
||||
elif _pmn_have c++; then echo "ok c++"
|
||||
else echo missing; fi ;;
|
||||
clang)
|
||||
if _pmn_have clang++; then echo "ok $(clang++ --version 2>/dev/null | sed -n '1s/.*version \([0-9][0-9.]*\).*/\1/p')"; else echo missing; fi ;;
|
||||
clang-format|clang-tidy)
|
||||
if _pmn_have "$1"; then echo "ok $(command -v "$1")"
|
||||
elif p="$(_pmn_brew_llvm_bin)" && [ -x "$p/$1" ]; then echo "ok $p/$1"
|
||||
else echo missing; fi ;;
|
||||
xc-arm64) if _pmn_have aarch64-linux-gnu-g++; then echo "ok"; else echo missing; fi ;;
|
||||
xc-arm32) if _pmn_have arm-linux-gnueabihf-g++; then echo "ok"; else echo missing; fi ;;
|
||||
xc-x64) if _pmn_have x86_64-linux-gnu-g++; then echo "ok"; else echo missing; fi ;;
|
||||
mingw64) if _pmn_have x86_64-w64-mingw32-g++; then echo "ok"; else echo missing; fi ;;
|
||||
mingw32) if _pmn_have i686-w64-mingw32-g++; then echo "ok"; else echo missing; fi ;;
|
||||
qemu) if _pmn_have qemu-aarch64 || _pmn_have qemu-arm || _pmn_have qemu-x86_64; then echo "ok"; else echo missing; fi ;;
|
||||
wine) if _pmn_have wine || _pmn_have wine64; then echo "ok"; else echo missing; fi ;;
|
||||
ninja) if _pmn_have ninja; then echo "ok $(ninja --version 2>/dev/null)"; else echo missing; fi ;;
|
||||
*) echo missing ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The catalog
|
||||
#
|
||||
# id | kind | label | why | apt | dnf | pacman | brew
|
||||
#
|
||||
# "-" means "not offered through that package manager". Rows are filtered per
|
||||
# platform by _pmn_tools_rows below, and listed in the order they're asked.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_pmn_row_cat() {
|
||||
case "$1" in
|
||||
git) echo "git|required|Git|FetchContent clones dependencies (fmt) with it|git|git|git|git" ;;
|
||||
cmake) echo "cmake|required|CMake $PMN_CMAKE_MIN_MAJOR.$PMN_CMAKE_MIN_MINOR or newer|configures and drives every build|cmake|cmake|cmake|cmake" ;;
|
||||
cxx) echo "cxx|required|C++ compiler (GCC)|compiles the project|g++|gcc-c++|gcc|-" ;;
|
||||
clang) echo "clang|optional|Clang|the linux-*-clang presets (GCC alone is enough otherwise)|clang|clang|clang|-" ;;
|
||||
clang-format) echo "clang-format|optional|clang-format|scripts/refactor.sh and the pre-commit hook|clang-format|clang-tools-extra|clang|clang-format" ;;
|
||||
clang-tidy) echo "clang-tidy|optional|clang-tidy|scripts/refactor.sh and the pre-commit hook|clang-tidy|clang-tools-extra|clang|llvm" ;;
|
||||
xc-arm64) echo "xc-arm64|optional|AArch64 cross compiler|the linux-arm64-* presets, built from this machine|g++-aarch64-linux-gnu|-|-|-" ;;
|
||||
xc-arm32) echo "xc-arm32|optional|32-bit ARM cross compiler|the linux-arm32-* presets, built from this machine|g++-arm-linux-gnueabihf|-|-|-" ;;
|
||||
xc-x64) echo "xc-x64|optional|x86-64 cross compiler|the linux-x64-* presets, built from this machine|g++-x86-64-linux-gnu|-|-|-" ;;
|
||||
mingw64) echo "mingw64|optional|MinGW-w64 (64-bit Windows)|the windows-x64-mingw64 preset, cross-built from Linux|g++-mingw-w64-x86-64|-|-|-" ;;
|
||||
mingw32) echo "mingw32|optional|MinGW-w64 (32-bit Windows)|the windows-x86-mingw32 preset, cross-built from Linux|g++-mingw-w64-i686|-|-|-" ;;
|
||||
qemu) echo "qemu|optional|qemu-user|runs ARM cross builds (scripts/run.sh, ctest)|qemu-user|-|-|-" ;;
|
||||
wine) echo "wine|optional|wine|runs MinGW cross builds (scripts/run.sh, ctest)|wine|-|-|-" ;;
|
||||
ninja) echo "ninja|required|Ninja|the build generator (no pinned download exists for this machine)|ninja-build|ninja-build|ninja|ninja" ;;
|
||||
clt) echo "clt|required|Xcode Command Line Tools|Apple Clang, git and make: the compiler for the macos-* presets|-|-|-|-" ;;
|
||||
brew) echo "brew|required|Homebrew|installs CMake and the other tools below|-|-|-|-" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# The ids to consider on this platform, in the order they are asked.
|
||||
_pmn_tools_ids() {
|
||||
if [ "$PMN_OS" = macos ]; then
|
||||
echo "clt brew cmake clang-format clang-tidy"
|
||||
return
|
||||
fi
|
||||
local ids="git cmake cxx clang clang-format clang-tidy"
|
||||
# Cross toolchains: apt only (see the file header), and only for
|
||||
# architectures this machine can build FOR without being them.
|
||||
if [ "$PMN_PM" = apt ]; then
|
||||
case "$PMN_ARCH" in
|
||||
x64) ids="$ids xc-arm64 xc-arm32 mingw64 mingw32 qemu wine" ;;
|
||||
arm64) ids="$ids xc-x64 xc-arm32 mingw64 mingw32 qemu" ;;
|
||||
esac
|
||||
fi
|
||||
echo "$ids"
|
||||
}
|
||||
|
||||
# Field <n> (1-based) of a catalog row. A `read` into named variables rather
|
||||
# than word-splitting the row: the rows contain "*" (linux-*-clang), and an
|
||||
# unquoted expansion would be subject to pathname globbing.
|
||||
_pmn_field() {
|
||||
local f1 f2 f3 f4 f5 f6 f7 f8
|
||||
IFS='|' read -r f1 f2 f3 f4 f5 f6 f7 f8 <<< "$1"
|
||||
case "$2" in
|
||||
1) printf '%s\n' "$f1" ;; 2) printf '%s\n' "$f2" ;; 3) printf '%s\n' "$f3" ;;
|
||||
4) printf '%s\n' "$f4" ;; 5) printf '%s\n' "$f5" ;; 6) printf '%s\n' "$f6" ;;
|
||||
7) printf '%s\n' "$f7" ;; 8) printf '%s\n' "$f8" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# The package(s) that provide a row's tool through the current package
|
||||
# manager, or "" if it isn't offered there.
|
||||
_pmn_row_packages() {
|
||||
local row="$1" col
|
||||
case "$PMN_PM" in
|
||||
apt) col=5 ;; dnf) col=6 ;; pacman) col=7 ;; brew) col=8 ;; *) return 0 ;;
|
||||
esac
|
||||
local p
|
||||
p="$(_pmn_field "$row" "$col")"
|
||||
[ "$p" = "-" ] && p=""
|
||||
printf '%s\n' "$p"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Asking
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_pmn_say() { printf '%s\n' "$*"; }
|
||||
_pmn_note() { printf '%s\n' "${_PMN_DIM}$*${_PMN_RESET}"; }
|
||||
|
||||
# Would an OPTIONAL tool be offered (asked about, or installed by --all) at
|
||||
# this point? If not, the caller skips it silently and mentions it once at
|
||||
# the end, instead of printing a "left alone" block for each.
|
||||
_pmn_optional_wanted() {
|
||||
if [ "$_PMN_ANS_QUIT" = 1 ] || [ "$_PMN_ANS_SKIP_OPT" = 1 ]; then return 1; fi
|
||||
if [ "$PMN_TOOLS_ALL" = 1 ] || [ "$_PMN_ANS_ALL" = 1 ]; then return 0; fi
|
||||
[ -t 0 ]
|
||||
}
|
||||
|
||||
# _pmn_confirm <required|optional> <question>
|
||||
#
|
||||
# Returns 0 for "install it", 1 for "leave it". Sets _PMN_ANS_* for answers
|
||||
# that outlast one prompt (see their comments up top).
|
||||
_pmn_confirm() {
|
||||
local kind="$1" question="$2" prompt ans
|
||||
|
||||
if [ "$_PMN_ANS_QUIT" = 1 ]; then return 1; fi
|
||||
if [ "$_PMN_ANS_ALL" = 1 ]; then return 0; fi
|
||||
if [ "$kind" = optional ] && [ "$_PMN_ANS_SKIP_OPT" = 1 ]; then return 1; fi
|
||||
|
||||
# Flags answer for you...
|
||||
if [ "$kind" = required ] && [ "$PMN_TOOLS_YES" = 1 ]; then return 0; fi
|
||||
if [ "$PMN_TOOLS_ALL" = 1 ]; then return 0; fi
|
||||
|
||||
# ...and with no terminal there is nobody to ask.
|
||||
if [ ! -t 0 ]; then
|
||||
_pmn_note " (not asking: no terminal. Re-run interactively, or pass --yes / --all.)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$kind" = required ]; then
|
||||
prompt=" ${question} [Y/n/a/q] "
|
||||
else
|
||||
prompt=" ${question} [y/N/a/s/q] "
|
||||
fi
|
||||
while :; do
|
||||
if ! IFS= read -r -p "$prompt" ans; then
|
||||
# EOF (Ctrl-D): stop asking rather than loop on it.
|
||||
echo
|
||||
_PMN_ANS_QUIT=1
|
||||
return 1
|
||||
fi
|
||||
case "$ans" in
|
||||
y|Y|yes|YES|Yes) return 0 ;;
|
||||
n|N|no|NO|No) return 1 ;;
|
||||
"") if [ "$kind" = required ]; then return 0; else return 1; fi ;;
|
||||
a|A|all) _PMN_ANS_ALL=1; return 0 ;;
|
||||
s|S|skip) _PMN_ANS_SKIP_OPT=1; return 1 ;;
|
||||
q|Q|quit) _PMN_ANS_QUIT=1; return 1 ;;
|
||||
*)
|
||||
if [ "$kind" = required ]; then
|
||||
_pmn_note " y = install n = not this one a = install everything left q = stop asking"
|
||||
else
|
||||
_pmn_note " y = install n = not this one a = install everything left s = skip the rest of the optional tools q = stop asking"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Installing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# _pmn_run_shown <command...>: print the command, then run it.
|
||||
_pmn_run_shown() {
|
||||
printf ' %s$ %s%s\n' "${_PMN_DIM}" "$*" "${_PMN_RESET}"
|
||||
"$@"
|
||||
}
|
||||
|
||||
# Sudo, when the package manager needs it. A machine that is neither root
|
||||
# nor has sudo can't install system packages; say so once, plainly.
|
||||
_pmn_pkg_install() {
|
||||
local pkgs="$1" rc=0
|
||||
if [ "$PMN_SUDO" = "?" ] && [ "$PMN_PM" != brew ]; then
|
||||
_pmn_say " ${_PMN_RED}cannot install:${_PMN_RESET} this needs root, and sudo isn't available."
|
||||
_pmn_say " Run this script as root, or install by hand: $pkgs"
|
||||
return 1
|
||||
fi
|
||||
# shellcheck disable=SC2086 # $pkgs is a deliberate word list
|
||||
case "$PMN_PM" in
|
||||
apt)
|
||||
if [ "$_PMN_APT_UPDATED" = 0 ]; then
|
||||
_pmn_run_shown ${PMN_SUDO:+"$PMN_SUDO"} apt-get update || true
|
||||
_PMN_APT_UPDATED=1
|
||||
fi
|
||||
_pmn_run_shown ${PMN_SUDO:+"$PMN_SUDO"} apt-get install -y $pkgs || rc=$?
|
||||
;;
|
||||
dnf)
|
||||
_pmn_run_shown ${PMN_SUDO:+"$PMN_SUDO"} dnf install -y $pkgs || rc=$?
|
||||
;;
|
||||
pacman)
|
||||
_pmn_run_shown ${PMN_SUDO:+"$PMN_SUDO"} pacman -S --needed --noconfirm $pkgs || rc=$?
|
||||
;;
|
||||
brew)
|
||||
_pmn_run_shown brew install $pkgs || rc=$?
|
||||
;;
|
||||
*)
|
||||
return 1 ;;
|
||||
esac
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
# macOS: `xcode-select --install` returns immediately after opening a GUI
|
||||
# installer, so wait for the developer directory to appear. Enter re-checks
|
||||
# right away; otherwise it looks every 5 seconds.
|
||||
_pmn_install_clt() {
|
||||
_pmn_run_shown xcode-select --install || true
|
||||
_pmn_say " A dialog should have opened: click Install and wait for it to finish."
|
||||
_pmn_say " Waiting for the tools (Enter checks now; Ctrl-C to stop waiting) ..."
|
||||
local waited=0
|
||||
until _pmn_clt_installed; do
|
||||
# Not `read -t 5` with stdin at EOF: that returns at once and would
|
||||
# spin. sleep is the wait; read only makes Enter an early re-check.
|
||||
if [ -t 0 ]; then
|
||||
IFS= read -r -t 5 _ 2>/dev/null || true
|
||||
else
|
||||
sleep 5
|
||||
fi
|
||||
waited=$((waited + 5))
|
||||
if [ "$waited" -ge 3600 ]; then
|
||||
_pmn_say " Gave up waiting after an hour -- re-run this script once the install has finished."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# Homebrew's own documented installer. It asks for your password and for a
|
||||
# RETURN of its own -- those prompts are Homebrew's, and are left alone
|
||||
# unless --yes was given (then NONINTERACTIVE=1 tells it not to ask).
|
||||
_pmn_install_brew() {
|
||||
if ! _pmn_have curl; then
|
||||
_pmn_say " curl not found -- see https://brew.sh for manual installation steps."
|
||||
return 1
|
||||
fi
|
||||
printf ' %s$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"%s\n' "${_PMN_DIM}" "${_PMN_RESET}"
|
||||
local rc=0
|
||||
if [ "$PMN_TOOLS_YES" = 1 ] || [ "$PMN_TOOLS_ALL" = 1 ]; then
|
||||
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || rc=$?
|
||||
else
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || rc=$?
|
||||
fi
|
||||
# A fresh install isn't on PATH in this shell yet.
|
||||
local b
|
||||
for b in /opt/homebrew/bin/brew /usr/local/bin/brew; do
|
||||
if [ -x "$b" ]; then eval "$("$b" shellenv)"; PMN_PM=brew; break; fi
|
||||
done
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
# _pmn_install_row <row>: install one catalog row; returns its status.
|
||||
_pmn_install_row() {
|
||||
local row="$1" id pkgs
|
||||
id="$(_pmn_field "$row" 1)"
|
||||
case "$id" in
|
||||
clt) _pmn_install_clt; return $? ;;
|
||||
brew) _pmn_install_brew; return $? ;;
|
||||
esac
|
||||
pkgs="$(_pmn_row_packages "$row")"
|
||||
if [ -z "$pkgs" ]; then
|
||||
_pmn_say " ${_PMN_YELLOW}no package known for this on ${PMN_PM}${_PMN_RESET} -- install it yourself."
|
||||
return 1
|
||||
fi
|
||||
_pmn_pkg_install "$pkgs"
|
||||
}
|
||||
|
||||
# What to tell someone whose package manager's CMake is too old (Ubuntu 22.04
|
||||
# ships 3.22, Debian 11 ships 3.18). Not run automatically: which of these is
|
||||
# right depends on the machine, and each one changes more than a package.
|
||||
_pmn_cmake_too_old_help() {
|
||||
_pmn_say " ${_PMN_YELLOW}CMake $1 is older than the ${PMN_CMAKE_MIN_MAJOR}.${PMN_CMAKE_MIN_MINOR} this project needs.${_PMN_RESET} Any one of:"
|
||||
_pmn_say " pipx install cmake # Kitware's own wheels (pipx from your package manager)"
|
||||
_pmn_say " sudo snap install cmake --classic # if you use snap"
|
||||
_pmn_say " https://cmake.org/download/ # official binary tarball"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Rows are collected into an array first, then looped over -- rather than
|
||||
# reading a here-doc in the loop -- so a command run inside the loop (a
|
||||
# package manager, sudo) can never swallow the rest of the list from stdin.
|
||||
_pmn_tools_run() {
|
||||
local rows=() id row kind label why status detail pkgs missing_required=0
|
||||
local failed=""
|
||||
local pm_desc
|
||||
|
||||
_pmn_tools_detect
|
||||
|
||||
case "$PMN_OS" in
|
||||
linux) pm_desc="Linux ${PMN_ARCH:-?}, package manager: ${PMN_PM}" ;;
|
||||
macos) pm_desc="macOS ${PMN_ARCH:-?}, package manager: ${PMN_PM}" ;;
|
||||
*) pm_desc="$(uname -s) (not supported by this installer)" ;;
|
||||
esac
|
||||
_pmn_say "==> Checking the tools this project builds with (${pm_desc})"
|
||||
|
||||
if [ "$PMN_OS" = other ]; then
|
||||
_pmn_say " This installer knows Linux and macOS; on Windows use scripts\\setup.bat."
|
||||
return 0
|
||||
fi
|
||||
if [ "$PMN_OS" = linux ] && [ "$PMN_PM" = none ]; then
|
||||
_pmn_say " ${_PMN_YELLOW}No supported package manager found${_PMN_RESET} (apt, dnf and pacman are)."
|
||||
_pmn_say " Install these yourself: a C++ compiler, git, CMake ${PMN_CMAKE_MIN_MAJOR}.${PMN_CMAKE_MIN_MINOR}+ (and clang-format/clang-tidy, optionally)."
|
||||
fi
|
||||
|
||||
for id in $(_pmn_tools_ids); do
|
||||
row="$(_pmn_row_cat "$id")"
|
||||
[ -n "$row" ] && rows+=("$row")
|
||||
done
|
||||
|
||||
# ---- report ----------------------------------------------------------
|
||||
for row in ${rows[@]+"${rows[@]}"}; do
|
||||
id="$(_pmn_field "$row" 1)"; kind="$(_pmn_field "$row" 2)"; label="$(_pmn_field "$row" 3)"
|
||||
status="$(_pmn_tool_status "$id")"
|
||||
detail="${status#* }"; [ "$detail" = "$status" ] && detail=""
|
||||
case "${status%% *}" in
|
||||
ok)
|
||||
if [ -n "$detail" ]; then
|
||||
printf ' %s[ ok ]%s %-30s %s%s%s\n' "$_PMN_GREEN" "$_PMN_RESET" "$label" "$_PMN_DIM" "$detail" "$_PMN_RESET"
|
||||
else
|
||||
printf ' %s[ ok ]%s %s\n' "$_PMN_GREEN" "$_PMN_RESET" "$label"
|
||||
fi ;;
|
||||
old)
|
||||
printf ' %s[old ]%s %-30s %s (needs %s.%s+)\n' "$_PMN_YELLOW" "$_PMN_RESET" "$label" "$detail" "$PMN_CMAKE_MIN_MAJOR" "$PMN_CMAKE_MIN_MINOR" ;;
|
||||
*)
|
||||
if [ "$kind" = required ]; then
|
||||
printf ' %s[ !! ]%s %-30s missing (required)\n' "$_PMN_RED" "$_PMN_RESET" "$label"
|
||||
else
|
||||
printf ' %s[ -- ]%s %-30s missing (optional)\n' "$_PMN_DIM" "$_PMN_RESET" "$label"
|
||||
fi ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$PMN_TOOLS_CHECK" = 1 ]; then
|
||||
for row in ${rows[@]+"${rows[@]}"}; do
|
||||
id="$(_pmn_field "$row" 1)"
|
||||
if [ "$(_pmn_field "$row" 2)" = required ] && [ "$(_pmn_tool_status "$id" | cut -d' ' -f1)" != ok ]; then
|
||||
# brew is only "required" for what it installs; not a failure on its own if nothing needs it
|
||||
[ "$id" = brew ] && continue
|
||||
missing_required=1
|
||||
fi
|
||||
done
|
||||
return "$missing_required"
|
||||
fi
|
||||
|
||||
# ---- offer installs, one at a time -----------------------------------
|
||||
local offered=0 skipped_optional=""
|
||||
for row in ${rows[@]+"${rows[@]}"}; do
|
||||
id="$(_pmn_field "$row" 1)"; kind="$(_pmn_field "$row" 2)"; label="$(_pmn_field "$row" 3)"
|
||||
why="$(_pmn_field "$row" 4)"
|
||||
|
||||
# Re-check now: an earlier install may already have provided this
|
||||
# one (dnf's clang-tools-extra is both clang-format and clang-tidy).
|
||||
status="$(_pmn_tool_status "$id")"
|
||||
if [ "${status%% *}" = ok ]; then continue; fi
|
||||
|
||||
if [ "$_PMN_ANS_QUIT" = 1 ]; then break; fi
|
||||
|
||||
# Everything through Homebrew needs Homebrew first.
|
||||
if [ "$PMN_OS" = macos ] && [ "$id" != clt ] && [ "$id" != brew ] && [ "$PMN_PM" != brew ]; then
|
||||
_pmn_say
|
||||
_pmn_say " ${_PMN_BOLD}${label}${_PMN_RESET} -- ${why}"
|
||||
_pmn_note " skipped: needs Homebrew, which isn't installed"
|
||||
failed="$failed $id"
|
||||
continue
|
||||
fi
|
||||
if [ "$PMN_OS" = linux ] && [ "$id" != brew ]; then
|
||||
pkgs="$(_pmn_row_packages "$row")"
|
||||
if [ "$PMN_PM" = none ] || [ -z "$pkgs" ]; then
|
||||
continue # nothing this package manager can do; already said so above
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$kind" = optional ] && ! _pmn_optional_wanted; then
|
||||
skipped_optional="${skipped_optional:+$skipped_optional, }$label"
|
||||
continue
|
||||
fi
|
||||
|
||||
offered=$((offered + 1))
|
||||
_pmn_say
|
||||
_pmn_say " ${_PMN_BOLD}${label}${_PMN_RESET} -- ${why}"
|
||||
if [ "${status%% *}" = old ]; then
|
||||
_pmn_note " (installed: ${status#* }, but too old)"
|
||||
fi
|
||||
|
||||
if _pmn_confirm "$kind" "Install ${label}?"; then
|
||||
if _pmn_install_row "$row"; then
|
||||
status="$(_pmn_tool_status "$id")"
|
||||
case "${status%% *}" in
|
||||
ok) _pmn_say " ${_PMN_GREEN}installed${_PMN_RESET}" ;;
|
||||
old) _pmn_cmake_too_old_help "${status#* }"; failed="$failed $id" ;;
|
||||
*) _pmn_say " ${_PMN_YELLOW}the install finished, but ${label} still isn't on PATH${_PMN_RESET} -- open a new terminal, or check the output above."
|
||||
failed="$failed $id" ;;
|
||||
esac
|
||||
else
|
||||
_pmn_say " ${_PMN_RED}install failed${_PMN_RESET} -- see the output above."
|
||||
failed="$failed $id"
|
||||
fi
|
||||
else
|
||||
# Declined (or quit): nothing to echo back to someone who just
|
||||
# typed it, but an optional one belongs in the closing summary.
|
||||
if [ "$kind" = optional ]; then
|
||||
skipped_optional="${skipped_optional:+$skipped_optional, }$label"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ "$offered" -eq 0 ] && [ -z "$skipped_optional" ] && [ "$_PMN_ANS_QUIT" != 1 ]; then
|
||||
_pmn_say " Nothing to install."
|
||||
fi
|
||||
if [ -n "$skipped_optional" ]; then
|
||||
_pmn_say
|
||||
_pmn_note " Optional, not installed: ${skipped_optional}"
|
||||
_pmn_note " (scripts/setup.sh, run interactively, offers each one; --all installs them all.)"
|
||||
fi
|
||||
|
||||
# ---- final verdict ---------------------------------------------------
|
||||
for row in ${rows[@]+"${rows[@]}"}; do
|
||||
id="$(_pmn_field "$row" 1)"
|
||||
if [ "$(_pmn_field "$row" 2)" = required ] && [ "$id" != brew ] \
|
||||
&& [ "$(_pmn_tool_status "$id" | cut -d' ' -f1)" != ok ]; then
|
||||
missing_required=1
|
||||
_pmn_say " ${_PMN_RED}still missing (required):${_PMN_RESET} $(_pmn_field "$row" 3)"
|
||||
fi
|
||||
done
|
||||
return "$missing_required"
|
||||
}
|
||||
|
||||
# _pmn_tools_offer <id>: offer to install ONE catalog tool -- for callers that
|
||||
# find out mid-way that they need it (setup.sh, when there is no pinned Ninja
|
||||
# for this machine). Same prompt, same command display, same rules.
|
||||
_pmn_tools_offer() {
|
||||
local id="$1" row status
|
||||
_pmn_tools_detect
|
||||
row="$(_pmn_row_cat "$id")"
|
||||
[ -n "$row" ] || return 1
|
||||
status="$(_pmn_tool_status "$id")"
|
||||
[ "${status%% *}" = ok ] && return 0
|
||||
if [ -z "$(_pmn_row_packages "$row")" ] || [ "$PMN_PM" = none ]; then
|
||||
_pmn_say " Install $(_pmn_field "$row" 3) yourself: $(_pmn_field "$row" 4)."
|
||||
return 1
|
||||
fi
|
||||
_pmn_say
|
||||
_pmn_say " ${_PMN_BOLD}$(_pmn_field "$row" 3)${_PMN_RESET} -- $(_pmn_field "$row" 4)"
|
||||
if _pmn_confirm "$(_pmn_field "$row" 2)" "Install $(_pmn_field "$row" 3)?"; then
|
||||
if ! _pmn_install_row "$row"; then
|
||||
_pmn_say " ${_PMN_RED}install failed${_PMN_RESET} -- see the output above."
|
||||
return 1
|
||||
fi
|
||||
if [ "$(_pmn_tool_status "$id" | cut -d' ' -f1)" = ok ]; then
|
||||
_pmn_say " ${_PMN_GREEN}installed${_PMN_RESET}"
|
||||
return 0
|
||||
fi
|
||||
_pmn_say " ${_PMN_YELLOW}the install finished, but $(_pmn_field "$row" 3) still isn't on PATH${_PMN_RESET} -- open a new terminal, or check the output above."
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
Executable
+358
@@ -0,0 +1,358 @@
|
||||
@echo off
|
||||
REM Configure (if needed) and build the project, preferably with Ninja.
|
||||
REM
|
||||
REM Usage (from the project root):
|
||||
REM scripts\build.bat [--preset ^<name^>] [BUILD_TYPE] [CMAKE_ARGS...]
|
||||
REM scripts\build.bat --list-presets
|
||||
REM scripts\build.bat --help
|
||||
REM
|
||||
REM --preset <name> Use a CMakePresets.json preset. A preset selects that
|
||||
REM target's toolchain file (cmake\toolchains\<name>.cmake),
|
||||
REM which picks the compiler and declares the DIST_TARGET
|
||||
REM together, instead of you having to pass a matching
|
||||
REM pair by hand:
|
||||
REM scripts\build.bat --preset windows-x64-mingw64
|
||||
REM Each preset builds in its own directory,
|
||||
REM out\build\<name>, so switching between presets never
|
||||
REM invalidates -- or deletes -- another one's build.
|
||||
REM Run --list-presets to see every preset for your OS.
|
||||
REM Must come first if given at all; --preset=<name>
|
||||
REM also works. Given with no value (or a value that
|
||||
REM looks like another flag), this fails fast with that
|
||||
REM same preset list shown right there. A value that IS
|
||||
REM given but isn't a real preset name is instead
|
||||
REM caught by cmake itself failing to configure, at
|
||||
REM which point this script shows the preset list too
|
||||
REM -- see the "configure" step below.
|
||||
REM BUILD_TYPE Debug^|Release^|RelWithDebInfo^|MinSizeRel
|
||||
REM (default: Configuration.cmake's DEFAULT_BUILD_TYPE)
|
||||
REM CMAKE_ARGS Anything else is forwarded to `cmake` verbatim, e.g.:
|
||||
REM scripts\build.bat --preset windows-x64-mingw64 Debug -DBUILD_TESTS=ON
|
||||
REM scripts\build.bat -DBUILD_TESTS=ON
|
||||
REM (BUILD_TYPE omitted -- recognized by not
|
||||
REM starting with "-")
|
||||
REM
|
||||
REM Picking a DIST_TARGET: three ways; the first two set the compiler and the
|
||||
REM label together, and this script rejects mixing --preset with a
|
||||
REM -DDIST_TARGET rather than leaving it ambiguous which one wins.
|
||||
REM 1. --preset <name> The recommended way; see --list-presets.
|
||||
REM 2. -DCMAKE_TOOLCHAIN_FILE=cmake\toolchains\<name>.cmake
|
||||
REM (no --preset) What a preset does, by hand, as one of
|
||||
REM CMAKE_ARGS above.
|
||||
REM 3. -DDIST_TARGET=<value> Sets the label alone, as one of CMAKE_ARGS
|
||||
REM (no --preset) above. Without also passing a matching
|
||||
REM compiler (or, better, the toolchain file
|
||||
REM above), DIST_TARGET is auto-detected from
|
||||
REM whatever compiler CMake finds on its own; if
|
||||
REM the two disagree, Configuration.cmake
|
||||
REM FATAL_ERRORs rather than silently building
|
||||
REM the wrong toolchain and mislabeling the
|
||||
REM result -- see that file and the README.
|
||||
REM Passing --preset together with an explicit -DDIST_TARGET=... in
|
||||
REM CMAKE_ARGS is rejected by this script before cmake even runs: the
|
||||
REM preset's toolchain already declares DIST_TARGET, so a second value
|
||||
REM alongside it is always either redundant or contradictory -- never
|
||||
REM useful.
|
||||
REM
|
||||
REM cmd.exe splits %1, %2, ... (and SHIFT) on space, comma, semicolon, AND
|
||||
REM "=" -- type "-DFOO=BAR" as an argument and it arrives as TWO separate
|
||||
REM parameters, %1=-DFOO %2=BAR, silently, with no way to tell after the
|
||||
REM fact. This script never reads a "="-laden argument (or --preset's own
|
||||
REM value) through %1 or SHIFT; everything is peeled off of the raw,
|
||||
REM unsplit %* instead using FOR /F (which only ever splits on space/tab),
|
||||
REM so "=" survives intact -- see the parsing block below. (bash's
|
||||
REM "$@"/shift has no such bug -- see build.sh for that half of this same
|
||||
REM feature.)
|
||||
REM
|
||||
REM Build directory: out\build\<preset> for --preset <preset>; without a
|
||||
REM preset, out\build\default (the compiler is auto-detected there, so keep
|
||||
REM to one compiler per directory or use presets). Set BUILD_DIR to use an
|
||||
REM exact directory of your own instead. scripts\run.bat, install.bat and
|
||||
REM docs.bat use the directory built last, or --preset <name> to pick another.
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM -h/--help is checked here, off the raw %1, before anything else --
|
||||
REM including whether cmake is even on PATH below -- since help shouldn't
|
||||
REM depend on that.
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
goto after_help
|
||||
|
||||
:show_help
|
||||
echo Usage: scripts\build.bat [--preset ^<name^>] [BUILD_TYPE] [CMAKE_ARGS...]
|
||||
echo scripts\build.bat --list-presets
|
||||
echo scripts\build.bat --help
|
||||
echo.
|
||||
echo --preset ^<name^> Use a CMakePresets.json preset: it selects the target's
|
||||
echo toolchain file ^(compiler and DIST_TARGET together^) and
|
||||
echo builds in its own directory, out\build\^<name^>. Must come
|
||||
echo first if given at all; --preset=^<name^> also works. Run
|
||||
echo --list-presets to see every preset for your OS.
|
||||
echo BUILD_TYPE Debug^|Release^|RelWithDebInfo^|MinSizeRel
|
||||
echo ^(default: Configuration.cmake's DEFAULT_BUILD_TYPE^)
|
||||
echo CMAKE_ARGS Anything else is forwarded to `cmake` verbatim, e.g.
|
||||
echo -DDIST_TARGET=^<value^> or -DBUILD_TESTS=ON.
|
||||
echo.
|
||||
echo Don't combine --preset with an explicit -DDIST_TARGET=... in CMAKE_ARGS --
|
||||
echo the preset's toolchain already declares one; see the comment at the top of
|
||||
echo this script ^(scripts\build.bat^) and Configuration.cmake's "Target selection"
|
||||
echo section for why picking a DIST_TARGET is one or the other, not both.
|
||||
echo.
|
||||
echo Examples:
|
||||
echo scripts\build.bat --preset linux-x64-clang
|
||||
echo scripts\build.bat --preset windows-x64-mingw64 Debug -DBUILD_TESTS=ON
|
||||
echo scripts\build.bat -DBUILD_TESTS=ON
|
||||
exit /b 0
|
||||
|
||||
:after_help
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM An explicit BUILD_DIR from the environment beats the one-directory-per-
|
||||
REM preset layout resolved below, and is then also not recorded as "the
|
||||
REM build made last" (run/install/docs would otherwise be sent to a
|
||||
REM directory the layout doesn't know about).
|
||||
set "_pmn_record_build=1"
|
||||
if not "%BUILD_DIR%"=="" set "_pmn_record_build=0"
|
||||
|
||||
where cmake >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo error: cmake not found on PATH.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM See the header comment above for why this doesn't use %1/SHIFT.
|
||||
set "ALL_ARGS=%*"
|
||||
set "PRESET="
|
||||
set "BUILD_TYPE="
|
||||
set "EXTRA_ARGS="
|
||||
if not defined ALL_ARGS goto args_done
|
||||
|
||||
for /f "tokens=1,* delims= " %%A in ("%ALL_ARGS%") do (
|
||||
set "_FIRST=%%A"
|
||||
set "_REST=%%B"
|
||||
)
|
||||
|
||||
if /i "%_FIRST%"=="--list-presets" (
|
||||
cmake --list-presets
|
||||
exit /b !ERRORLEVEL!
|
||||
)
|
||||
if /i "%_FIRST%"=="--preset" goto preset_space_form
|
||||
if /i "%_FIRST:~0,9%"=="--preset=" goto preset_equals_form
|
||||
goto no_preset
|
||||
|
||||
:preset_space_form
|
||||
if not defined _REST (
|
||||
echo error: --preset requires a value, e.g. --preset windows-x64-mingw64
|
||||
echo.
|
||||
cmake --list-presets
|
||||
exit /b 1
|
||||
)
|
||||
for /f "tokens=1,* delims= " %%A in ("%_REST%") do (
|
||||
set "PRESET=%%A"
|
||||
set "_REST=%%B"
|
||||
)
|
||||
if "%PRESET:~0,1%"=="-" (
|
||||
echo error: --preset requires a value, e.g. --preset windows-x64-mingw64
|
||||
echo.
|
||||
cmake --list-presets
|
||||
exit /b 1
|
||||
)
|
||||
goto have_preset
|
||||
|
||||
:preset_equals_form
|
||||
set "PRESET=%_FIRST:~9%"
|
||||
if not defined PRESET (
|
||||
echo error: --preset= requires a value, e.g. --preset=windows-x64-mingw64
|
||||
echo.
|
||||
cmake --list-presets
|
||||
exit /b 1
|
||||
)
|
||||
goto have_preset
|
||||
|
||||
:have_preset
|
||||
set "ALL_ARGS=%_REST%"
|
||||
if not defined ALL_ARGS goto args_done
|
||||
for /f "tokens=1,* delims= " %%A in ("%ALL_ARGS%") do (
|
||||
set "_FIRST=%%A"
|
||||
set "_REST=%%B"
|
||||
)
|
||||
|
||||
:no_preset
|
||||
if "%_FIRST:~0,1%"=="-" (
|
||||
set "EXTRA_ARGS=%ALL_ARGS%"
|
||||
) else (
|
||||
set "BUILD_TYPE=%_FIRST%"
|
||||
set "EXTRA_ARGS=%_REST%"
|
||||
)
|
||||
|
||||
:args_done
|
||||
if "%BUILD_TYPE%"=="" call :read_config DEFAULT_BUILD_TYPE BUILD_TYPE
|
||||
if "%TOOLCACHE_DIR%"=="" call :read_config TOOLCACHE_DIR TOOLCACHE_DIR
|
||||
|
||||
REM One build directory per preset (out\build\<preset>; "default" when no
|
||||
REM preset was given), so switching presets never has to reuse -- or wipe -- a
|
||||
REM directory configured for another compiler. cmake\BuildDir.cmake owns the
|
||||
REM layout (Configuration.cmake's BUILD_OUTPUT_DIR); goto rather than an
|
||||
REM if-block for the reason given further down about nesting.
|
||||
set "_pmn_name=default"
|
||||
if defined PRESET set "_pmn_name=%PRESET%"
|
||||
if not "%BUILD_DIR%"=="" goto build_dir_known
|
||||
call :resolve_build_dir "%_pmn_name%" BUILD_DIR
|
||||
if errorlevel 1 exit /b 1
|
||||
:build_dir_known
|
||||
|
||||
REM Combining --preset with an explicit -DDIST_TARGET=... in CMAKE_ARGS is
|
||||
REM ambiguous (the preset already sets one) -- caught here, before cmake
|
||||
REM ever runs, rather than leaving it to whichever of the two CMake
|
||||
REM happens to prefer. findstr's /C: does a literal (non-regex) substring
|
||||
REM search, which sidesteps cmd.exe's own "=" splitting quirk (see the
|
||||
REM header comment) entirely -- there's no argument-parsing involved here,
|
||||
REM just a plain string search over EXTRA_ARGS as already assembled.
|
||||
if defined PRESET (
|
||||
echo %EXTRA_ARGS%| findstr /C:"-DDIST_TARGET=" >nul
|
||||
if not errorlevel 1 (
|
||||
echo error: both --preset %PRESET% and an explicit -DDIST_TARGET=... were given.
|
||||
echo A preset's toolchain already declares DIST_TARGET ^(and picks the compiler^) -- pick one:
|
||||
echo scripts\build.bat --preset %PRESET%
|
||||
echo scripts\build.bat -DCMAKE_TOOLCHAIN_FILE=cmake\toolchains\^<target^>.cmake
|
||||
echo See the comment at the top of this script, or Configuration.cmake's
|
||||
echo "Target selection" section, for why these two aren't combined.
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Prefer a pinned ninja (fetched by scripts\setup.bat -- see
|
||||
REM cmake\FetchNinja.cmake) over a system one, same "always the pinned
|
||||
REM copy" preference as Doxygen. Needed whether or not --preset is used:
|
||||
REM every preset sets its generator to Ninja but doesn't hardcode which
|
||||
REM ninja binary to use (that would just be TOOLCACHE_DIR's default
|
||||
REM duplicated a third time -- see Configuration.cmake) --
|
||||
REM CMAKE_MAKE_PROGRAM supplies it either way; only -G Ninja itself is
|
||||
REM skipped below when a preset already sets the generator.
|
||||
REM
|
||||
REM Deliberately goto-based, and deliberately `pushd` + a bare wildcard
|
||||
REM rather than `for /d %%D in ("path\*")` -- both tested directly; a
|
||||
REM path-prefixed wildcard didn't match here, this form does, and it also
|
||||
REM sidesteps nesting a FOR loop inside an if/else block, which
|
||||
REM separately corrupts cmd.exe's paren-matching for the enclosing block
|
||||
REM (see scripts\refactor.bat for that one).
|
||||
set "NINJA_BIN="
|
||||
if not exist "%TOOLCACHE_DIR%" goto check_system_ninja
|
||||
|
||||
pushd "%TOOLCACHE_DIR%"
|
||||
for /d %%D in (ninja-*) do if exist "%%D\ninja.exe" if not defined NINJA_BIN set "NINJA_BIN=%%~fD\ninja.exe"
|
||||
popd
|
||||
|
||||
if defined NINJA_BIN goto have_pinned_ninja
|
||||
|
||||
:check_system_ninja
|
||||
where ninja >nul 2>nul
|
||||
if errorlevel 1 goto no_ninja
|
||||
echo ==^> Using system ninja
|
||||
if not defined PRESET set "GENERATOR_ARGS=-G Ninja"
|
||||
goto configure
|
||||
|
||||
:have_pinned_ninja
|
||||
echo ==^> Using pinned ninja: %NINJA_BIN%
|
||||
if defined PRESET (
|
||||
set "GENERATOR_ARGS=-DCMAKE_MAKE_PROGRAM=%NINJA_BIN%"
|
||||
) else (
|
||||
set "GENERATOR_ARGS=-G Ninja -DCMAKE_MAKE_PROGRAM=%NINJA_BIN%"
|
||||
)
|
||||
goto configure
|
||||
|
||||
:no_ninja
|
||||
if defined PRESET (
|
||||
echo warning: ninja not found ^(no pinned copy, none on PATH^) -- the '%PRESET%'
|
||||
echo preset requires it; configure will likely fail. Run
|
||||
echo scripts\setup.bat to fetch a pinned ninja, or install ninja
|
||||
echo yourself.
|
||||
) else (
|
||||
echo warning: ninja not found ^(no pinned copy, none on PATH^) -- falling back to
|
||||
echo CMake's default generator for this platform. Run scripts\setup.bat
|
||||
echo to fetch a pinned ninja, or install ninja yourself, for a faster
|
||||
echo and more consistent build. Continuing without it.
|
||||
)
|
||||
set "GENERATOR_ARGS="
|
||||
|
||||
:configure
|
||||
echo ==^> Configuring (%BUILD_TYPE%) into %BUILD_DIR%\
|
||||
if defined PRESET echo Preset: %PRESET%
|
||||
if defined EXTRA_ARGS echo Extra CMake arguments: %EXTRA_ARGS%
|
||||
if defined PRESET (
|
||||
cmake --preset %PRESET% -S . -B "%BUILD_DIR%" %GENERATOR_ARGS% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% %EXTRA_ARGS%
|
||||
) else (
|
||||
cmake %GENERATOR_ARGS% -S . -B "%BUILD_DIR%" -DCMAKE_BUILD_TYPE=%BUILD_TYPE% %EXTRA_ARGS%
|
||||
)
|
||||
if errorlevel 1 (
|
||||
if defined PRESET (
|
||||
echo.
|
||||
echo error: configuring with preset '%PRESET%' failed -- see the cmake output above.
|
||||
echo If '%PRESET%' isn't the preset name you meant, here is what --list-presets
|
||||
echo shows for this OS:
|
||||
echo.
|
||||
cmake --list-presets
|
||||
)
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Configure succeeded: this is now the build the other scripts default to.
|
||||
if "%_pmn_record_build%"=="1" cmake "-DPMN_NAME=%_pmn_name%" -DPMN_RECORD=ON -P cmake\BuildDir.cmake >nul
|
||||
|
||||
echo ==^> Building
|
||||
cmake --build "%BUILD_DIR%"
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo ==^> Done. Run a module with: scripts\run.bat ^<target-name^>
|
||||
exit /b 0
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :read_config <ConfigurationVarName> <LocalVarName>
|
||||
REM
|
||||
REM Reads one value out of Configuration.cmake -- this project's single
|
||||
REM source of truth -- via cmake\PrintConfig.cmake, instead of hardcoding
|
||||
REM a second copy of a default that can silently drift out of sync, and
|
||||
REM stores it into %2 in the CALLER's scope. See cmake\PrintConfig.cmake
|
||||
REM for exactly what's being stripped off of its output below.
|
||||
REM ---------------------------------------------------------------------
|
||||
:read_config
|
||||
setlocal
|
||||
set "_pmn_line="
|
||||
for /f "usebackq delims=" %%V in (`cmake "-DPRINT_VARS=%~1" -P cmake\PrintConfig.cmake`) do set "_pmn_line=%%V"
|
||||
for /f "tokens=1,* delims==" %%A in ("%_pmn_line%") do set "_pmn_value=%%B"
|
||||
endlocal & set "%~2=%_pmn_value%"
|
||||
goto :eof
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :resolve_build_dir <name-or-empty> <LocalVarName> [quiet]
|
||||
REM
|
||||
REM Asks cmake\BuildDir.cmake which build directory to use -- %1 is a preset
|
||||
REM name, or "default"; empty means "discover one" (the build made last, or
|
||||
REM the only one) -- and stores it into %2 in the CALLER's scope, with
|
||||
REM backslashes. Same shape as :read_config above, and the same
|
||||
REM `cmake -P` one-line-answer protocol: "-- BUILD_DIR=<path>" or
|
||||
REM "-- ERROR=<text>" (see that file). On failure it prints the reason to
|
||||
REM stderr, unless a third argument "quiet" is given, and returns errorlevel 1.
|
||||
REM ---------------------------------------------------------------------
|
||||
:resolve_build_dir
|
||||
setlocal
|
||||
set "_pmn_key="
|
||||
set "_pmn_val="
|
||||
for /f "usebackq tokens=1,* delims==" %%A in (`cmake "-DPMN_NAME=%~1" -P cmake\BuildDir.cmake`) do (
|
||||
set "_pmn_key=%%A"
|
||||
set "_pmn_val=%%B"
|
||||
)
|
||||
if "%_pmn_key%"=="-- BUILD_DIR" if defined _pmn_val goto resolve_ok
|
||||
if /i "%~3"=="quiet" goto resolve_failed
|
||||
if defined _pmn_val (
|
||||
echo error: %_pmn_val% 1>&2
|
||||
) else (
|
||||
echo error: cmake\BuildDir.cmake did not answer. 1>&2
|
||||
)
|
||||
:resolve_failed
|
||||
endlocal
|
||||
exit /b 1
|
||||
|
||||
:resolve_ok
|
||||
endlocal & set "%~2=%_pmn_val:/=\%"
|
||||
goto :eof
|
||||
Executable
+280
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configure (if needed) and build the project, preferably with Ninja.
|
||||
#
|
||||
# Usage (from the project root):
|
||||
# scripts/build.sh [--preset <name>] [BUILD_TYPE] [CMAKE_ARGS...]
|
||||
# scripts/build.sh --list-presets
|
||||
# scripts/build.sh --help
|
||||
#
|
||||
# --preset <name> Use a CMakePresets.json preset. A preset selects that
|
||||
# target's toolchain file (cmake/toolchains/<name>.cmake),
|
||||
# which picks the compiler and declares the DIST_TARGET
|
||||
# together, instead of you having to pass a matching
|
||||
# pair by hand:
|
||||
# scripts/build.sh --preset windows-x64-mingw64
|
||||
# Each preset builds in its own directory,
|
||||
# out/build/<name>, so switching between presets never
|
||||
# invalidates -- or deletes -- another one's build.
|
||||
# Run --list-presets to see every preset for your OS.
|
||||
# Must come first if given at all; --preset=<name> also
|
||||
# works. Given with no value, or a value that isn't one
|
||||
# of --list-presets' own, this fails fast with that
|
||||
# same preset list shown right there, rather than
|
||||
# letting a typo fall through to a less obvious `cmake`
|
||||
# error further down.
|
||||
# BUILD_TYPE Debug|Release|RelWithDebInfo|MinSizeRel
|
||||
# (default: Configuration.cmake's DEFAULT_BUILD_TYPE)
|
||||
# CMAKE_ARGS Anything else is forwarded to `cmake` verbatim, e.g.:
|
||||
# scripts/build.sh --preset windows-x64-mingw64 Debug -DBUILD_TESTS=ON
|
||||
# scripts/build.sh -DBUILD_TESTS=ON
|
||||
# (BUILD_TYPE omitted -- recognized by not
|
||||
# starting with "-")
|
||||
#
|
||||
# Picking a DIST_TARGET: three ways; the first two set the compiler and the
|
||||
# label together, and this script rejects mixing --preset with a
|
||||
# -DDIST_TARGET rather than leaving it ambiguous which one wins.
|
||||
# 1. --preset <name> The recommended way; see --list-presets.
|
||||
# 2. -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/<name>.cmake
|
||||
# (no --preset) What a preset does, by hand, as one of
|
||||
# CMAKE_ARGS above.
|
||||
# 3. -DDIST_TARGET=<value> Sets the label alone, as one of CMAKE_ARGS
|
||||
# (no --preset) above. Without also passing a matching
|
||||
# compiler (or, better, the toolchain file
|
||||
# above), DIST_TARGET is auto-detected from
|
||||
# whatever compiler CMake finds on its own; if
|
||||
# the two disagree, Configuration.cmake
|
||||
# FATAL_ERRORs rather than silently building
|
||||
# the wrong toolchain and mislabeling the
|
||||
# result -- see that file and the README.
|
||||
# Passing --preset together with an explicit -DDIST_TARGET=... in
|
||||
# CMAKE_ARGS is rejected by this script before cmake even runs: the
|
||||
# preset's toolchain already declares DIST_TARGET, so a second value
|
||||
# alongside it is always either redundant (same value) or contradictory
|
||||
# (different value) -- never useful.
|
||||
#
|
||||
# Build directory: out/build/<preset> for --preset <preset>; without a
|
||||
# preset, out/build/default (the compiler is auto-detected there, so keep
|
||||
# to one compiler per directory or use presets). Set BUILD_DIR to use an
|
||||
# exact directory of your own instead. scripts/run.sh, install.sh and
|
||||
# docs.sh use the directory built last, or --preset <name> to pick another.
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/build.sh [--preset <name>] [BUILD_TYPE] [CMAKE_ARGS...]
|
||||
scripts/build.sh --list-presets
|
||||
scripts/build.sh --help
|
||||
|
||||
--preset <name> Use a CMakePresets.json preset: it selects the target's
|
||||
toolchain file (compiler and DIST_TARGET together) and
|
||||
builds in its own directory, out/build/<name>. Must come
|
||||
first if given at all; --preset=<name> also works. Run
|
||||
--list-presets to see every preset for your OS.
|
||||
BUILD_TYPE Debug|Release|RelWithDebInfo|MinSizeRel
|
||||
(default: Configuration.cmake's DEFAULT_BUILD_TYPE)
|
||||
CMAKE_ARGS Anything else is forwarded to `cmake` verbatim, e.g.
|
||||
-DDIST_TARGET=<value> or -DBUILD_TESTS=ON.
|
||||
|
||||
Don't combine --preset with an explicit -DDIST_TARGET=... in CMAKE_ARGS --
|
||||
the preset's toolchain already declares one; see the comment at the top of
|
||||
this script (scripts/build.sh) and Configuration.cmake's "Target selection"
|
||||
section for why picking a DIST_TARGET is one or the other, not both.
|
||||
|
||||
Examples:
|
||||
scripts/build.sh --preset linux-x64-clang
|
||||
scripts/build.sh --preset windows-x64-mingw64 Debug -DBUILD_TESTS=ON
|
||||
scripts/build.sh -DBUILD_TESTS=ON
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
command -v cmake >/dev/null 2>&1 || { echo "error: cmake not found on PATH." >&2; exit 1; }
|
||||
|
||||
if [ "${1:-}" = "--list-presets" ]; then
|
||||
exec cmake --list-presets
|
||||
fi
|
||||
|
||||
# _pmn_read_config, _pmn_resolve_build_dir, _pmn_record_build_dir.
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
# Prints CMake's own preset listing (already filtered to this OS by each
|
||||
# preset's "condition" in CMakePresets.json) to stderr -- used to show the
|
||||
# actual options right alongside a preset-related error below, instead of
|
||||
# just telling the user to go run --list-presets separately.
|
||||
_pmn_show_presets() {
|
||||
echo "" >&2
|
||||
cmake --list-presets >&2
|
||||
}
|
||||
|
||||
# Membership test against that same listing -- $1 matches a real preset
|
||||
# name for this OS only if it appears there as a quoted entry (anchored on
|
||||
# both quote characters so e.g. "windows-x64-mingw32" can't false-match a
|
||||
# check for "windows-x64-mingw3").
|
||||
_pmn_known_preset() {
|
||||
cmake --list-presets 2>/dev/null | grep -qF "\"$1\""
|
||||
}
|
||||
|
||||
_pmn_has_explicit_dist_target() {
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-DDIST_TARGET=*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# --preset must come first if given at all -- see Configuration.cmake's
|
||||
# and CMakePresets.json's own comments for why this is the recommended
|
||||
# way to pick a non-default DIST_TARGET: its toolchain file sets the
|
||||
# compiler and the label together, instead of you having to pass both by
|
||||
# hand.
|
||||
PRESET=""
|
||||
case "${1:-}" in
|
||||
--preset)
|
||||
if [ "$#" -lt 2 ] || [[ "$2" == -* ]]; then
|
||||
echo "error: --preset requires a value, e.g. --preset windows-x64-mingw64" >&2
|
||||
_pmn_show_presets
|
||||
exit 1
|
||||
fi
|
||||
PRESET="$2"
|
||||
shift 2
|
||||
;;
|
||||
--preset=*)
|
||||
PRESET="${1#--preset=}"
|
||||
shift
|
||||
if [ -z "$PRESET" ]; then
|
||||
echo "error: --preset= requires a value, e.g. --preset=windows-x64-mingw64" >&2
|
||||
_pmn_show_presets
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "$PRESET" ] && ! _pmn_known_preset "$PRESET"; then
|
||||
echo "error: '${PRESET}' is not a preset --list-presets knows about for this OS." >&2
|
||||
_pmn_show_presets
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# One build directory per preset (out/build/<preset>; "default" when no
|
||||
# preset was given), so switching presets never has to reuse -- or wipe -- a
|
||||
# directory configured for another compiler. cmake/BuildDir.cmake owns the
|
||||
# layout (Configuration.cmake's BUILD_OUTPUT_DIR); an explicit BUILD_DIR
|
||||
# from the environment beats it, and is then also not recorded as "the one
|
||||
# built last" (run/install/docs would otherwise be sent to a directory the
|
||||
# layout doesn't know about).
|
||||
_pmn_record_build=1
|
||||
if [ -n "${BUILD_DIR:-}" ]; then
|
||||
_pmn_record_build=0
|
||||
else
|
||||
BUILD_DIR="$(_pmn_resolve_build_dir "${PRESET:-default}")"
|
||||
fi
|
||||
|
||||
# If the first remaining argument doesn't start with "-", it's BUILD_TYPE
|
||||
# and everything after it is CMAKE_ARGS; otherwise there's no BUILD_TYPE
|
||||
# override and every argument is CMAKE_ARGS. Plain bash "$@"/shift, no
|
||||
# special-casing needed: unlike Windows cmd.exe (see build.bat), bash
|
||||
# never splits a "-DFOO=BAR"-style argument on its own "=".
|
||||
if [ "$#" -gt 0 ] && [[ "$1" != -* ]]; then
|
||||
BUILD_TYPE="$1"
|
||||
shift
|
||||
else
|
||||
BUILD_TYPE="$(_pmn_read_config DEFAULT_BUILD_TYPE)"
|
||||
fi
|
||||
CMAKE_ARGS=("$@")
|
||||
|
||||
# ${arr[@]+"${arr[@]}"} rather than a plain "${arr[@]}" wherever an array can be
|
||||
# empty: under `set -u`, bash before 4.4 -- which includes the 3.2 that macOS
|
||||
# still ships as /bin/bash -- treats expanding an EMPTY array as an unset
|
||||
# variable and aborts with "unbound variable".
|
||||
if [ -n "$PRESET" ] && _pmn_has_explicit_dist_target ${CMAKE_ARGS[@]+"${CMAKE_ARGS[@]}"}; then
|
||||
echo "error: both --preset ${PRESET} and an explicit -DDIST_TARGET=... were given." >&2
|
||||
echo " A preset's toolchain already declares DIST_TARGET (and picks the compiler) -- pick one:" >&2
|
||||
echo " scripts/build.sh --preset ${PRESET}" >&2
|
||||
echo " scripts/build.sh -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/<target>.cmake" >&2
|
||||
echo " See the comment at the top of this script, or Configuration.cmake's" >&2
|
||||
echo " \"Target selection\" section, for why these two aren't combined." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prefer a pinned ninja (fetched by scripts/setup.sh -- see
|
||||
# cmake/FetchNinja.cmake) over a system one, same "always the pinned copy"
|
||||
# preference as Doxygen. Needed whether or not --preset is used: every
|
||||
# preset sets its generator to Ninja but doesn't hardcode which ninja
|
||||
# binary to use (that would just be TOOLCACHE_DIR's default duplicated a
|
||||
# third time -- see Configuration.cmake) -- CMAKE_MAKE_PROGRAM supplies it
|
||||
# either way; only -G Ninja itself is skipped below when a preset already
|
||||
# sets the generator.
|
||||
TOOLCACHE_DIR="${TOOLCACHE_DIR:-$(_pmn_read_config TOOLCACHE_DIR)}"
|
||||
NINJA_BIN=""
|
||||
for _candidate in "${TOOLCACHE_DIR}"/ninja-*/ninja; do
|
||||
if [ -x "$_candidate" ]; then
|
||||
# Must be absolute: CMake invokes CMAKE_MAKE_PROGRAM from several
|
||||
# different working directories during its own internal
|
||||
# try-compile steps, where a relative path doesn't resolve.
|
||||
NINJA_BIN="$(cd "$(dirname "$_candidate")" && pwd)/$(basename "$_candidate")"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
GENERATOR_ARGS=()
|
||||
if [ -n "$NINJA_BIN" ]; then
|
||||
echo "==> Using pinned ninja: ${NINJA_BIN}"
|
||||
if [ -z "$PRESET" ]; then
|
||||
GENERATOR_ARGS=(-G Ninja "-DCMAKE_MAKE_PROGRAM=${NINJA_BIN}")
|
||||
else
|
||||
GENERATOR_ARGS=("-DCMAKE_MAKE_PROGRAM=${NINJA_BIN}")
|
||||
fi
|
||||
elif command -v ninja >/dev/null 2>&1; then
|
||||
echo "==> Using system ninja: $(command -v ninja)"
|
||||
if [ -z "$PRESET" ]; then
|
||||
GENERATOR_ARGS=(-G Ninja)
|
||||
fi
|
||||
else
|
||||
if [ -n "$PRESET" ]; then
|
||||
echo "warning: ninja not found (no pinned copy, none on PATH) -- the '${PRESET}'" >&2
|
||||
echo " preset requires it; configure will likely fail. Run" >&2
|
||||
echo " scripts/setup.sh to fetch a pinned ninja, or install ninja" >&2
|
||||
echo " yourself." >&2
|
||||
else
|
||||
echo "warning: ninja not found (no pinned copy, none on PATH) -- falling back to" >&2
|
||||
echo " CMake's default generator for this platform. Run scripts/setup.sh" >&2
|
||||
echo " to fetch a pinned ninja, or install ninja yourself, for a faster" >&2
|
||||
echo " and more consistent build. Continuing without it." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
CONFIGURE_ARGS=(-S . -B "${BUILD_DIR}")
|
||||
if [ -n "$PRESET" ]; then
|
||||
CONFIGURE_ARGS=(--preset "${PRESET}" "${CONFIGURE_ARGS[@]}")
|
||||
fi
|
||||
|
||||
echo "==> Configuring (${BUILD_TYPE}) into ${BUILD_DIR}/"
|
||||
if [ -n "$PRESET" ]; then
|
||||
echo " Preset: ${PRESET}"
|
||||
fi
|
||||
if [ "${#CMAKE_ARGS[@]}" -gt 0 ]; then
|
||||
echo " Extra CMake arguments: ${CMAKE_ARGS[*]}"
|
||||
fi
|
||||
cmake "${CONFIGURE_ARGS[@]}" ${GENERATOR_ARGS[@]+"${GENERATOR_ARGS[@]}"} -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" ${CMAKE_ARGS[@]+"${CMAKE_ARGS[@]}"}
|
||||
|
||||
# Configure succeeded: this is now the build the other scripts default to.
|
||||
if [ "$_pmn_record_build" = 1 ]; then
|
||||
_pmn_record_build_dir "${PRESET:-default}"
|
||||
fi
|
||||
|
||||
echo "==> Building"
|
||||
cmake --build "${BUILD_DIR}"
|
||||
|
||||
echo "==> Done. Run a module with: scripts/run.sh <target-name>"
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
@echo off
|
||||
REM Build Doxygen documentation for every module (see cmake\Documentation.cmake)
|
||||
REM and print the path to the landing page. A no-op with a clear message if
|
||||
REM Doxygen isn't installed or ENABLE_DOXYGEN=OFF.
|
||||
REM
|
||||
REM Output goes to out\docs\ (DOCS_OUTPUT_DIR in Configuration.cmake) --
|
||||
REM note this is a sibling of out\build, not nested inside it, and shared by
|
||||
REM every preset: documentation doesn't depend on which compiler built the
|
||||
REM code. The docs targets just need SOME configured build directory to run
|
||||
REM from, so this uses the one built last unless you pick with --preset. If
|
||||
REM you've overridden DOCS_OUTPUT_DIR at configure time, also set DOCS_DIR here
|
||||
REM to match so the printed path is correct (docs still land in the right
|
||||
REM place either way -- this only affects the message below).
|
||||
REM
|
||||
REM Usage (from the project root):
|
||||
REM scripts\docs.bat REM every module
|
||||
REM scripts\docs.bat core_lib REM just one module's docs (docs_<target>)
|
||||
REM scripts\docs.bat --preset windows-x64-clang-cl REM ... from that preset's build directory
|
||||
REM scripts\docs.bat --help
|
||||
REM
|
||||
REM --preset must come first if given at all; --preset=<name> also works. Set
|
||||
REM BUILD_DIR to use an exact build directory instead.
|
||||
setlocal
|
||||
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
goto after_help
|
||||
|
||||
:show_help
|
||||
echo Usage: scripts\docs.bat [--preset ^<name^>] [module-target-name]
|
||||
echo.
|
||||
echo Builds Doxygen documentation for every module ^(see cmake\Documentation.cmake^)
|
||||
echo and prints the path to the landing page. A no-op with a clear message if
|
||||
echo Doxygen isn't installed or ENABLE_DOXYGEN=OFF.
|
||||
echo.
|
||||
echo --preset ^<name^> Run from that preset's build directory
|
||||
echo ^(out\build\^<name^>^). Default: the build
|
||||
echo scripts\build.bat made last. Must come first if
|
||||
echo given at all; --preset=^<name^> also works.
|
||||
echo ^(no argument^) Build docs for every module.
|
||||
echo module-target-name Build docs for just that one module ^(its own
|
||||
echo docs_^<name^> target^), e.g.:
|
||||
echo scripts\docs.bat core_lib
|
||||
echo.
|
||||
echo Output goes to out\docs\ ^(DOCS_OUTPUT_DIR in Configuration.cmake^), shared
|
||||
echo by every preset -- see the comment at the top of this script if you've
|
||||
echo overridden that.
|
||||
exit /b 0
|
||||
|
||||
:after_help
|
||||
cd /d "%~dp0.."
|
||||
|
||||
where cmake >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo error: cmake not found on PATH.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM --preset must come first if given at all, same as scripts\build.bat.
|
||||
REM cmd.exe splits "--preset=<name>" into "--preset" and "<name>" on its own
|
||||
REM (see build.bat's note on "="), so both spellings arrive here identically
|
||||
REM and this one branch handles both.
|
||||
set "PRESET="
|
||||
if /i not "%~1"=="--preset" goto preset_done
|
||||
if "%~2"=="" (
|
||||
echo error: --preset requires a value, e.g. --preset windows-x64-clang-cl 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
set "PRESET=%~2"
|
||||
shift
|
||||
shift
|
||||
:preset_done
|
||||
REM Suffix for the error hints below that say to run build.bat first.
|
||||
set "_pmn_hint="
|
||||
if defined PRESET set "_pmn_hint= --preset %PRESET%"
|
||||
|
||||
REM BUILD_DIR from the environment beats everything; otherwise --preset, else
|
||||
REM whichever build was made last (see cmake\BuildDir.cmake). Explains itself
|
||||
REM and stops if there's nothing to pick.
|
||||
if not "%BUILD_DIR%"=="" goto build_dir_known
|
||||
call :resolve_build_dir "%PRESET%" BUILD_DIR
|
||||
if errorlevel 1 exit /b 1
|
||||
:build_dir_known
|
||||
if "%DOCS_DIR%"=="" set "DOCS_DIR=out\docs"
|
||||
|
||||
if not exist "%BUILD_DIR%" (
|
||||
echo error: "%BUILD_DIR%" not found. Run scripts\build.bat%_pmn_hint% first.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ==^> Using build directory %BUILD_DIR%
|
||||
|
||||
if "%~1"=="" (
|
||||
cmake --build "%BUILD_DIR%" --target docs
|
||||
if exist "%DOCS_DIR%\index.html" (
|
||||
echo ==^> Open: %DOCS_DIR%\index.html
|
||||
)
|
||||
) else (
|
||||
cmake --build "%BUILD_DIR%" --target docs_%1
|
||||
echo ==^> Open: %DOCS_DIR%\%1\html\index.html
|
||||
)
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :resolve_build_dir <name-or-empty> <LocalVarName> [quiet]
|
||||
REM
|
||||
REM Asks cmake\BuildDir.cmake which build directory to use -- %1 is a preset
|
||||
REM name, or "default"; empty means "discover one" (the build made last, or
|
||||
REM the only one) -- and stores it into %2 in the CALLER's scope, with
|
||||
REM backslashes. Same shape as build.bat's, and the same `cmake -P`
|
||||
REM one-line-answer protocol: "-- BUILD_DIR=<path>" or "-- ERROR=<text>"
|
||||
REM (see that file). On failure it prints the reason to stderr, unless a
|
||||
REM third argument "quiet" is given, and returns errorlevel 1.
|
||||
REM ---------------------------------------------------------------------
|
||||
:resolve_build_dir
|
||||
setlocal
|
||||
set "_pmn_key="
|
||||
set "_pmn_val="
|
||||
for /f "usebackq tokens=1,* delims==" %%A in (`cmake "-DPMN_NAME=%~1" -P cmake\BuildDir.cmake`) do (
|
||||
set "_pmn_key=%%A"
|
||||
set "_pmn_val=%%B"
|
||||
)
|
||||
if "%_pmn_key%"=="-- BUILD_DIR" if defined _pmn_val goto resolve_ok
|
||||
if /i "%~3"=="quiet" goto resolve_failed
|
||||
if defined _pmn_val (
|
||||
echo error: %_pmn_val% 1>&2
|
||||
) else (
|
||||
echo error: cmake\BuildDir.cmake did not answer. 1>&2
|
||||
)
|
||||
:resolve_failed
|
||||
endlocal
|
||||
exit /b 1
|
||||
|
||||
:resolve_ok
|
||||
endlocal & set "%~2=%_pmn_val:/=\%"
|
||||
goto :eof
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build Doxygen documentation for every module (see cmake/Documentation.cmake)
|
||||
# and print the path to the landing page. A no-op with a clear message if
|
||||
# Doxygen isn't installed or ENABLE_DOXYGEN=OFF.
|
||||
#
|
||||
# Output goes to out/docs/ (DOCS_OUTPUT_DIR in Configuration.cmake) --
|
||||
# note this is a sibling of out/build, not nested inside it, and shared by
|
||||
# every preset: documentation doesn't depend on which compiler built the
|
||||
# code. The docs targets just need SOME configured build directory to run
|
||||
# from, so this uses the one built last unless you pick with --preset. If
|
||||
# you've overridden DOCS_OUTPUT_DIR at configure time, also set DOCS_DIR here
|
||||
# to match so the printed path is correct (docs still land in the right
|
||||
# place either way -- this only affects the message below).
|
||||
#
|
||||
# Usage (from the project root):
|
||||
# scripts/docs.sh # every module
|
||||
# scripts/docs.sh core_lib # just one module's docs (docs_<target>)
|
||||
# scripts/docs.sh --preset linux-x64-gcc # ... from that preset's build directory
|
||||
# scripts/docs.sh --help
|
||||
#
|
||||
# --preset must come first if given at all; --preset=<name> also works. Set
|
||||
# BUILD_DIR to use an exact build directory instead.
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help)
|
||||
cat <<'HELP'
|
||||
Usage: scripts/docs.sh [--preset <name>] [module-target-name]
|
||||
|
||||
Builds Doxygen documentation for every module (see cmake/Documentation.cmake)
|
||||
and prints the path to the landing page. A no-op with a clear message if
|
||||
Doxygen isn't installed or ENABLE_DOXYGEN=OFF.
|
||||
|
||||
--preset <name> Run from that preset's build directory
|
||||
(out/build/<name>). Default: the build
|
||||
scripts/build.sh made last. Must come first if
|
||||
given at all; --preset=<name> also works.
|
||||
(no argument) Build docs for every module.
|
||||
module-target-name Build docs for just that one module (its own
|
||||
docs_<name> target), e.g.:
|
||||
scripts/docs.sh core_lib
|
||||
|
||||
Output goes to out/docs/ (DOCS_OUTPUT_DIR in Configuration.cmake), shared
|
||||
by every preset -- see the comment at the top of this script if you've
|
||||
overridden that.
|
||||
HELP
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
# _pmn_read_config, _pmn_resolve_build_dir, _pmn_record_build_dir.
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
# --preset must come first if given at all, same as scripts/build.sh.
|
||||
PRESET=""
|
||||
case "${1:-}" in
|
||||
--preset)
|
||||
if [ "$#" -lt 2 ] || [[ "$2" == -* ]]; then
|
||||
echo "error: --preset requires a value, e.g. --preset linux-x64-gcc" >&2
|
||||
exit 1
|
||||
fi
|
||||
PRESET="$2"
|
||||
shift 2
|
||||
;;
|
||||
--preset=*)
|
||||
PRESET="${1#--preset=}"
|
||||
shift
|
||||
if [ -z "$PRESET" ]; then
|
||||
echo "error: --preset= requires a value, e.g. --preset=linux-x64-gcc" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "${BUILD_DIR:-}" ]; then
|
||||
# Explains itself and stops if there's nothing to pick.
|
||||
BUILD_DIR="$(_pmn_resolve_build_dir "$PRESET")"
|
||||
fi
|
||||
DOCS_DIR="${DOCS_DIR:-out/docs}"
|
||||
|
||||
if [ ! -d "$BUILD_DIR" ]; then
|
||||
echo "error: '${BUILD_DIR}' not found. Run scripts/build.sh${PRESET:+ --preset $PRESET} first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Using build directory ${BUILD_DIR}"
|
||||
if [ $# -ge 1 ]; then
|
||||
TARGET="docs_$1"
|
||||
cmake --build "${BUILD_DIR}" --target "${TARGET}"
|
||||
echo "==> Open: ${DOCS_DIR}/$1/html/index.html"
|
||||
else
|
||||
cmake --build "${BUILD_DIR}" --target docs
|
||||
if [ -f "${DOCS_DIR}/index.html" ]; then
|
||||
echo "==> Open: ${DOCS_DIR}/index.html"
|
||||
fi
|
||||
fi
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
@echo off
|
||||
REM Install this project's own build artifacts to
|
||||
REM out\install\<platform>-<arch>-<compiler>\ (INSTALL_OUTPUT_DIR in
|
||||
REM Configuration.cmake; see CMakeLists.txt's install() DESTINATION paths) --
|
||||
REM the same name as the out\build\<preset> directory they came from, since
|
||||
REM a preset is named after its target.
|
||||
REM
|
||||
REM Always passes --component to `cmake --install`. Without it, any
|
||||
REM vendored/fetched dependency's own install() rules (see
|
||||
REM cmake\Dependencies.cmake) would also run, typically dumping files under
|
||||
REM CMAKE_INSTALL_PREFIX -- COMPONENT is what keeps `cmake --install`
|
||||
REM scoped to just this project's own targets.
|
||||
REM
|
||||
REM Usage (from the project root):
|
||||
REM scripts\install.bat [--preset <name>] [BUILD_TYPE]
|
||||
REM scripts\install.bat --help
|
||||
REM
|
||||
REM --preset <name> Install the build made with that preset
|
||||
REM (out\build\<name>). Without it: the build
|
||||
REM scripts\build.bat made last, or the only one that
|
||||
REM exists. Must come first if given at all;
|
||||
REM --preset=<name> also works.
|
||||
REM BUILD_TYPE Debug|Release|RelWithDebInfo|MinSizeRel
|
||||
REM (default: Configuration.cmake's DEFAULT_BUILD_TYPE) --
|
||||
REM only relevant for a Ninja Multi-Config build.
|
||||
REM
|
||||
REM Set BUILD_DIR to install from an exact build directory instead.
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
goto after_help
|
||||
|
||||
:show_help
|
||||
echo Usage: scripts\install.bat [--preset ^<name^>] [BUILD_TYPE]
|
||||
echo.
|
||||
echo Installs this project's own build artifacts to
|
||||
echo out\install\^<platform^>-^<arch^>-^<compiler^>\ ^(INSTALL_OUTPUT_DIR in
|
||||
echo Configuration.cmake^), scoped to just this project via `cmake --install
|
||||
echo --component` -- see the comment at the top of this script.
|
||||
echo.
|
||||
echo --preset ^<name^> Install the build made with that preset
|
||||
echo ^(out\build\^<name^>^). Default: the build
|
||||
echo scripts\build.bat made last. Must come first if given
|
||||
echo at all; --preset=^<name^> also works.
|
||||
echo BUILD_TYPE Debug^|Release^|RelWithDebInfo^|MinSizeRel
|
||||
echo ^(default: Configuration.cmake's DEFAULT_BUILD_TYPE^) --
|
||||
echo only relevant for a Ninja Multi-Config build.
|
||||
exit /b 0
|
||||
|
||||
:after_help
|
||||
cd /d "%~dp0.."
|
||||
|
||||
where cmake >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo error: cmake not found on PATH.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM --preset must come first if given at all, same as scripts\build.bat.
|
||||
REM cmd.exe splits "--preset=<name>" into "--preset" and "<name>" on its own
|
||||
REM (see build.bat's note on "="), so both spellings arrive here identically
|
||||
REM and this one branch handles both.
|
||||
set "PRESET="
|
||||
if /i not "%~1"=="--preset" goto preset_done
|
||||
if "%~2"=="" (
|
||||
echo error: --preset requires a value, e.g. --preset windows-x64-clang-cl 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
set "PRESET=%~2"
|
||||
shift
|
||||
shift
|
||||
:preset_done
|
||||
REM Suffix for the error hints below that say to run build.bat first.
|
||||
set "_pmn_hint="
|
||||
if defined PRESET set "_pmn_hint= --preset %PRESET%"
|
||||
|
||||
REM BUILD_DIR from the environment beats everything; otherwise --preset, else
|
||||
REM whichever build was made last (see cmake\BuildDir.cmake). Explains itself
|
||||
REM and stops if there's nothing to pick.
|
||||
if not "%BUILD_DIR%"=="" goto build_dir_known
|
||||
call :resolve_build_dir "%PRESET%" BUILD_DIR
|
||||
if errorlevel 1 exit /b 1
|
||||
:build_dir_known
|
||||
|
||||
REM A plain %~1 is safe here (unlike build.bat's arguments): a build type
|
||||
REM like "Debug" never contains "=", so cmd.exe's positional-parameter
|
||||
REM splitting on it never comes into play.
|
||||
set "BUILD_TYPE=%~1"
|
||||
if "%BUILD_TYPE%"=="" call :read_config DEFAULT_BUILD_TYPE BUILD_TYPE
|
||||
call :read_config PROJECT_CODE_NAME COMPONENT
|
||||
|
||||
if not exist "%BUILD_DIR%" (
|
||||
echo error: "%BUILD_DIR%" not found. Run scripts\build.bat%_pmn_hint% first.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ==^> Installing (component: %COMPONENT%) from %BUILD_DIR%
|
||||
cmake --install "%BUILD_DIR%" --config %BUILD_TYPE% --component %COMPONENT%
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :read_config <ConfigurationVarName> <LocalVarName>
|
||||
REM
|
||||
REM Reads one value out of Configuration.cmake -- this project's single
|
||||
REM source of truth -- via cmake\PrintConfig.cmake, instead of hardcoding
|
||||
REM a second copy of a default that can silently drift out of sync, and
|
||||
REM stores it into %2 in the CALLER's scope. See cmake\PrintConfig.cmake
|
||||
REM for exactly what's being stripped off of its output below.
|
||||
REM ---------------------------------------------------------------------
|
||||
:read_config
|
||||
setlocal
|
||||
set "_pmn_line="
|
||||
for /f "usebackq delims=" %%V in (`cmake "-DPRINT_VARS=%~1" -P cmake\PrintConfig.cmake`) do set "_pmn_line=%%V"
|
||||
for /f "tokens=1,* delims==" %%A in ("%_pmn_line%") do set "_pmn_value=%%B"
|
||||
endlocal & set "%~2=%_pmn_value%"
|
||||
goto :eof
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :resolve_build_dir <name-or-empty> <LocalVarName> [quiet]
|
||||
REM
|
||||
REM Asks cmake\BuildDir.cmake which build directory to use -- %1 is a preset
|
||||
REM name, or "default"; empty means "discover one" (the build made last, or
|
||||
REM the only one) -- and stores it into %2 in the CALLER's scope, with
|
||||
REM backslashes. Same shape as build.bat's, and the same `cmake -P`
|
||||
REM one-line-answer protocol: "-- BUILD_DIR=<path>" or "-- ERROR=<text>"
|
||||
REM (see that file). On failure it prints the reason to stderr, unless a
|
||||
REM third argument "quiet" is given, and returns errorlevel 1.
|
||||
REM ---------------------------------------------------------------------
|
||||
:resolve_build_dir
|
||||
setlocal
|
||||
set "_pmn_key="
|
||||
set "_pmn_val="
|
||||
for /f "usebackq tokens=1,* delims==" %%A in (`cmake "-DPMN_NAME=%~1" -P cmake\BuildDir.cmake`) do (
|
||||
set "_pmn_key=%%A"
|
||||
set "_pmn_val=%%B"
|
||||
)
|
||||
if "%_pmn_key%"=="-- BUILD_DIR" if defined _pmn_val goto resolve_ok
|
||||
if /i "%~3"=="quiet" goto resolve_failed
|
||||
if defined _pmn_val (
|
||||
echo error: %_pmn_val% 1>&2
|
||||
) else (
|
||||
echo error: cmake\BuildDir.cmake did not answer. 1>&2
|
||||
)
|
||||
:resolve_failed
|
||||
endlocal
|
||||
exit /b 1
|
||||
|
||||
:resolve_ok
|
||||
endlocal & set "%~2=%_pmn_val:/=\%"
|
||||
goto :eof
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install this project's own build artifacts to
|
||||
# out/install/<platform>-<arch>-<compiler>/ (INSTALL_OUTPUT_DIR in
|
||||
# Configuration.cmake; see CMakeLists.txt's install() DESTINATION paths) --
|
||||
# the same name as the out/build/<preset> directory they came from, since
|
||||
# a preset is named after its target.
|
||||
#
|
||||
# Always passes --component to `cmake --install`. Without it, any
|
||||
# vendored/fetched dependency's own install() rules (see
|
||||
# cmake/Dependencies.cmake) would also run, typically dumping files under
|
||||
# CMAKE_INSTALL_PREFIX (e.g. /usr/local) -- COMPONENT is what keeps
|
||||
# `cmake --install` scoped to just this project's own targets.
|
||||
#
|
||||
# Usage (from the project root):
|
||||
# scripts/install.sh [--preset <name>] [BUILD_TYPE]
|
||||
# scripts/install.sh --help
|
||||
#
|
||||
# --preset <name> Install the build made with that preset
|
||||
# (out/build/<name>). Without it: the build
|
||||
# scripts/build.sh made last, or the only one that
|
||||
# exists. Must come first if given at all;
|
||||
# --preset=<name> also works.
|
||||
# BUILD_TYPE Debug|Release|RelWithDebInfo|MinSizeRel
|
||||
# (default: Configuration.cmake's DEFAULT_BUILD_TYPE) --
|
||||
# only relevant for a Ninja Multi-Config build.
|
||||
#
|
||||
# Set BUILD_DIR to install from an exact build directory instead.
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help)
|
||||
cat <<'HELP'
|
||||
Usage: scripts/install.sh [--preset <name>] [BUILD_TYPE]
|
||||
|
||||
Installs this project's own build artifacts to
|
||||
out/install/<platform>-<arch>-<compiler>/ (INSTALL_OUTPUT_DIR in
|
||||
Configuration.cmake), scoped to just this project via `cmake --install
|
||||
--component` -- see the comment at the top of this script.
|
||||
|
||||
--preset <name> Install the build made with that preset (out/build/<name>).
|
||||
Default: the build scripts/build.sh made last. Must come
|
||||
first if given at all; --preset=<name> also works.
|
||||
BUILD_TYPE Debug|Release|RelWithDebInfo|MinSizeRel
|
||||
(default: Configuration.cmake's DEFAULT_BUILD_TYPE) --
|
||||
only relevant for a Ninja Multi-Config build.
|
||||
HELP
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
command -v cmake >/dev/null 2>&1 || { echo "error: cmake not found on PATH." >&2; exit 1; }
|
||||
|
||||
# _pmn_read_config, _pmn_resolve_build_dir, _pmn_record_build_dir. Reading
|
||||
# values out of Configuration.cmake this way -- its single source of truth
|
||||
# -- is the bug fix that replaced COMPONENT being a literal "demo" here,
|
||||
# with a comment warning it had to be kept in sync with PROJECT_CODE_NAME by
|
||||
# hand.
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
# --preset must come first if given at all, same as scripts/build.sh.
|
||||
PRESET=""
|
||||
case "${1:-}" in
|
||||
--preset)
|
||||
if [ "$#" -lt 2 ] || [[ "$2" == -* ]]; then
|
||||
echo "error: --preset requires a value, e.g. --preset linux-x64-gcc" >&2
|
||||
exit 1
|
||||
fi
|
||||
PRESET="$2"
|
||||
shift 2
|
||||
;;
|
||||
--preset=*)
|
||||
PRESET="${1#--preset=}"
|
||||
shift
|
||||
if [ -z "$PRESET" ]; then
|
||||
echo "error: --preset= requires a value, e.g. --preset=linux-x64-gcc" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "${BUILD_DIR:-}" ]; then
|
||||
# Explains itself and stops if there's nothing to pick.
|
||||
BUILD_DIR="$(_pmn_resolve_build_dir "$PRESET")"
|
||||
fi
|
||||
BUILD_TYPE="${1:-$(_pmn_read_config DEFAULT_BUILD_TYPE)}"
|
||||
COMPONENT="$(_pmn_read_config PROJECT_CODE_NAME)"
|
||||
|
||||
if [ ! -d "$BUILD_DIR" ]; then
|
||||
echo "error: '${BUILD_DIR}' not found. Run scripts/build.sh${PRESET:+ --preset $PRESET} first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Installing (component: ${COMPONENT}) from ${BUILD_DIR}"
|
||||
cmake --install "${BUILD_DIR}" --config "${BUILD_TYPE}" --component "${COMPONENT}"
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
@echo off
|
||||
REM Applies clang-format and clang-tidy's --fix across this project's own
|
||||
REM C/C++ code. The pre-commit hook only CHECKS; this is the "just fix it"
|
||||
REM counterpart -- review the result with `git diff` afterward, same as any
|
||||
REM auto-formatter.
|
||||
REM
|
||||
REM Defaults to source\ and c-api\, this template's own-code roots -- NEVER
|
||||
REM walks out\, .cache\, vendor\, or .git\ by default, so it can't
|
||||
REM accidentally spend minutes reformatting/analyzing a fetched dependency's
|
||||
REM entire source tree the way scanning the whole repo from "." would.
|
||||
REM
|
||||
REM Usage, from the project root: scripts\refactor.bat [optional path]
|
||||
REM scripts\refactor.bat every file under source\ and c-api\
|
||||
REM scripts\refactor.bat source\Core just one file or directory -- taken
|
||||
REM as given, with no exclusion filter,
|
||||
REM so pick a path that doesn't overlap
|
||||
REM with out\/.cache\/vendor\/.git\
|
||||
REM scripts\refactor.bat --help
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
goto after_help
|
||||
|
||||
:show_help
|
||||
echo Usage: scripts\refactor.bat [scope]
|
||||
echo.
|
||||
echo Applies clang-format and clang-tidy's --fix across this project's own
|
||||
echo C/C++ code -- review the result with `git diff` afterward. See the
|
||||
echo comment at the top of this script for the default scope and why it
|
||||
echo never walks out\, .cache\, vendor\, or .git\.
|
||||
echo.
|
||||
echo ^(no argument^) Every file under source\ and c-api\ ^(whichever exist^).
|
||||
echo scope Just one file or directory, e.g.:
|
||||
echo scripts\refactor.bat source\Core
|
||||
exit /b 0
|
||||
|
||||
:after_help
|
||||
cd /d "%~dp0.."
|
||||
|
||||
where clang-format >nul 2>nul
|
||||
set "HAVE_FORMAT=0"
|
||||
if not errorlevel 1 set "HAVE_FORMAT=1"
|
||||
where clang-tidy >nul 2>nul
|
||||
set "HAVE_TIDY=0"
|
||||
if not errorlevel 1 set "HAVE_TIDY=1"
|
||||
|
||||
if "%HAVE_FORMAT%"=="1" goto have_a_tool
|
||||
if "%HAVE_TIDY%"=="1" goto have_a_tool
|
||||
echo error: neither clang-format nor clang-tidy found on PATH. Run scripts\setup.bat for install guidance.
|
||||
exit /b 1
|
||||
:have_a_tool
|
||||
|
||||
set "FILELIST=%TEMP%\refactor_files_%RANDOM%.txt"
|
||||
if exist "%FILELIST%" del "%FILELIST%"
|
||||
|
||||
REM Collect the file list. Deliberately NOT using an if/else block here --
|
||||
REM a for /f or for /r loop nested inside a parenthesized if(...) else(...)
|
||||
REM block confuses cmd.exe's paren-matching for the enclosing block (this
|
||||
REM was tested and confirmed, not a guess); goto-based branching sidesteps
|
||||
REM the problem entirely by never nesting a FOR loop inside another block.
|
||||
if not "%~1"=="" goto collect_custom
|
||||
|
||||
set "SCOPE_DESC=source and c-api"
|
||||
if exist "source" for /r "source" %%F in (*.c *.h *.cpp *.hpp *.cc *.cxx *.tpp) do echo %%F>>"%FILELIST%"
|
||||
if exist "c-api" for /r "c-api" %%F in (*.c *.h *.cpp *.hpp *.cc *.cxx *.tpp) do echo %%F>>"%FILELIST%"
|
||||
goto collected
|
||||
|
||||
:collect_custom
|
||||
set "SCOPE_DESC=%~1"
|
||||
if not exist "%~1" goto collected
|
||||
for /r "%~1" %%F in (*.c *.h *.cpp *.hpp *.cc *.cxx *.tpp) do echo %%F>>"%FILELIST%"
|
||||
|
||||
:collected
|
||||
if exist "%FILELIST%" goto have_files
|
||||
echo No C/C++ files found under: %SCOPE_DESC%
|
||||
exit /b 0
|
||||
:have_files
|
||||
|
||||
echo ==^> Files in scope: %SCOPE_DESC%
|
||||
|
||||
if "%HAVE_FORMAT%"=="1" goto do_format
|
||||
echo ==^> Skipping clang-format ^(not found on PATH -- see scripts\setup.bat^)
|
||||
goto after_format
|
||||
:do_format
|
||||
echo ==^> Running clang-format -i
|
||||
for /f "usebackq delims=" %%F in ("%FILELIST%") do clang-format -i "%%F"
|
||||
:after_format
|
||||
|
||||
if "%HAVE_TIDY%"=="1" goto do_tidy
|
||||
echo ==^> Skipping clang-tidy ^(not found on PATH -- see scripts\setup.bat^)
|
||||
goto after_tidy
|
||||
:do_tidy
|
||||
if exist compile_commands.json goto run_tidy
|
||||
echo ==^> Skipping clang-tidy ^(no compile_commands.json -- run scripts\build.bat first^)
|
||||
goto after_tidy
|
||||
:run_tidy
|
||||
echo ==^> Running clang-tidy --fix ^(auto-fixable issues only; build once first if this is stale^)
|
||||
for /f "usebackq delims=" %%F in ("%FILELIST%") do clang-tidy --quiet --fix "%%F"
|
||||
:after_tidy
|
||||
|
||||
del "%FILELIST%" 2>nul
|
||||
echo ==^> Done. Review the changes: git diff
|
||||
exit /b 0
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Applies clang-format and clang-tidy's --fix across this project's own
|
||||
# C/C++ code. The pre-commit hook only CHECKS; this is the "just fix it"
|
||||
# counterpart -- review the result with `git diff` afterward, same as any
|
||||
# auto-formatter.
|
||||
#
|
||||
# Defaults to source/ and c-api/ (this template's own-code roots) --
|
||||
# NEVER walks out/, .cache/, vendor/, or .git/ by default, so it can't
|
||||
# accidentally spend minutes reformatting/analyzing a fetched dependency's
|
||||
# entire source tree (e.g. out/build/_deps/*-src/) the way scanning the
|
||||
# whole repo from "." would. Passing an explicit scope still excludes
|
||||
# those four directories defensively, in case the scope you give overlaps
|
||||
# with them.
|
||||
#
|
||||
# Usage (from the project root):
|
||||
# scripts/refactor.sh # every file under source/ and c-api/
|
||||
# scripts/refactor.sh source/Core # just one file or directory
|
||||
# scripts/refactor.sh --help
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
Usage: scripts/refactor.sh [scope]
|
||||
|
||||
Applies clang-format and clang-tidy's --fix across this project's own
|
||||
C/C++ code -- review the result with `git diff` afterward. See the
|
||||
comment at the top of this script for the default scope and why it never
|
||||
walks out/, .cache/, vendor/, or .git/.
|
||||
|
||||
(no argument) Every file under source/ and c-api/ (whichever exist).
|
||||
scope Just one file or directory, e.g.:
|
||||
scripts/refactor.sh source/Core
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
# _pmn_extend_tool_path: finds Homebrew's keg-only llvm (clang-tidy) on macOS.
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
_pmn_extend_tool_path
|
||||
|
||||
if [ $# -ge 1 ]; then
|
||||
scopes=("$1")
|
||||
else
|
||||
scopes=()
|
||||
[ -d source ] && scopes+=(source)
|
||||
[ -d c-api ] && scopes+=(c-api)
|
||||
fi
|
||||
|
||||
if [ "${#scopes[@]}" -eq 0 ]; then
|
||||
echo "No default scope found (expected a source/ and/or c-api/ directory here)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v clang-format >/dev/null 2>&1 && ! command -v clang-tidy >/dev/null 2>&1; then
|
||||
echo "error: neither clang-format nor clang-tidy found on PATH. Run scripts/setup.sh for install guidance." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A read loop rather than mapfile/readarray: those are bash 4+, and macOS
|
||||
# ships bash 3.2.
|
||||
files=()
|
||||
while IFS= read -r _file; do
|
||||
files+=("$_file")
|
||||
done < <(
|
||||
find "${scopes[@]}" \
|
||||
\( -path './vendor' -o -path './out' -o -path './.cache' -o -path './.git' \) -prune -o \
|
||||
-type f \( -name '*.c' -o -name '*.h' -o -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.cxx' -o -name '*.tpp' \) -print \
|
||||
| sed 's|^\./||' | sort
|
||||
)
|
||||
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No C/C++ files found under: ${scopes[*]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "==> ${#files[@]} file(s) in scope: ${scopes[*]}"
|
||||
|
||||
if command -v clang-format >/dev/null 2>&1; then
|
||||
echo "==> Running clang-format -i"
|
||||
clang-format -i "${files[@]}"
|
||||
else
|
||||
echo "==> Skipping clang-format (not found on PATH -- see scripts/setup.sh)"
|
||||
fi
|
||||
|
||||
if command -v clang-tidy >/dev/null 2>&1; then
|
||||
if [ ! -f compile_commands.json ]; then
|
||||
echo "==> Skipping clang-tidy (no compile_commands.json -- run scripts/build.sh first)"
|
||||
else
|
||||
echo "==> Running clang-tidy --fix (auto-fixable issues only; build once first if this is stale)"
|
||||
for f in "${files[@]}"; do
|
||||
clang-tidy --quiet --fix "$f" || true
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo "==> Skipping clang-tidy (not found on PATH -- see scripts/setup.sh)"
|
||||
fi
|
||||
|
||||
echo "==> Done. Review the changes: git diff"
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
@echo off
|
||||
REM Run an executable module by its CMake target name (as registered in
|
||||
REM that module's own CMakeLists.txt), resolved via the manifest that
|
||||
REM _write_module_manifest() generates at configure time -- so this script
|
||||
REM never needs updating when modules are added, removed, or renamed.
|
||||
REM
|
||||
REM Usage (from the project root):
|
||||
REM scripts\run.bat [--preset <name>] <target-name> [-- program-args...]
|
||||
REM
|
||||
REM --preset <name> Run the build made with that preset (out\build\<name>).
|
||||
REM Without it: the build scripts\build.bat made last, or
|
||||
REM the only one that exists. Must come first if given at
|
||||
REM all; --preset=<name> also works.
|
||||
REM
|
||||
REM Examples:
|
||||
REM scripts\run.bat hello_exe
|
||||
REM scripts\run.bat --preset windows-x64-clang-cl hello_exe
|
||||
REM scripts\run.bat farewell_exe -- Ale
|
||||
REM
|
||||
REM Set BUILD_DIR to run out of an exact build directory instead.
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM --preset must come first if given at all, same as scripts\build.bat.
|
||||
REM cmd.exe splits "--preset=<name>" into "--preset" and "<name>" on its own
|
||||
REM (see build.bat's note on "="), so both spellings arrive here identically
|
||||
REM and this one branch handles both.
|
||||
set "PRESET="
|
||||
if /i not "%~1"=="--preset" goto preset_done
|
||||
if "%~2"=="" (
|
||||
echo error: --preset requires a value, e.g. --preset windows-x64-clang-cl 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
set "PRESET=%~2"
|
||||
shift
|
||||
shift
|
||||
:preset_done
|
||||
REM Suffix for the error hints below that say to run build.bat first.
|
||||
set "_pmn_hint="
|
||||
if defined PRESET set "_pmn_hint= --preset %PRESET%"
|
||||
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
if "%~1"=="" goto usage
|
||||
|
||||
set "TARGET_NAME=%~1"
|
||||
shift
|
||||
|
||||
REM Collect any remaining args to pass through to the program (skipping a
|
||||
REM leading "--" separator if present).
|
||||
set "PASSTHROUGH="
|
||||
set "skip_next_dashes=1"
|
||||
:collect
|
||||
if "%~1"=="" goto collected
|
||||
if "!skip_next_dashes!"=="1" if "%~1"=="--" (
|
||||
set "skip_next_dashes=0"
|
||||
shift
|
||||
goto collect
|
||||
)
|
||||
set "skip_next_dashes=0"
|
||||
set "PASSTHROUGH=!PASSTHROUGH! "%~1""
|
||||
shift
|
||||
goto collect
|
||||
:collected
|
||||
|
||||
REM BUILD_DIR from the environment beats everything; otherwise --preset, else
|
||||
REM whichever build was made last (see cmake\BuildDir.cmake). Explains itself
|
||||
REM and stops if there's nothing to pick.
|
||||
if not "%BUILD_DIR%"=="" goto build_dir_known
|
||||
where cmake >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo error: cmake not found on PATH -- it is needed to work out which build directory to use. 1>&2
|
||||
echo Put it on PATH, or set BUILD_DIR to the directory explicitly, e.g. out\build\^<preset^>. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
call :resolve_build_dir "%PRESET%" BUILD_DIR
|
||||
if errorlevel 1 exit /b 1
|
||||
:build_dir_known
|
||||
|
||||
set "MANIFEST="
|
||||
for %%F in ("%BUILD_DIR%\module_manifest-*.txt") do (
|
||||
if not defined MANIFEST set "MANIFEST=%%F"
|
||||
)
|
||||
if not defined MANIFEST (
|
||||
echo error: no build manifest found in "%BUILD_DIR%". Run scripts\build.bat%_pmn_hint% first. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "BIN_PATH="
|
||||
for /f "usebackq tokens=1,* delims==" %%K in ("%MANIFEST%") do (
|
||||
if "%%K"=="%TARGET_NAME%" set "BIN_PATH=%%L"
|
||||
)
|
||||
|
||||
if not defined BIN_PATH (
|
||||
echo error: no executable target named "%TARGET_NAME%" in "%BUILD_DIR%". 1>&2
|
||||
echo Available targets: 1>&2
|
||||
for /f "usebackq tokens=1 delims==" %%T in ("%MANIFEST%") do echo %%T 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%BIN_PATH%" (
|
||||
echo error: "%BIN_PATH%" was not found. Rebuild with scripts\build.bat%_pmn_hint%. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Which build this came from -- to stderr, so a program's own stdout stays
|
||||
REM clean to pipe.
|
||||
echo ==^> Running %TARGET_NAME% ^(%BUILD_DIR%^) 1>&2
|
||||
"%BIN_PATH%" !PASSTHROUGH!
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
:usage
|
||||
REM Best-effort here: usage should still print with nothing built yet.
|
||||
if "%BUILD_DIR%"=="" call :resolve_build_dir "%PRESET%" BUILD_DIR quiet
|
||||
echo Usage: %~nx0 [--preset ^<name^>] ^<cmake-target-name^> [-- program-args...] 1>&2
|
||||
echo. 1>&2
|
||||
echo Available executable targets: 1>&2
|
||||
set "found=0"
|
||||
for %%F in ("%BUILD_DIR%\module_manifest-*.txt") do (
|
||||
set "found=1"
|
||||
for /f "usebackq tokens=1 delims==" %%T in ("%%F") do echo %%T 1>&2
|
||||
)
|
||||
if "!found!"=="0" echo (none found -- did you run scripts\build.bat?) 1>&2
|
||||
exit /b 1
|
||||
|
||||
:show_help
|
||||
REM Best-effort here: help should still print with nothing built yet.
|
||||
if "%BUILD_DIR%"=="" call :resolve_build_dir "%PRESET%" BUILD_DIR quiet
|
||||
echo Usage: %~nx0 [--preset ^<name^>] ^<cmake-target-name^> [-- program-args...]
|
||||
echo.
|
||||
echo Runs an executable module by its CMake target name, resolved via the
|
||||
echo manifest scripts\build.bat generates at configure time -- see the
|
||||
echo comment at the top of this script.
|
||||
echo.
|
||||
echo --preset ^<name^> Run the build made with that preset. Default: the
|
||||
echo build scripts\build.bat made last.
|
||||
echo.
|
||||
echo Available executable targets:
|
||||
set "found=0"
|
||||
for %%F in ("%BUILD_DIR%\module_manifest-*.txt") do (
|
||||
set "found=1"
|
||||
for /f "usebackq tokens=1 delims==" %%T in ("%%F") do echo %%T
|
||||
)
|
||||
if "!found!"=="0" echo (none found -- did you run scripts\build.bat?)
|
||||
echo.
|
||||
echo Examples:
|
||||
echo scripts\run.bat hello_exe
|
||||
echo scripts\run.bat --preset windows-x64-clang-cl hello_exe
|
||||
echo scripts\run.bat farewell_exe -- Ale
|
||||
exit /b 0
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :resolve_build_dir <name-or-empty> <LocalVarName> [quiet]
|
||||
REM
|
||||
REM Asks cmake\BuildDir.cmake which build directory to use -- %1 is a preset
|
||||
REM name, or "default"; empty means "discover one" (the build made last, or
|
||||
REM the only one) -- and stores it into %2 in the CALLER's scope, with
|
||||
REM backslashes. Same shape as build.bat's, and the same `cmake -P`
|
||||
REM one-line-answer protocol: "-- BUILD_DIR=<path>" or "-- ERROR=<text>"
|
||||
REM (see that file). On failure it prints the reason to stderr, unless a
|
||||
REM third argument "quiet" is given, and returns errorlevel 1.
|
||||
REM ---------------------------------------------------------------------
|
||||
:resolve_build_dir
|
||||
setlocal
|
||||
set "_pmn_key="
|
||||
set "_pmn_val="
|
||||
for /f "usebackq tokens=1,* delims==" %%A in (`cmake "-DPMN_NAME=%~1" -P cmake\BuildDir.cmake`) do (
|
||||
set "_pmn_key=%%A"
|
||||
set "_pmn_val=%%B"
|
||||
)
|
||||
if "%_pmn_key%"=="-- BUILD_DIR" if defined _pmn_val goto resolve_ok
|
||||
if /i "%~3"=="quiet" goto resolve_failed
|
||||
if defined _pmn_val (
|
||||
echo error: %_pmn_val% 1>&2
|
||||
) else (
|
||||
echo error: cmake\BuildDir.cmake did not answer. 1>&2
|
||||
)
|
||||
:resolve_failed
|
||||
endlocal
|
||||
exit /b 1
|
||||
|
||||
:resolve_ok
|
||||
endlocal & set "%~2=%_pmn_val:/=\%"
|
||||
goto :eof
|
||||
Executable
+190
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run an executable module by its CMake target name (as registered in
|
||||
# that module's own CMakeLists.txt), resolved via the manifest that
|
||||
# _write_module_manifest() generates at configure time -- so this script
|
||||
# never needs updating when modules are added, removed, or renamed.
|
||||
#
|
||||
# Usage (from the project root):
|
||||
# scripts/run.sh [--preset <name>] <target-name> [-- program-args...]
|
||||
#
|
||||
# --preset <name> Run the build made with that preset (out/build/<name>).
|
||||
# Without it: the build scripts/build.sh made last, or
|
||||
# the only one that exists. Must come first if given at
|
||||
# all; --preset=<name> also works.
|
||||
#
|
||||
# Examples:
|
||||
# scripts/run.sh hello_exe
|
||||
# scripts/run.sh --preset linux-x64-clang hello_exe
|
||||
# scripts/run.sh farewell_exe -- Ale
|
||||
#
|
||||
# Set BUILD_DIR to run out of an exact build directory instead.
|
||||
#
|
||||
# An executable built for another architecture or OS -- linux-arm64-* on an
|
||||
# x64 machine, windows-*-mingw* from Linux -- is run under the emulator its
|
||||
# toolchain registered (qemu-user, wine) if one was installed when the build
|
||||
# was configured; otherwise it can only run on the target itself.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
# _pmn_read_config, _pmn_resolve_build_dir, _pmn_record_build_dir.
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
# --preset must come first if given at all, same as scripts/build.sh.
|
||||
PRESET=""
|
||||
case "${1:-}" in
|
||||
--preset)
|
||||
if [ "$#" -lt 2 ] || [[ "$2" == -* ]]; then
|
||||
echo "error: --preset requires a value, e.g. --preset linux-x64-gcc" >&2
|
||||
exit 1
|
||||
fi
|
||||
PRESET="$2"
|
||||
shift 2
|
||||
;;
|
||||
--preset=*)
|
||||
PRESET="${1#--preset=}"
|
||||
shift
|
||||
if [ -z "$PRESET" ]; then
|
||||
echo "error: --preset= requires a value, e.g. --preset=linux-x64-gcc" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# BUILD_DIR from the environment beats everything; otherwise --preset, else
|
||||
# whichever build was made last (see cmake/BuildDir.cmake). Help and the
|
||||
# usage message look it up best-effort -- they should still print on a
|
||||
# fresh checkout with nothing built yet -- while actually running
|
||||
# something needs a real answer, and stops with the reason if there isn't
|
||||
# one.
|
||||
_pmn_try_build_dir() {
|
||||
if [ -z "${BUILD_DIR:-}" ]; then
|
||||
BUILD_DIR="$(_pmn_resolve_build_dir "$PRESET" 2>/dev/null || true)"
|
||||
fi
|
||||
}
|
||||
|
||||
list_targets() {
|
||||
local manifest
|
||||
[ -n "${BUILD_DIR:-}" ] || return 0
|
||||
for manifest in "${BUILD_DIR}"/module_manifest-*.txt; do
|
||||
[ -e "$manifest" ] || continue
|
||||
cut -d= -f1 "$manifest"
|
||||
done | sort -u
|
||||
}
|
||||
|
||||
usage() {
|
||||
_pmn_try_build_dir
|
||||
echo "Usage: $0 [--preset <name>] <cmake-target-name> [-- program-args...]" >&2
|
||||
echo >&2
|
||||
echo "Available executable targets:" >&2
|
||||
local targets
|
||||
targets="$(list_targets)"
|
||||
if [ -n "$targets" ]; then
|
||||
echo "$targets" | sed 's/^/ /' >&2
|
||||
else
|
||||
echo " (none found -- did you run scripts/build.sh?)" >&2
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
show_help() {
|
||||
_pmn_try_build_dir
|
||||
echo "Usage: $0 [--preset <name>] <cmake-target-name> [-- program-args...]"
|
||||
echo
|
||||
echo "Runs an executable module by its CMake target name, resolved via the"
|
||||
echo "manifest scripts/build.sh generates at configure time -- see the"
|
||||
echo "comment at the top of this script."
|
||||
echo
|
||||
echo " --preset <name> Run the build made with that preset. Default: the"
|
||||
echo " build scripts/build.sh made last."
|
||||
echo
|
||||
echo "Available executable targets:"
|
||||
local targets
|
||||
targets="$(list_targets)"
|
||||
if [ -n "$targets" ]; then
|
||||
echo "$targets" | sed 's/^/ /'
|
||||
else
|
||||
echo " (none found -- did you run scripts/build.sh?)"
|
||||
fi
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " scripts/run.sh hello_exe"
|
||||
echo " scripts/run.sh --preset linux-x64-clang hello_exe"
|
||||
echo " scripts/run.sh farewell_exe -- Ale"
|
||||
exit 0
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help) show_help ;;
|
||||
esac
|
||||
|
||||
[ $# -ge 1 ] || usage
|
||||
|
||||
TARGET_NAME="$1"
|
||||
shift
|
||||
if [ "${1:-}" = "--" ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ -z "${BUILD_DIR:-}" ]; then
|
||||
# Explains itself and stops if there's nothing to pick.
|
||||
BUILD_DIR="$(_pmn_resolve_build_dir "$PRESET")"
|
||||
fi
|
||||
|
||||
MANIFEST=""
|
||||
for m in "${BUILD_DIR}"/module_manifest-*.txt; do
|
||||
[ -e "$m" ] || continue
|
||||
MANIFEST="$m"
|
||||
break
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "error: no build manifest found in '${BUILD_DIR}'. Run scripts/build.sh${PRESET:+ --preset $PRESET} first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BIN_PATH="$(grep "^${TARGET_NAME}=" "$MANIFEST" | head -n1 | cut -d= -f2- || true)"
|
||||
|
||||
if [ -z "$BIN_PATH" ]; then
|
||||
echo "error: no executable target named '${TARGET_NAME}' in '${BUILD_DIR}'." >&2
|
||||
echo "Available targets:" >&2
|
||||
list_targets | sed 's/^/ /' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$BIN_PATH" ]; then
|
||||
echo "error: '${BIN_PATH}' was not found or is not executable. Rebuild with scripts/build.sh${PRESET:+ --preset $PRESET}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A cross build's toolchain file (cmake/toolchains/) registers an emulator --
|
||||
# qemu-user for another CPU, wine for Windows -- as
|
||||
# CMAKE_CROSSCOMPILING_EMULATOR when one is installed, which is also what
|
||||
# lets ctest run the same binaries. Use it the same way. It's a
|
||||
# ";"-separated list (the emulator, then its arguments).
|
||||
RUN_PREFIX=()
|
||||
if [ -f "${BUILD_DIR}/CMakeCache.txt" ]; then
|
||||
_pmn_emulator="$(sed -n 's/^CMAKE_CROSSCOMPILING_EMULATOR:[A-Z]*=//p' "${BUILD_DIR}/CMakeCache.txt")"
|
||||
if [ -n "$_pmn_emulator" ]; then
|
||||
IFS=';' read -r -a RUN_PREFIX <<< "$_pmn_emulator"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Which build this came from -- to stderr, so a program's own stdout stays
|
||||
# clean to pipe.
|
||||
echo "==> Running ${TARGET_NAME} (${BUILD_DIR})${RUN_PREFIX[0]:+ under ${RUN_PREFIX[0]##*/}}" >&2
|
||||
|
||||
# Not `exec`, so a failure to start at all can be explained below.
|
||||
_pmn_status=0
|
||||
${RUN_PREFIX[@]+"${RUN_PREFIX[@]}"} "$BIN_PATH" "$@" || _pmn_status=$?
|
||||
|
||||
# 126 is the shell's "found it, couldn't execute it" -- which is what running
|
||||
# a binary built for another architecture or OS looks like.
|
||||
if [ "$_pmn_status" -eq 126 ]; then
|
||||
echo "error: '${BIN_PATH}' couldn't be started on this machine. If it is a cross build" >&2
|
||||
echo " (built for another CPU or OS), run it on the target, or install an" >&2
|
||||
echo " emulator -- qemu-user for another CPU, wine for Windows -- and reconfigure" >&2
|
||||
echo " (scripts/build.sh --preset <name>) so the toolchain picks it up." >&2
|
||||
fi
|
||||
exit "$_pmn_status"
|
||||
Executable
+219
@@ -0,0 +1,219 @@
|
||||
@echo off
|
||||
REM One-time (or re-run any time) environment setup for this template.
|
||||
REM Windows; on Linux and macOS it is scripts/setup.sh.
|
||||
REM
|
||||
REM 1. Checks for the tools this project builds with -- Git, CMake 3.25+ and a
|
||||
REM C++ compiler (Visual Studio Build Tools, or MinGW-w64 through MSYS2:
|
||||
REM required), plus LLVM and the other MSYS2 toolchains (optional) -- and
|
||||
REM offers to install whatever is missing, ONE TOOL AT A TIME, showing the
|
||||
REM exact winget command before running it. Nothing is installed without
|
||||
REM your yes. The work is done by scripts\_tools.ps1 (PowerShell: prompts,
|
||||
REM PATH handling and winget are far more robust there than in batch).
|
||||
REM 2. Fetches the pinned ninja (cmake\FetchNinja.cmake) -- used by
|
||||
REM scripts\build.bat if found (see there); a system ninja still
|
||||
REM works fine too, this just avoids needing one installed at all.
|
||||
REM 3. Fetches the pinned Doxygen (cmake\FetchDoxygen.cmake) -- NOT
|
||||
REM required to build the project itself (see cmake\Documentation.cmake),
|
||||
REM only to run scripts\docs.bat afterward.
|
||||
REM 4. Activates the pre-commit hook (.githooks\pre-commit) for this
|
||||
REM clone, if it's a git repository.
|
||||
REM
|
||||
REM A failure in step 2 or 3 (no pinned binary for this machine, no network)
|
||||
REM is reported and setup carries on -- it used to stop there, before the hook
|
||||
REM was activated.
|
||||
REM
|
||||
REM Usage (from the project root):
|
||||
REM scripts\setup.bat [--yes] [--all] [--check] [--skip-tools]
|
||||
REM scripts\setup.bat --help
|
||||
REM
|
||||
REM No delayed expansion in this file, on purpose: PATH is edited below, and
|
||||
REM with delayed expansion on, a "!" in any directory name would be eaten.
|
||||
setlocal
|
||||
|
||||
REM %~dp0 is captured first: a plain SHIFT (used to walk the arguments below)
|
||||
REM shifts %0 as well.
|
||||
set "_PMN_HERE=%~dp0"
|
||||
|
||||
set "_PMN_TOOLS_ARGS="
|
||||
set "_PMN_SKIP_TOOLS=0"
|
||||
set "_PMN_CHECK=0"
|
||||
set "_PMN_TOOLS_RC=0"
|
||||
|
||||
:parse_args
|
||||
if "%~1"=="" goto args_done
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
if /i "%~1"=="-y" goto opt_yes
|
||||
if /i "%~1"=="--yes" goto opt_yes
|
||||
if /i "%~1"=="--all" goto opt_all
|
||||
if /i "%~1"=="--check" goto opt_check
|
||||
if /i "%~1"=="--skip-tools" goto opt_skip_tools
|
||||
echo error: unknown option "%~1" 1>&2
|
||||
echo Run scripts\setup.bat --help for usage. 1>&2
|
||||
exit /b 1
|
||||
|
||||
:opt_yes
|
||||
set "_PMN_TOOLS_ARGS=%_PMN_TOOLS_ARGS% -Yes"
|
||||
shift
|
||||
goto parse_args
|
||||
|
||||
:opt_all
|
||||
set "_PMN_TOOLS_ARGS=%_PMN_TOOLS_ARGS% -All"
|
||||
shift
|
||||
goto parse_args
|
||||
|
||||
:opt_check
|
||||
set "_PMN_TOOLS_ARGS=%_PMN_TOOLS_ARGS% -Check"
|
||||
set "_PMN_CHECK=1"
|
||||
shift
|
||||
goto parse_args
|
||||
|
||||
:opt_skip_tools
|
||||
set "_PMN_SKIP_TOOLS=1"
|
||||
shift
|
||||
goto parse_args
|
||||
|
||||
:show_help
|
||||
echo Usage: scripts\setup.bat [--yes] [--all] [--check] [--skip-tools]
|
||||
echo.
|
||||
echo One-time ^(or re-run any time^) environment setup for this template:
|
||||
echo 1. Checks the tools the project builds with and offers to install what's
|
||||
echo missing, one tool at a time ^(Git, CMake and a C++ compiler are
|
||||
echo required; LLVM and the other MSYS2 toolchains are optional^). Each
|
||||
echo install shows its exact winget command and waits for your yes.
|
||||
echo 2. Fetches the pinned ninja ^(cmake\FetchNinja.cmake^).
|
||||
echo 3. Fetches the pinned Doxygen ^(cmake\FetchDoxygen.cmake^).
|
||||
echo 4. Activates the pre-commit hook ^(.githooks\pre-commit^) for this clone,
|
||||
echo if it's a git repository.
|
||||
echo.
|
||||
echo Options:
|
||||
echo -y, --yes Install the REQUIRED tools without asking. For scripts and
|
||||
echo CI; without a terminal and without this, nothing is asked
|
||||
echo and nothing is installed.
|
||||
echo --all Also install the optional tools without asking ^(implies
|
||||
echo --yes^).
|
||||
echo --check Only report what is and isn't installed; change nothing,
|
||||
echo fetch nothing. Exits 1 if a required tool is missing.
|
||||
echo --skip-tools Skip step 1 and go straight to the pinned downloads.
|
||||
echo.
|
||||
echo At each question: y install it n not this one a install everything
|
||||
echo left s skip the rest of the optional tools q stop asking.
|
||||
echo.
|
||||
echo See the comment at the top of this script for more on each step.
|
||||
exit /b 0
|
||||
|
||||
:args_done
|
||||
cd /d "%_PMN_HERE%.."
|
||||
|
||||
REM ---- 1. tools --------------------------------------------------------------
|
||||
if "%_PMN_SKIP_TOOLS%"=="1" goto tools_done
|
||||
where powershell >nul 2>nul
|
||||
if errorlevel 1 goto no_powershell
|
||||
|
||||
set "_PMN_PATHFILE=%TEMP%\pmn-path-%RANDOM%%RANDOM%.txt"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%_PMN_HERE%_tools.ps1" %_PMN_TOOLS_ARGS% -PathOut "%_PMN_PATHFILE%"
|
||||
set "_PMN_TOOLS_RC=%ERRORLEVEL%"
|
||||
|
||||
REM The installer adds tool folders (CMake, Git, ...) to ITS copy of PATH so
|
||||
REM it can use them; a child process can't change ours, so it hands the list
|
||||
REM back in a file and they're appended here, for the steps below.
|
||||
if exist "%_PMN_PATHFILE%" call :take_path_additions
|
||||
echo.
|
||||
goto tools_done
|
||||
|
||||
:no_powershell
|
||||
echo error: powershell.exe not found on PATH -- it runs the tool installer. 1>&2
|
||||
echo Install the tools yourself and re-run with --skip-tools. 1>&2
|
||||
exit /b 1
|
||||
|
||||
:tools_done
|
||||
if not "%_PMN_CHECK%"=="1" goto after_check
|
||||
if "%_PMN_TOOLS_RC%"=="0" goto check_ok
|
||||
echo ==^> Required tools are missing. Run scripts\setup.bat to install them.
|
||||
exit /b %_PMN_TOOLS_RC%
|
||||
:check_ok
|
||||
echo ==^> All required tools are present.
|
||||
exit /b 0
|
||||
:after_check
|
||||
|
||||
REM Everything below runs CMake scripts.
|
||||
where cmake >nul 2>nul
|
||||
if errorlevel 1 goto no_cmake
|
||||
goto have_cmake
|
||||
:no_cmake
|
||||
echo error: cmake not found on PATH -- nothing below can run without it. 1>&2
|
||||
if "%_PMN_SKIP_TOOLS%"=="1" goto no_cmake_skip
|
||||
echo Install CMake ^(3.25 or newer^), open a new terminal, and re-run this script. 1>&2
|
||||
exit /b 1
|
||||
:no_cmake_skip
|
||||
echo Re-run without --skip-tools to be offered an install. 1>&2
|
||||
exit /b 1
|
||||
:have_cmake
|
||||
|
||||
REM ---- 2. pinned ninja -------------------------------------------------------
|
||||
echo ==^> Fetching pinned ninja (cmake\FetchNinja.cmake)
|
||||
cmake -P cmake\FetchNinja.cmake
|
||||
if errorlevel 1 goto ninja_failed
|
||||
goto ninja_done
|
||||
:ninja_failed
|
||||
echo.
|
||||
echo No pinned ninja here ^(no upstream binary for this machine, or the
|
||||
echo download failed -- see above^).
|
||||
where ninja >nul 2>nul
|
||||
if errorlevel 1 goto ninja_missing
|
||||
echo The ninja on PATH will be used instead.
|
||||
goto ninja_done
|
||||
:ninja_missing
|
||||
echo scripts\build.bat needs a ninja: e.g. winget install Ninja-build.Ninja
|
||||
:ninja_done
|
||||
|
||||
REM ---- 3. pinned Doxygen -----------------------------------------------------
|
||||
echo.
|
||||
echo ==^> Fetching pinned Doxygen (cmake\FetchDoxygen.cmake)
|
||||
cmake -P cmake\FetchDoxygen.cmake
|
||||
if errorlevel 1 goto doxygen_failed
|
||||
goto doxygen_done
|
||||
:doxygen_failed
|
||||
echo.
|
||||
echo Documentation ^(scripts\docs.bat^) won't be available: there's no pinned
|
||||
echo Doxygen for this machine, or the download failed -- see above.
|
||||
echo The project itself builds fine without it.
|
||||
:doxygen_done
|
||||
|
||||
REM ---- 4. pre-commit hook ----------------------------------------------------
|
||||
echo.
|
||||
git rev-parse --is-inside-work-tree >nul 2>nul
|
||||
if errorlevel 1 goto hook_skip
|
||||
echo ==^> Activating pre-commit hook
|
||||
git config core.hooksPath .githooks
|
||||
echo core.hooksPath = .githooks
|
||||
goto hook_done
|
||||
:hook_skip
|
||||
echo ==^> Skipping pre-commit hook activation ^(not a git repository yet^)
|
||||
echo Once you've run "git init", re-run this script or:
|
||||
echo git config core.hooksPath .githooks
|
||||
:hook_done
|
||||
|
||||
echo.
|
||||
if "%_PMN_TOOLS_RC%"=="0" goto setup_ok
|
||||
echo ==^> Setup finished, but a required tool is still missing -- see above.
|
||||
exit /b 1
|
||||
:setup_ok
|
||||
echo ==^> Setup complete. scripts\build.bat and scripts\docs.bat are ready to use.
|
||||
exit /b 0
|
||||
|
||||
REM ---------------------------------------------------------------------
|
||||
REM :take_path_additions
|
||||
REM
|
||||
REM Appends every directory listed in %_PMN_PATHFILE% to PATH, then deletes
|
||||
REM the file. One "for /f ... do call" line, deliberately not a parenthesized
|
||||
REM block: a directory like "C:\Program Files (x86)\..." would close it early.
|
||||
REM ---------------------------------------------------------------------
|
||||
:take_path_additions
|
||||
for /f "usebackq delims=" %%D in ("%_PMN_PATHFILE%") do call :add_path "%%D"
|
||||
del "%_PMN_PATHFILE%" >nul 2>nul
|
||||
goto :eof
|
||||
|
||||
:add_path
|
||||
set "PATH=%PATH%;%~1"
|
||||
goto :eof
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-time (or re-run any time) environment setup for this template. Linux
|
||||
# and macOS; on Windows it is scripts\setup.bat.
|
||||
#
|
||||
# 1. Checks for the tools this project builds with -- git, CMake 3.25+ and a
|
||||
# C++ compiler (both of which are required), and Clang, clang-format,
|
||||
# clang-tidy, cross compilers, MinGW and emulators (all optional) -- and
|
||||
# offers to install whatever is missing, ONE TOOL AT A TIME, showing
|
||||
# the exact command before running it. Nothing is installed without
|
||||
# your yes. See scripts/_tools.sh for what it knows and how.
|
||||
# 2. Fetches the pinned ninja (cmake/FetchNinja.cmake) -- used by
|
||||
# scripts/build.sh / build.bat if found (see there); a system ninja
|
||||
# still works fine too, this just avoids needing one installed at all.
|
||||
# Where upstream ships no ninja binary for your machine, it offers to
|
||||
# install one through your package manager instead.
|
||||
# 3. Fetches the pinned Doxygen (cmake/FetchDoxygen.cmake) -- NOT
|
||||
# required to build the project itself (see cmake/Documentation.cmake),
|
||||
# only to run scripts/docs.sh / docs.bat afterward.
|
||||
# 4. Activates the pre-commit hook (.githooks/pre-commit) for this
|
||||
# clone, if it's a git repository.
|
||||
#
|
||||
# A failure in step 2 or 3 (no pinned binary for this machine, no network)
|
||||
# is reported and setup carries on -- it used to abort here, before the hook
|
||||
# was activated.
|
||||
#
|
||||
# Usage (from the project root):
|
||||
# scripts/setup.sh [--yes] [--all] [--check] [--skip-tools]
|
||||
# scripts/setup.sh --help
|
||||
set -euo pipefail
|
||||
|
||||
_usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/setup.sh [--yes] [--all] [--check] [--skip-tools]
|
||||
|
||||
One-time (or re-run any time) environment setup for this template:
|
||||
1. Checks the tools the project builds with and offers to install what's
|
||||
missing, one tool at a time (git, CMake and a C++ compiler are
|
||||
required; Clang, clang-format/clang-tidy, cross compilers, MinGW and
|
||||
emulators are optional). Each install shows its exact command and
|
||||
waits for your yes.
|
||||
2. Fetches the pinned ninja (cmake/FetchNinja.cmake).
|
||||
3. Fetches the pinned Doxygen (cmake/FetchDoxygen.cmake).
|
||||
4. Activates the pre-commit hook (.githooks/pre-commit) for this clone,
|
||||
if it's a git repository.
|
||||
|
||||
Options:
|
||||
-y, --yes Install the REQUIRED tools without asking. For scripts and
|
||||
CI; without a terminal and without this, nothing is asked
|
||||
and nothing is installed.
|
||||
--all Also install the optional tools without asking (implies
|
||||
--yes).
|
||||
--check Only report what is and isn't installed; change nothing,
|
||||
fetch nothing. Exits 1 if a required tool is missing.
|
||||
--skip-tools Skip step 1 and go straight to the pinned downloads.
|
||||
|
||||
At each question: y install it n not this one a install everything
|
||||
left s skip the rest of the optional tools q stop asking.
|
||||
|
||||
See the comment at the top of this script for more on each step.
|
||||
EOF
|
||||
}
|
||||
|
||||
PMN_TOOLS_YES=0
|
||||
PMN_TOOLS_ALL=0
|
||||
PMN_TOOLS_CHECK=0
|
||||
SKIP_TOOLS=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) _usage; exit 0 ;;
|
||||
-y|--yes) PMN_TOOLS_YES=1 ;;
|
||||
--all) PMN_TOOLS_ALL=1; PMN_TOOLS_YES=1 ;;
|
||||
--check) PMN_TOOLS_CHECK=1 ;;
|
||||
--skip-tools) SKIP_TOOLS=1 ;;
|
||||
*)
|
||||
echo "error: unknown option '$1'" >&2
|
||||
echo "Run scripts/setup.sh --help for usage." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
export PMN_TOOLS_YES PMN_TOOLS_ALL PMN_TOOLS_CHECK
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
# _pmn_tools_run, _pmn_tools_offer and their helpers.
|
||||
source "$SCRIPT_DIR/_tools.sh"
|
||||
|
||||
# ---- 1. tools --------------------------------------------------------------
|
||||
_tools_rc=0
|
||||
if [ "$SKIP_TOOLS" = 0 ]; then
|
||||
_pmn_tools_run || _tools_rc=$?
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ "$PMN_TOOLS_CHECK" = 1 ]; then
|
||||
if [ "$_tools_rc" -ne 0 ]; then
|
||||
echo "==> Required tools are missing. Run scripts/setup.sh to install them."
|
||||
else
|
||||
echo "==> All required tools are present."
|
||||
fi
|
||||
exit "$_tools_rc"
|
||||
fi
|
||||
|
||||
# Everything below runs CMake scripts.
|
||||
if ! command -v cmake >/dev/null 2>&1; then
|
||||
echo "error: cmake not found on PATH -- nothing below can run without it." >&2
|
||||
if [ "$SKIP_TOOLS" = 1 ]; then
|
||||
echo " Re-run without --skip-tools to be offered an install." >&2
|
||||
else
|
||||
echo " Install CMake (3.25 or newer), open a new terminal, and re-run this script." >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- 2. pinned ninja -------------------------------------------------------
|
||||
echo "==> Fetching pinned ninja (cmake/FetchNinja.cmake)"
|
||||
if ! cmake -P cmake/FetchNinja.cmake; then
|
||||
echo
|
||||
echo " No pinned ninja here (no upstream binary for this machine, or the"
|
||||
echo " download failed -- see above)."
|
||||
if command -v ninja >/dev/null 2>&1; then
|
||||
echo " The ninja on PATH will be used instead: $(command -v ninja)"
|
||||
elif [ "$SKIP_TOOLS" = 0 ]; then
|
||||
# A build needs SOME ninja; this is the machine-specific moment to say so.
|
||||
_pmn_tools_offer ninja || echo " scripts/build.sh needs a ninja: install one and put it on PATH."
|
||||
else
|
||||
echo " scripts/build.sh needs a ninja: install one and put it on PATH."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- 3. pinned Doxygen -----------------------------------------------------
|
||||
echo
|
||||
echo "==> Fetching pinned Doxygen (cmake/FetchDoxygen.cmake)"
|
||||
if ! cmake -P cmake/FetchDoxygen.cmake; then
|
||||
echo
|
||||
echo " Documentation (scripts/docs.sh) won't be available: there's no pinned"
|
||||
echo " Doxygen for this machine, or the download failed -- see above."
|
||||
echo " The project itself builds fine without it."
|
||||
fi
|
||||
|
||||
# ---- 4. pre-commit hook ----------------------------------------------------
|
||||
echo
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
echo "==> Activating pre-commit hook"
|
||||
chmod +x .githooks/pre-commit
|
||||
git config core.hooksPath .githooks
|
||||
echo " core.hooksPath = .githooks"
|
||||
else
|
||||
echo "==> Skipping pre-commit hook activation (not a git repository yet)"
|
||||
echo " Once you've run 'git init', re-run this script or:"
|
||||
echo " chmod +x .githooks/pre-commit && git config core.hooksPath .githooks"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$_tools_rc" -ne 0 ]; then
|
||||
echo "==> Setup finished, but a required tool is still missing -- see above."
|
||||
exit 1
|
||||
fi
|
||||
echo "==> Setup complete. scripts/build.sh and scripts/docs.sh are ready to use."
|
||||
Reference in new issue
Block a user