# Author: B. Scott Michel (scooter.phd@gmail.com) # "scooter me fecit" <# .SYNOPSIS Configure and build SIMH's dependencies and simulators using the Microsoft Visual Studio C compiler or MinGW-W64-based gcc compiler. .DESCRIPTION This script executes the three (3) phases of building the entire suite of SIMH simulators using the CMake meta-build tool. The phases are: 1. Configure and generate the build environment selected by '-flavor' option. 2. Build missing runtime dependencies and the simulator suite with the compiler configuration selected by the '-config' option. The "Release" configuration generates optimized executables; the "Debug" configuration generates development executables with debugger information. 3. Test the simulators There is an install phase that can be invoked separately as part of the SIMH packaging process. The test and install phases can be enabled or disabled by the appropriate command line flag (e.g., '-noInstall', '-noTest', '-testOnly', '-installOnly'.) Build environment and artifact locations: ----------------------------------------- cmake/build-vs* MSVC build products and artifacts cmake/build-mingw MinGW-W64 products and artifacts cmake/build-ninja Ninja builder products and artifacts .EXAMPLE PS> cmake-builder.ps1 -flavor vs2022 -config Release Generate/configure, build, test and install the SIMH simulator suite using the Visual Studio 2022 toolchain in the Release (optimized) compile configuration. .EXAMPLE PS> cmake-builder.ps1 vs2022 Release Another way to generate/configure, build, test and install the SIMH simulator suite using the Visual Studio 2022 toolchain in the Release (optimized) compile configuration. .EXAMPLE PS> cmake-builder.ps1 vs2019 Debug -notest -noinstall Generate/configure and build the SIMH simulator suite with the Visual Studio 2019 toolchain in the Debug compile configuration. Does not execute tests and does not install the simulators under the BIN subdirectory in the top of the source tree. .EXAMPLE PS> cmake-builder.ps1 -flavor vs2019 -config Release -installonly Install the simulators under the BIN subdirectory in the top of the source tree. Does not generate/configure, but will build to ensure that compile targets (simulator executables) are up-to-date. #> param ( ## String arguments are positional, so if the user invokes this script ## as "cmake-builder.ps1 vs2022 Debug", it's the same as saying ## "cmake-builder.ps1 -flavor vs2022 -config Debug" ## The build environment's "flavor" that determines which CMake generator is used ## to create all of the build machinery to compile the SIMH simulator suite ## and the target compiler. ## ## Supported flavors: ## ------------------ ## vs2022 Visual Studio 2022 (default) ## vs2022-xp Visual Studio 2022 XP compat ## vs2022-x64 Visual Studio 2022 64-bit ## vs2019 Visual Studio 2019 ## vs2019-xp Visual Studio 2019 XP compat ## vs2019-x64 Visual Studio 2019 64-bit ## vs2017 Visual Studio 2017 ## vs2017-xp Visual Studio 2017 XP compat ## vs2017-x64 Visual Studio 2017 64-bit ## vs2015 Visual Studio 2015 ## mingw-make MinGW GCC/mingw32-make ## mingw-ninja MinGW GCC/ninja [Parameter(Mandatory=$false)] [string] $flavor = "vs2022", ## The target build configuration. Valid values: "Release" and "Debug" [Parameter(Mandatory=$false)] [string] $config = "Release", ## Supply a suffix for CPack package names via -DSIMH_PACKAGE_SUFFIX [Parameter(Mandatory=$false)] [string] $cpack_suffix = "", ## (optional) Build a specific simulator or simulators. Separate multiple ## targets with a comma, ## e.g. "--target pdp8,pdp11,vax750,altairz80,3b2" [Parameter(Mandatory=$false)] [string[]] $target = "", ## The rest are flag arguments ## Clean (remove) the CMake build directory before configuring [Parameter(Mandatory=$false)] [switch] $clean = $false, ## Get help. [Parameter(Mandatory=$false)] [switch] $help = $false, ## Compile the SIMH simulator suite without network support. [Parameter(Mandatory=$false)] [switch] $nonetwork = $false, ## Compile the SIMH simulator suite without video support. [Parameter(Mandatory=$false)] [switch] $novideo = $false, ## Compile the SIMH simulator without AIO support. [Parameter(Mandatory=$false)] [switch] $noaio = $false, ## Compile the SIMH simulator without AIO instrinsics ("lock-free" AIO), ## using lock-based AIO via thread mutexes instead. [Parameter(Mandatory=$false)] [switch] $noaiointrinsics = $false, ## Disable the build's tests. [Parameter(Mandatory=$false)] [switch] $notest = $false, ## Do not install the simulator suite in the source directory's BIN ## subdirectory. [Parameter(Mandatory=$false)] [switch] $noinstall = $false, ## Enable parallel builds. [Parameter(Mandatory=$false)] [switch] $parallel = $false, ## Configure and generate the build environment. Don't compile, test or install. [Parameter(Mandatory=$false)] [switch] $generate = $false, ## Only run the tests. [Parameter(Mandatory=$false)] [switch] $testonly = $false, ## Only install the SIMH simulator suite in the source directory's BIN ## subdirectory. [Parameter(Mandatory=$false)] [switch] $installOnly = $false, ## Turn on Windows API deprecation warnings. NOTE: These warnings are OFF by ## default. [Parameter(Mandatory=$false)] [switch] $windeprecation = $false, ## Enable Link-Time Optimization (LTO). [Parameter(Mandatory=$false)] [switch] $lto = $false, ## Turn on maximal compiler warnings for Debug builds (e.g. "-Wall" or "/W3") [Parameter(Mandatory=$false)] [switch] $debugWall = $false, ## Enable the cppcheck static code analysis rules [Parameter(Mandatory=$false)] [switch] $cppcheck = $false ) $scriptName = $(Split-Path -Leaf $PSCommandPath) $scriptCmd = ${PSCommandPath} function Show-Help { Get-Help -full ${scriptCmd} exit 0 } ## CMake generator info: class GeneratorInfo { [string] $Generator [bool] $SingleConfig [bool] $UCRT [string] $UCRTVersion [string[]]$ArchArgs GeneratorInfo([string]$gen, $configFlag, $ucrtFlag, $ucrtVer, [string[]]$arch) { $this.Generator = $gen $this.SingleConfig = $configFlag $this.UCRT = $ucrtFlag $this.UCRTVersion = $ucrtVer $this.ArchArgs = $arch } } ## Multiple build configurations selected at compile time $multiConfig = $false ## Single configuration selected at configuration time $singleConfig = $true $cmakeGenMap = @{ "vs2022" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "Win32")); "vs2022-xp" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "Win32", "-T", "v141_xp")); "vs2022-x64" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "x64", "-T", "host=x64")); "vs2019" = [GeneratorInfo]::new("Visual Studio 16 2019", $multiConfig, $false, "", @("-A", "Win32")); "vs2019-xp" = [GeneratorInfo]::new("Visual Studio 16 2019", $multiConfig, $false, "", @("-A", "Win32", "-T", "v141_xp")); "vs2019-x64" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "x64", "-T", "host=x64")); "vs2017" = [GeneratorInfo]::new("Visual Studio 15 2017", $multiConfig, $false, "", @("-A", "Win32")); "vs2017-xp" = [GeneratorInfo]::new("Visual Studio 15 2017", $multiConfig, $false, "", @("-A", "Win32", "-T", "v141_xp")); "vs2017-x64" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "x64", "-T", "host=x64")); "vs2015" = [GeneratorInfo]::new("Visual Studio 14 2015", $multiConfig, $false, "", @()); "mingw-make" = [GeneratorInfo]::new("MinGW Makefiles", $singleConfig, $false, "", @()); "mingw-ninja" = [GeneratorInfo]::new("Ninja", $singleConfig, $false, "", @()) } function Get-GeneratorInfo([string]$flavor) { return $cmakeGenMap[$flavor] } function Quote-Args([string[]]$arglist) { return ($arglist | foreach-object { if ($_ -like "* *") { "`"$_`"" } else { $_ } }) } ## Output help early and exit. if ($help) { Show-Help } ### CTest params: ## timeout is 180 seconds $ctestTimeout = "300" ## Sanity checking: Check that utilities we expect exist... ## CMake: Save the location of the command because we'll invoke it later. Same ## with CTest $cmakeCmd = $(Get-Command -Name cmake.exe -ErrorAction Ignore).Path $ctestCmd = $(Get-Command -Name ctest.exe -ErrorAction Ignore).Path if ($cmakeCmd.Length -gt 0) { Write-Host "** ${scriptName}: cmake is '${cmakeCmd}'" Write-Host "** $(& ${cmakeCmd} --version)" } else { @" !! ${scriptName} error: The 'cmake' command was not found. Please ensure that you have installed CMake and that your PATH environment variable references the directory in which it was installed. "@ exit 1 } if (!$testonly) { ## Check for GCC and mingw32-make if user wants the mingw flavor build. if ($flavor -eq "mingw" -or $flavor -eq "ninja") { if ($(Get-Command gcc -ErrorAction Ignore).Path.Length -eq 0) { @" !! ${scriptName} error: Did not find 'gcc', the GNU C/C++ compiler toolchain. Please ensure you have installed gcc and that your PATH environment variables references the directory in which it was installed. "@ exit 1 } if ($(Get-Command mingw32-make -ErrorAction Ignore).Path.Length -eq 0) { @" !! ${scriptName} error: Did not find 'mingw32-make'. Please ensure you have installed mingw32-make and that your PATH environment variables references the directory in which it was installed. See the .travis/deps.sh functions mingw64() and ucrt64() for the pacman packages that should be installed. "@ exit 1 } } } ## Validate the requested configuration. if (!@("Release", "Debug").Contains($config)) { @" ${scriptName}: Invalid configuration: "${config}". "@ Show-Help } ## Look for Git's /usr/bin subdirectory: CMake (and other utilities) have issues ## with the /bin/sh installed there (Git's version of MinGW.) $tmp_path = $env:PATH $git_usrbin = "${env:ProgramFiles}\Git\usr\bin" $tmp_path = ($tmp_path.Split(';') | Where-Object { $_ -ne "${git_usrbin}"}) -join ';' if ($tmp_path -ne ${env:PATH}) { Write-Host "** ${scriptName}: Removed ${git_usrbin} from PATH (Git MinGW problem)" $env:PATH = $tmp_path } ## Also make sure that none of the other cmake-* directories are in the user's PATH ## because CMake's find_package does traverse PATH looking for potential candidates ## for dependency libraries. $origPath = $env:PATH $modPath = $origPath if (Test-Path -Path cmake\dependencies) { $bdirs = $(Get-ChildItem -Attribute Directory cmake\dependencies\*).ForEach({ $_.FullName + "\bin" }) $modPath = (${env:Path}.Split(';') | Where-Object { $bdirs -notcontains $_ }) -join ';' if ($modPath -ne $origPath) { Write-Host "** ${scriptName}: Removed cmake\dependencies 'bin' directories from PATH." } } ## Setup: $simhTopDir = $(Split-Path -Parent $(Resolve-Path -Path $PSCommandPath).Path) While (!([String]::IsNullOrEmpty($simhTopDir) -or (Test-Path -Path ${simhTopDir}\CMakeLists.txt))) { $simhTopDir = $(Split-Path -Parent $simhTopDir) } if ([String]::IsNullOrEmpty($simhTopDir)) { @" !! ${scriptName}: Cannot locate SIMH top-level source directory from the script's path name. You should really not see this message. "@ exit 1 } else { Write-Host "** ${scriptName}: SIMH top-level source directory is ${simhTopDir}" } $buildDir = "${simhTopDir}\cmake\build-${flavor}" $genInfo = $(Get-GeneratorInfo $flavor) if ($null -eq $genInfo) { Write-Host "" Write-Host "!! ${scriptName}: Unrecognized build flavor '${flavor}'." Write-Host "" Show-Help } if ($testonly) { $scriptPhases = @("test") } elseif ($generate) { $scriptPhases = @("generate") } elseif ($installOnly) { $scriptPhases = @("install") } else { $scriptPhases = @( "generate", "build", "test") if ($notest) { $scriptPhases = $scriptPhases | Where-Object { $_ -ne 'test' } } if ($noinstall -or ![String]::IsNullOrEmpty($target)) { $scriptPhases = $scriptPhases | Where-Object { $_ -ne 'install' } } } if (($scriptPhases -contains "generate") -or ($scriptPhases -contains "build")) { ## Clean out the build subdirectory if ((Test-Path -Path ${buildDir}) -and $clean) { Write-Host "** ${scriptName}: Removing ${buildDir}" Remove-Item -recurse -force -Path ${buildDir} -ErrorAction SilentlyContinue | Out-Null } if (!(Test-Path -Path ${buildDir})) { Write-Host "** ${scriptName}: Creating ${buildDir} subdirectory" New-Item -Path ${buildDir} -ItemType Directory | Out-Null } else { Write-Host "** ${scriptName}: ${buildDir} exists." } ## Unconditionally remove the CMake cache. Remove-Item -Force -Path ${buildDir}/CMakeCache.txt -ErrorAction SilentlyContinue | Out-Null Remove-Item -Recurse -Force -Path ${buildDir}/CMakeFiles -ErrorAction SilentlyContinue | Out-Null ## Where we do the heaving lifting: $generateArgs = @("-G", $genInfo.Generator) if ($genInfo.SingleConfig) { ## Single configuration set at compile time: $generateArgs += @("-DCMAKE_BUILD_TYPE=${config}") } if ($genInfo.UCRT) { ## Universal Windows Platform $generateArgs += @("-DCMAKE_SYSTEM_NAME=WindowsStore", "-DCMAKE_SYSTEM_VERSION=$($genInfo.UCRTVersion)") } $generateArgs += $genInfo.ArchArgs + @("-Wno-dev", "--no-warn-unused-cli") if ($nonetwork) { $generateArgs += @("-DWITH_NETWORK:Bool=Off") } if ($novideo) { $generateArgs += @("-DWITH_VIDEO:Bool=Off") } if ($noaio) { $generateArgs += @("-DWITH_ASYNC:Bool=Off") } if ($noaiointrinsics) { $generateArgs += @("-DDONT_USE_AIO_INTRINSICS:Bool=On") } if ($lto) { $generateArgs += @("-DRELEASE_LTO:Bool=On") } if ($debugWall) { $generateArgs += @("-DDEBUG_WALL:Bool=On") } if ($cppcheck) { $generateArgs += @("-DENABLE_CPPCHECK:Bool=On") } if (![String]::IsNullOrEmpty($cpack_suffix)) { $generateArgs += @("-DSIMH_PACKAGE_SUFFIX:Bool=${cpack_suffix}") } $buildArgs = @("--build", "${buildDir}", "--config", "${config}") if ($parallel) { $buildArgs += "--parallel" } if ($verbose) { $buildArgs += "--verbose" } if ($windeprecation) { $buildArgs += "-DWINAPI_DEPRECATION:Bool=TRUE" } if (![String]::IsNullOrEmpty($target)) { foreach ($targ in $target) { $buildArgs += @("--target", "$targ") } } $buildSpecificArgs = @() if ($flavor -eq "mingw" -and $parallel) { ## Limit the number of parallel jobs mingw32-make can spawn. Otherwise ## it'll overwhelm the machine. $buildSpecificArgs += @("-j", "8") } } $exitval = 0 foreach ($phase in $scriptPhases) { $savedPATH = $env:PATH $argList = @() $phaseCommand = "Write-Output" switch -exact ($phase) { "generate" { $generateArgs += @("-S", ${simhTopDir}) $generateArgs += @("-B", ${buildDir}) Write-Host "** ${scriptName}: Configuring and generating" $phaseCommand = ${cmakeCmd} $argList = $generateArgs } "build" { Write-Host "** ${scriptName}: Building simulators." $phaseCommand = ${cmakeCmd} $argList = $buildArgs + $buildSpecificArgs } "test" { Write-Host "** ${scriptName}: Testing simulators." ## CTest arguments: $testArgs = @("-C", $config, "--timeout", $ctestTimeout, "-T", "test", "--output-on-failure") ## Output gets confusing (and tests can time out when executing in parallel) ## if ($parallel) ## { ## $testArgs += @("--parallel", $ctestParallel) ## } if ($verbose) { $testArgs += @("--verbose") } if (![String]::IsNullOrEmpty($target)) { $tests = "simh-(" + ($target -join "|") + ")`$" $testArgs += @("-R", $tests) } $phaseCommand = ${ctestCmd} $argList = $testArgs $env:PATH = $modPath $depTopDir = $(& $cmakeCmd -L -N ${buildDir} | Select-String "SIMH_DEP_TOPDIR") if ($depTopDir) { ## RHS of the cached variable's value. $depTopDir = $depTopDir.Line.Split('=')[1] $env:PATH = "${depTopdir}\bin;${env:PATH}" } } "install" { Write-Host "** ${scriptName}: Installing simulators." $installPrefix = $(& $cmakeCmd -L -N ${buildDir} | Select-String "CMAKE_INSTALL_PREFIX") $installPrefix = $installPrefix.Line.Split('=')[1] $installPath = $installPrefix Write-Host "** ${scriptName}: Install directory ${installPath}" if (!(Test-Path -Path ${installPath})) { Write-Host "** ${scriptName}: Creating ${installPath}" New-Item -${installPath} -ItemType Directory -ErrorAction SilentlyContinue } $phaseCommand = ${cmakeCmd} $argList = @( "--install", "${buildDir}", "--config", "${config}") } } try { Push-Location ${buildDir} Write-Host "** ${phaseCommand} $(Quote-Args ${argList})" & $phaseCommand @arglist if ($LastExitCode -gt 0) { $printPhase = (Get-Culture).TextInfo.ToTitleCase($phase) Write-Error $("${printPhase} phase exited with non-zero status: " + $LastExitCode) exit 1 } } catch { Write-Host "Error running '${phaseCommand} ${argList}' command: $($_.Exception.Message)" -ForegroundColor Red throw $_ } finally { Pop-Location } $env:PATH = $savedPATH } exit $exitval