From 01d6efe5b7381110cd75cca93f5962b80f1f6e15 Mon Sep 17 00:00:00 2001 From: Mark Schofield <34426337+MarkSchofield@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:10:42 -0700 Subject: [PATCH] Canonicalize -Preset,-Configuration; wildcard support --- PSCMake/Common/CMake.ps1 | 16 +++ PSCMake/PSCMake.psm1 | 132 +++++++++++-------- Tests/Build-CMakeBuild.Tests.ps1 | 39 +++++- Tests/BuildConfigurationsCompleter.Tests.ps1 | 4 +- 4 files changed, 131 insertions(+), 60 deletions(-) diff --git a/PSCMake/Common/CMake.ps1 b/PSCMake/Common/CMake.ps1 index 03f3c90..e61f9d6 100644 --- a/PSCMake/Common/CMake.ps1 +++ b/PSCMake/Common/CMake.ps1 @@ -37,6 +37,22 @@ $CMakeCandidates = @( } ) +<# + .Synopsis + Invokes CMake. + + .Description + A function wrapping calls to CMake, allowing the calls to be mocked for testing. +#> +function InvokeCMake { + param( + [string] $CMakePath, + [string[]] $Arguments + ) + Write-Verbose "CMake Arguments: $Arguments" + & $CMakePath @Arguments +} + <# .Synopsis Finds the root of the CMake build - the current or ancestral folder containing a 'CMakePresets.json' file. diff --git a/PSCMake/PSCMake.psm1 b/PSCMake/PSCMake.psm1 index 358529b..5eca240 100644 --- a/PSCMake/PSCMake.psm1 +++ b/PSCMake/PSCMake.psm1 @@ -97,14 +97,14 @@ function BuildTargetsCompleter { $null = $CommandAst $CMakePresetsJson = GetCMakePresets -Silent $PresetNames = GetBuildPresetNames $CMakePresetsJson - $PresetName = $FakeBoundParameters['Presets'] ?? $PresetNames | + $PresetName = $FakeBoundParameters['Preset'] ?? $PresetNames | Select-Object -First 1 $BuildPreset, $ConfigurePreset = ResolvePresets $CMakePresetsJson 'buildPresets' $PresetName $BinaryDirectory = GetBinaryDirectory $CMakePresetsJson $ConfigurePreset $CMakeCodeModel = Get-CMakeBuildCodeModel $BinaryDirectory # TODO: See if the $BuildPreset has a configuration. - $ConfigurationName = $FakeBoundParameters['Configurations'] ?? $CMakeCodeModel.configurations.Name | + $ConfigurationName = $FakeBoundParameters['Configuration'] ?? $CMakeCodeModel.configurations.Name | Select-Object -First 1 $ConfigurationsJson = $CMakeCodeModel.configurations | Where-Object -Property 'name' -EQ $ConfigurationName @@ -166,7 +166,7 @@ function ExecutableTargetsCompleter { <# .Synopsis - An argument-completer for `Configure-CMakeBuild`'s `-Presets` parameter. + An argument-completer for `Configure-CMakeBuild`'s `-Preset` parameter. #> function ConfigurePresetsCompleter { param( @@ -209,9 +209,8 @@ function ConfigureCMake { '--log-level=VERBOSE' } ) - Write-Verbose "CMake Arguments: $CMakeArguments" - & $CMake @CMakeArguments + InvokeCMake $CMake $CMakeArguments if ($LASTEXITCODE -ne 0) { Write-Error "Configuration failed. Command line: '$($CMake.Source)' $($CMakeArguments -join ' ')" } @@ -224,41 +223,46 @@ function ConfigureCMake { .Description Configures the specified 'configurePresets' entries from a CMakePresets.json file in the current-or-higher folder. - .Parameter Presets - The configure preset names to use. + .Parameter Preset + The configure preset name to use. Multiple presets can be specified. .Parameter Fresh A switch specifying whether a 'fresh' configuration is performed - removing any existing cache. .Example # Configure the 'windows-x64' and 'windows-x86' CMake builds. - Configure-CMakeBuild -Presets windows-x64,windows-x86 + Configure-CMakeBuild -Preset windows-x64,windows-x86 #> function Configure-CMakeBuild { [CmdletBinding()] param( + [Alias('Presets')] + [SupportsWildcards()] [Parameter()] - [string[]] $Presets, + [string[]] $Preset, [Parameter()] [switch] $Fresh ) $CMakeRoot = FindCMakeRoot $CMakePresetsJson = GetCMakePresets - $PresetNames = GetConfigurePresetNames $CMakePresetsJson - if (-not $Presets) { - $Presets = $PresetNames | Select-Object -First 1 - Write-Information "No preset specified, defaulting to: $Presets" + $ConfigurePresetNames = GetConfigurePresetNames $CMakePresetsJson + $ConfigurePresetNames = if (-not $Preset) { + $ConfigurePresetNames | Select-Object -First 1 + } else { + foreach ($CandidatePreset in $Preset) { + $ConfigurePresetNames | Where-Object { $_ -like $CandidatePreset } + } } $CMake = GetCMake Using-Location $CMakeRoot { - foreach ($Preset in $Presets) { - Write-Output "Preset : $Preset" + foreach ($ConfigurePresetName in $ConfigurePresetNames) { + Write-Output "Preset : $ConfigurePresetName" - $ConfigurePreset = $CMakePresetsJson.configurePresets | Where-Object { $_.name -eq $Preset } + $ConfigurePreset = $CMakePresetsJson.configurePresets | Where-Object { $_.name -eq $ConfigurePresetName } if (-not $ConfigurePreset) { - Write-Error "Unable to find configuration preset '$Preset' in $script:CMakePresetsPath" + Write-Error "Unable to find configuration preset '$ConfigurePresetName' in $script:CMakePresetsPath" } ConfigureCMake -CMake $CMake $CMakePresetsJson $ConfigurePreset -Fresh:$Fresh @@ -273,11 +277,11 @@ function Configure-CMakeBuild { .Description Builds the specified 'buildPresets' entries from a CMakePresets.json file in the current-or-higher folder. - .Parameter Presets + .Parameter Preset - .Parameter Configurations + .Parameter Configuration - .Parameter Targets + .Parameter Target One or more .Parameter Configure @@ -291,26 +295,31 @@ function Configure-CMakeBuild { .Example # Build the 'windows-x64' and 'windows-x86' CMake builds. - Build-CMakeBuild -Presets windows-x64,windows-x86 + Build-CMakeBuild -Preset windows-x64,windows-x86 # Build the 'windows-x64' and 'windows-x86' CMake builds, with the 'Release' configuration. - Build-CMakeBuild -Presets windows-x64,windows-x86 -Configurations Release + Build-CMakeBuild -Preset windows-x64,windows-x86 -Configuration Release # Build the 'HelperLibrary' target, for the 'windows-x64' and 'windows-x86' CMake builds, with the 'Release' # configuration. - Build-CMakeBuild -Presets windows-x64,windows-x86 -Configurations Release -Targets HelperLibrary + Build-CMakeBuild -Preset windows-x64,windows-x86 -Configuration Release -Target HelperLibrary #> function Build-CMakeBuild { [CmdletBinding()] param( + [Alias('Presets')] + [SupportsWildcards()] [Parameter(Position = 0)] - [string[]] $Presets, + [string[]] $Preset, + [Alias('Configurations')] + [SupportsWildcards()] [Parameter(Position = 1)] - [string[]] $Configurations = @($null), + [string[]] $Configuration, + [Alias('Targets')] [Parameter(Position = 2)] - [string[]] $Targets, + [string[]] $Target, [Parameter()] [switch] $Configure, @@ -323,27 +332,30 @@ function Build-CMakeBuild { ) $CMakeRoot = FindCMakeRoot $CMakePresetsJson = GetCMakePresets - $PresetNames = GetBuildPresetNames $CMakePresetsJson - - if (-not $Presets) { - if (-not $PresetNames) { + $BuildPresetNames = GetBuildPresetNames $CMakePresetsJson + $BuildPresetNames = if (-not $Preset) { + if (-not $BuildPresetNames) { Write-Error "No Presets values specified, and one could not be inferred." } - $Presets = $PresetNames | Select-Object -First 1 + $BuildPresetNames | Select-Object -First 1 + } else { + foreach ($CandidatePreset in $Preset) { + $BuildPresetNames | Where-Object { $_ -like $CandidatePreset } + } } # If; # * no targets were specified, and # * the current location is different from the cmake root # Then we're a scoped build! - $ScopedBuild = (-not $Targets) -and ($CMakeRoot -ne ((Get-Location).Path)) + $ScopedBuild = (-not $Target) -and ($CMakeRoot -ne ((Get-Location).Path)) $ScopeLocation = (Get-Location).Path $CMake = GetCMake Using-Location $CMakeRoot { - foreach ($Preset in $Presets) { - Write-Output "Preset : $Preset" + foreach ($BuildPresetName in $BuildPresetNames) { + Write-Output "Preset : $BuildPresetName" - $BuildPreset, $ConfigurePreset = ResolvePresets $CMakePresetsJson 'buildPresets' $Preset + $BuildPreset, $ConfigurePreset = ResolvePresets $CMakePresetsJson 'buildPresets' $BuildPresetName $BinaryDirectory = GetBinaryDirectory $CMakePresetsJson $ConfigurePreset $CMakeCacheFile = Join-Path -Path $BinaryDirectory -ChildPath 'CMakeCache.txt' @@ -361,33 +373,39 @@ function Build-CMakeBuild { $CodeModel = Get-CMakeBuildCodeModel $BinaryDirectory - foreach ($Configuration in $Configurations) { - Write-Output "Configuration : $Configuration" + [string[]] $ConfigurationNames = @($null) + if ($Configuration) { + $ConfigurationNames = foreach ($CandidateConfigurationName in $Configuration) { + $CodeModel.configurations.name | Where-Object { $_ -like $CandidateConfigurationName } + } + } + + foreach ($ConfigurationName in $ConfigurationNames) { + Write-Output "Configuration : $ConfigurationName" - if ($ScopedBuild) { - $TargetTuples = GetScopedTargets $CodeModel $Configuration $ScopeLocation - $Targets = if ($TargetTuples) { + $TargetNames = if ($ScopedBuild) { + $TargetTuples = GetScopedTargets $CodeModel $ConfigurationName $ScopeLocation + if ($TargetTuples) { $TargetTuples.name - } else { - @() } - Write-Output "Scoped Targets : $Targets" + } else { + $Target } $CMakeArguments = @( '--build' - '--preset', $Preset + '--preset', $BuildPresetName + if ($ConfigurationName) { + '--config', $ConfigurationName + } + if ($TargetNames) { + '--target' + $TargetNames + } ) - if ($Targets) { - $CMakeArguments += '--target' - $CMakeArguments += $Targets - } - - Write-Verbose "CMake Arguments: $CMakeArguments" - $StartTime = [datetime]::Now - & $CMake @CMakeArguments (($Configuration)?('--config', $Configuration):$null) + InvokeCMake $CMake $CMakeArguments if ($LASTEXITCODE -ne 0) { Write-Error "Build failed. Command line: '$($CMake.Source)' $($CMakeArguments -join ' ')" } @@ -498,7 +516,7 @@ function Invoke-CMakeOutput { # $CodeModel = Get-CMakeBuildCodeModel $BinaryDirectory if (-not $CodeModel) { - Configure-CMakeBuild -Presets $Preset + Configure-CMakeBuild -Preset $Preset $CodeModel = Get-CMakeBuildCodeModel $BinaryDirectory } @@ -547,11 +565,11 @@ Register-ArgumentCompleter -CommandName Invoke-CMakeOutput -ParameterName Preset Register-ArgumentCompleter -CommandName Invoke-CMakeOutput -ParameterName Configuration -ScriptBlock $function:BuildConfigurationsCompleter Register-ArgumentCompleter -CommandName Invoke-CMakeOutput -ParameterName Target -ScriptBlock $function:ExecutableTargetsCompleter -Register-ArgumentCompleter -CommandName Build-CMakeBuild -ParameterName Presets -ScriptBlock $function:BuildPresetsCompleter -Register-ArgumentCompleter -CommandName Build-CMakeBuild -ParameterName Configurations -ScriptBlock $function:BuildConfigurationsCompleter -Register-ArgumentCompleter -CommandName Build-CMakeBuild -ParameterName Targets -ScriptBlock $function:BuildTargetsCompleter +Register-ArgumentCompleter -CommandName Build-CMakeBuild -ParameterName Preset -ScriptBlock $function:BuildPresetsCompleter +Register-ArgumentCompleter -CommandName Build-CMakeBuild -ParameterName Configuration -ScriptBlock $function:BuildConfigurationsCompleter +Register-ArgumentCompleter -CommandName Build-CMakeBuild -ParameterName Target -ScriptBlock $function:BuildTargetsCompleter -Register-ArgumentCompleter -CommandName Configure-CMakeBuild -ParameterName Presets -ScriptBlock $function:ConfigurePresetsCompleter +Register-ArgumentCompleter -CommandName Configure-CMakeBuild -ParameterName Preset -ScriptBlock $function:ConfigurePresetsCompleter Register-ArgumentCompleter -CommandName Write-CMakeBuild -ParameterName Preset -ScriptBlock $function:BuildPresetsCompleter Register-ArgumentCompleter -CommandName Write-CMakeBuild -ParameterName Configuration -ScriptBlock $function:BuildConfigurationsCompleter diff --git a/Tests/Build-CMakeBuild.Tests.ps1 b/Tests/Build-CMakeBuild.Tests.ps1 index e577244..491bb89 100644 --- a/Tests/Build-CMakeBuild.Tests.ps1 +++ b/Tests/Build-CMakeBuild.Tests.ps1 @@ -7,18 +7,55 @@ BeforeAll { . $PSScriptRoot/TestUtilities.ps1 . $PSScriptRoot/ReferenceBuild.ps1 + # Configure the reference build so that there is reference content to work with. $Properties = PrepareReferenceBuild - $CMake = "$env:ProgramFiles/CMake/bin/cmake.exe" & $CMake @Properties Import-Module -Force $PSScriptRoot/../PSCMake/PSCMake.psd1 -DisableNameChecking + + # Mock subsequent calls to invoke CMake so that we don't actually try to build anything. + $script:CMakeCalls = @() + Mock -ModuleName PSCMake InvokeCMake { + param( + [string] $CMakePath, + [string[]] $Arguments + ) + $script:CMakeCalls += , $Arguments + } } Describe 'Build-CMakeBuild' { + BeforeEach { + $script:CMakeCalls = @() + } + It 'Builds with no parameters' { Using-Location "$PSScriptRoot/ReferenceBuild" { Build-CMakeBuild } + + $script:CMakeCalls | Should -HaveCount 1 + $script:CMakeCalls[0] | Should -Be @('--build', '--preset', 'windows-x64') + } + + It 'Builds with wildcard presets' { + Using-Location "$PSScriptRoot/ReferenceBuild" { + Build-CMakeBuild -Preset '*-x64' + } + + $CMakeCalls | Should -HaveCount 1 + $script:CMakeCalls[0] | Should -Be @('--build', '--preset', 'windows-x64') + } + + It 'Builds with wildcard configurations' { + Using-Location "$PSScriptRoot/ReferenceBuild" { + Build-CMakeBuild -Preset 'windows-x64' -Configuration * + } + + $CMakeCalls | Should -HaveCount 3 + $script:CMakeCalls[0] | Should -Be @('--build', '--preset', 'windows-x64', '--config', 'Debug') + $script:CMakeCalls[1] | Should -Be @('--build', '--preset', 'windows-x64', '--config', 'Release') + $script:CMakeCalls[2] | Should -Be @('--build', '--preset', 'windows-x64', '--config', 'RelWithDebInfo') } } diff --git a/Tests/BuildConfigurationsCompleter.Tests.ps1 b/Tests/BuildConfigurationsCompleter.Tests.ps1 index a5c7320..a2663d8 100644 --- a/Tests/BuildConfigurationsCompleter.Tests.ps1 +++ b/Tests/BuildConfigurationsCompleter.Tests.ps1 @@ -11,7 +11,7 @@ BeforeAll { Describe 'BuildConfigurationsCompleter' { It 'Returns the default configurations when no preset is specified' { - $Completions = Get-CommandCompletion "Build-CMakeBuild -Configurations " + $Completions = Get-CommandCompletion "Build-CMakeBuild -Configuration " $Completions.CompletionMatches.Count | Should -Be 4 $Completions.CompletionMatches[0].CompletionText | Should -Be 'Release' $Completions.CompletionMatches[1].CompletionText | Should -Be 'Debug' @@ -20,7 +20,7 @@ Describe 'BuildConfigurationsCompleter' { } It 'Returns the default configurations when no preset is specified, filtered by the word to complete' { - $Completions = Get-CommandCompletion "Build-CMakeBuild -Configurations D" + $Completions = Get-CommandCompletion "Build-CMakeBuild -Configuration D" $Completions.CompletionMatches.Count | Should -Be 1 $Completions.CompletionMatches[0].CompletionText | Should -Be 'Debug' }