Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .build/tasks/WorkspaceDependencies.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@
`$BuildRoot`.

.PARAMETER BuiltModuleSubdirectory
The parent path of the module to be built. Defaults to `module`.
The parent path of the module to be built, relative to `$OutputDirectory`.
Defaults to an empty string, which resolves the link root to the output
root - the same location `build.ps1` builds to and pre-pends to
`PSModulePath` when `BuiltModuleSubdirectory` is not set in `build.yaml`.
The default must stay empty because InvokeBuild dot-sources every task
file into the same scope and a non-empty default here would overwrite the
shared value for all other tasks. `Get-SamplerWorkspaceLinkedModuleRoot`
keeps `module` as its own parameter default for when the argument is
omitted entirely.

.PARAMETER WorkspaceModules
The sibling workspace modules to link into the local output module path.
Expand All @@ -41,7 +49,7 @@ param

[Parameter()]
[System.String]
$BuiltModuleSubdirectory = (property BuiltModuleSubdirectory 'module'),
$BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''),

[Parameter()]
[System.String[]]
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- The `WorkspaceDependencies` build task declared its `BuiltModuleSubdirectory`
parameter default as `(property BuiltModuleSubdirectory 'module')` while every
other task file uses `''`. InvokeBuild dot-sources all imported task files into
the same scope and the `property` helper treats an empty string as unset, so
this non-empty default (dot-sourced last) overwrote the shared
`$BuiltModuleSubdirectory` value for every task in the build. Projects that do
not set `BuiltModuleSubdirectory` in `build.yaml` build to the output root, but
the tasks then looked for the built module under `output\module`, failing with
`Could not find the built module manifest for module '<Name>'. Build the module
before running tasks that require the built module output.` from
`Set-SamplerTaskVariable`. The default is now `''`, which also makes
`Link_Local_Workspace_Dependencies` link sibling modules into the location that
is actually on `PSModulePath` for the consuming project. Fixes #593.

## [0.120.0] - 2026-07-14

### Added
Expand Down
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mode: ContinuousDelivery
next-version: 0.90
next-version: 0.90.0
major-version-bump-message: '\s?(breaking|major|breaking\schange)'
minor-version-bump-message: '(adds?|features?|minor)\b'
patch-version-bump-message: '\s?(fix|patch)'
Expand Down
46 changes: 46 additions & 0 deletions tests/QA/module.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,52 @@ Describe 'General module control' -Tags 'FunctionalQuality' {
}
}

Describe 'Build task shared property defaults' -Tags 'FunctionalQuality' {
<#
InvokeBuild dot-sources every imported task file into the same scope, and
the 'property' helper treats an empty string as unset. A task file that
declares a non-empty default for a shared property therefore overwrites
the value used by every other task in the build.
#>
It 'Should use the same BuiltModuleSubdirectory property default in every build task file' {
$taskFilePath = Join-Path -Path $projectPath -ChildPath (Join-Path -Path '.build' -ChildPath 'tasks')

$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.CommandAst] }

$propertyDefault = @()

foreach ($taskFile in (Get-ChildItem -Path $taskFilePath -Filter '*.build.ps1'))
{
$abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseFile($taskFile.FullName, [ref] $null, [ref] $null)

$parameterAst = @(
$abstractSyntaxTree.ParamBlock.Parameters |
Where-Object -FilterScript {
$_.Name.VariablePath.UserPath -eq 'BuiltModuleSubdirectory'
}
)

if ($parameterAst.Count -eq 0)
{
continue
}

$propertyCommandAst = $parameterAst[0].DefaultValue.Find($astSearchDelegate, $true)

$propertyDefault += [PSCustomObject] @{
FileName = $taskFile.Name
Default = $propertyCommandAst.CommandElements[2].Value
}
}

$propertyDefault.Count | Should -BeGreaterThan 0 -Because 'the build task files must declare the shared BuiltModuleSubdirectory property'

$divergentDefault = @($propertyDefault | Where-Object -FilterScript { $_.Default -ne '' })

$divergentDefault.Count | Should -Be 0 -Because ("the shared 'BuiltModuleSubdirectory' property default must be an empty string in every build task file, but these files diverge: {0}" -f ($divergentDefault.FileName -join ', '))
}
}

BeforeDiscovery {
# Must use the imported module to build test cases.
$allModuleFunctions = & $mut { Get-Command -Module $args[0] -CommandType Function } $script:moduleName
Expand Down
29 changes: 29 additions & 0 deletions tests/Unit/tasks/WorkspaceDependencies.build.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,35 @@ Describe 'WorkspaceDependencies.build' {
$taskAlias.ReferencedCommand | Should -Be 'WorkspaceDependencies.build.ps1'
$taskAlias.Definition | Should -Match 'Sampler[\/|\\]\d+\.\d+\.\d+[\/|\\]tasks[\/|\\]WorkspaceDependencies\.build\.ps1'
}

<#
InvokeBuild dot-sources every imported task file into the same scope, and
the 'property' helper treats an empty string as unset. A non-empty default
here would therefore overwrite the shared $BuiltModuleSubdirectory value
for every other task in the build.
#>
It 'Should default the BuiltModuleSubdirectory property to an empty string' {
$taskFilePath = (Get-Alias -Name 'WorkspaceDependencies.build.Sampler.ib.tasks').Definition

$abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseFile($taskFilePath, [ref] $null, [ref] $null)

$parameterAst = @(
$abstractSyntaxTree.ParamBlock.Parameters |
Where-Object -FilterScript {
$_.Name.VariablePath.UserPath -eq 'BuiltModuleSubdirectory'
}
)

$parameterAst.Count | Should -Be 1

$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.CommandAst] }

$propertyCommandAst = $parameterAst[0].DefaultValue.Find($astSearchDelegate, $true)

$propertyCommandAst.CommandElements[0].Value | Should -Be 'property'
$propertyCommandAst.CommandElements[1].Value | Should -Be 'BuiltModuleSubdirectory'
$propertyCommandAst.CommandElements[2].Value | Should -Be '' -Because 'a non-empty default leaks into the shared build scope of every other task'
}
}

Describe 'Link_Local_Workspace_Dependencies' {
Expand Down