Skip to content

Commit 060fc0a

Browse files
authored
Populate output/exeOutputDir/dcuOutputDir, add warnings/errors counts (PR #18)
* Populate output/exeOutputDir/dcuOutputDir, add warnings/errors counts +fix duplicate UnitSearchPath - output is now always captured; -ShowOutput streams it to host but no longer discards it - exeOutputDir and dcuOutputDir resolved from dcc32 -E/-NO flags in build output when not supplied as parameters - warnings and errors integer counts added to result object from MSBuild summary line Issue #17 * All tests now pass
1 parent 04cc747 commit 060fc0a

3 files changed

Lines changed: 84 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44

55
---
66

7+
## [0.6.0] - 2026-04-01
8+
9+
- `.output` is now always populated in the result object -- previously it was
10+
`$null` when `-ShowOutput` was used; output is now captured and streamed
11+
- `.exeOutputDir` and `.dcuOutputDir` are now resolved from the dcc32 compiler
12+
invocation in the build output when not supplied as parameters
13+
- `.warnings` and `.errors` integer counts added to the result object, parsed
14+
from the MSBuild summary line
15+
- Fix: duplicate `/p:DCC_UnitSearchPath` argument when `-UnitSearchPath` was
16+
supplied (the unquoted copy was appended first, then the quoted copy)
17+
718
## [0.5.0] - 2026-03-19
819

920
- `-Define` parameter broken - MSBuild thinks it's a switch

source/delphi-msbuild.ps1

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ NOTES
3333
-Config is the RAD Studio MSBuild property name (/p:Config); common values
3434
are Debug and Release.
3535
36-
By default MSBuild output is captured and returned in the result object's
37-
.output property. Use -ShowOutput to stream output to stdout in real time;
38-
in that case .output is null and errors are written via Write-Error.
36+
MSBuild output is always captured and returned in the result object's
37+
.output property. Use -ShowOutput to also stream output to stdout in real
38+
time; .output is populated in both cases.
3939
4040
Exit codes:
4141
0 success
@@ -53,6 +53,8 @@ NOTES
5353
Justification='Script accepts at most one piped installation object; end-block semantics are correct.')]
5454
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Get-RsvarsEnvLines',
5555
Justification='Function returns multiple KEY=VALUE lines from cmd.exe set; plural noun is accurate.')]
56+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '',
57+
Justification='Write-Host is intentional: -ShowOutput streams build text directly to the console host.')]
5658
param(
5759
[Parameter(ValueFromPipeline=$true)]
5860
[psobject]$DelphiInstallation,
@@ -100,7 +102,7 @@ $ExitRootDirError = 3
100102
$ExitProjectNotFound = 4
101103
$ExitBuildFailed = 5
102104

103-
$script:Version = '0.5.0'
105+
$script:Version = '0.6.0'
104106

105107
# Resolve the Delphi root dir from the explicit -RootDir parameter or from a
106108
# piped delphi-inspect result object (.rootDir property).
@@ -163,21 +165,18 @@ function Invoke-RsvarsEnvironment {
163165
}
164166

165167
# Invoke msbuild.exe with the given arguments.
166-
# Returns [pscustomobject]@{ ExitCode; Output } where Output is $null when
167-
# -ShowOutput is set (output streams to stdout instead of being captured).
168+
# Returns [pscustomobject]@{ ExitCode; Output } where Output is always the
169+
# captured build text. When -ShowOutput is set the text is also written to
170+
# the host so the caller sees it in real time.
168171
# Separated into its own function so tests can mock it.
169172
function Invoke-MsbuildExe {
170173
param(
171174
[string[]]$Arguments,
172175
[switch]$ShowOutput
173176
)
174177

175-
if ($ShowOutput) {
176-
& msbuild.exe @Arguments | Out-Host
177-
return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $null }
178-
}
179-
180178
$output = & msbuild.exe @Arguments 2>&1 | Out-String
179+
if ($ShowOutput) { Write-Host $output }
181180
return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $output }
182181
}
183182

@@ -208,11 +207,6 @@ function Invoke-MsbuildProject {
208207
if (-not [string]::IsNullOrWhiteSpace($ExeOutputDir)) { $msbuildArgs += "/p:DCC_ExeOutput=$ExeOutputDir" }
209208
if (-not [string]::IsNullOrWhiteSpace($DcuOutputDir)) { $msbuildArgs += "/p:DCC_DcuOutput=$DcuOutputDir" }
210209

211-
if ($UnitSearchPath.Count -gt 0) {
212-
$unitSearchValue = '$(DCC_UnitSearchPath);' + ($UnitSearchPath -join ';')
213-
$msbuildArgs += "/p:DCC_UnitSearchPath=$unitSearchValue"
214-
}
215-
216210
if ($UnitSearchPath.Count -gt 0) {
217211
$unitSearchValue = '$(DCC_UnitSearchPath);' + ($UnitSearchPath -join ';')
218212
$msbuildArgs += "/p:DCC_UnitSearchPath=`"$unitSearchValue`""
@@ -226,6 +220,56 @@ function Invoke-MsbuildProject {
226220
return Invoke-MsbuildExe -Arguments $msbuildArgs -ShowOutput:$ShowOutput
227221
}
228222

223+
# Parse the dcc32.exe invocation line from captured msbuild output and extract
224+
# the exe output dir (-E flag) and DCU output dir (-NO flag).
225+
# Paths are resolved to absolute using the project file directory as base.
226+
# Returns [pscustomobject]@{ ExeOutputDir; DcuOutputDir } -- either may be $null.
227+
function Get-BuildOutputDir {
228+
param(
229+
[string]$Output,
230+
[string]$ProjectFileDir
231+
)
232+
233+
$result = [pscustomobject]@{ ExeOutputDir = $null; DcuOutputDir = $null }
234+
if ([string]::IsNullOrWhiteSpace($Output)) { return $result }
235+
236+
$dcc32Line = ($Output -split "`n") |
237+
Where-Object { $_ -match '[/\\]dcc32\.exe\s' } |
238+
Select-Object -First 1
239+
if (-not $dcc32Line) { return $result }
240+
241+
if ($dcc32Line -match '\s-E(\S+)') {
242+
$result.ExeOutputDir = [System.IO.Path]::GetFullPath(
243+
[System.IO.Path]::Combine($ProjectFileDir, $Matches[1]))
244+
}
245+
246+
if ($dcc32Line -match '\s-NO(\S+)') {
247+
$result.DcuOutputDir = [System.IO.Path]::GetFullPath(
248+
[System.IO.Path]::Combine($ProjectFileDir, $Matches[1]))
249+
}
250+
251+
return $result
252+
}
253+
254+
# Parse the MSBuild summary block from captured output and return warning and
255+
# error counts as integers.
256+
# Returns [pscustomobject]@{ Warnings; Errors }.
257+
function Get-BuildCount {
258+
param([string]$Output)
259+
260+
$warnings = 0
261+
$errors = 0
262+
if (-not [string]::IsNullOrWhiteSpace($Output)) {
263+
$wMatch = [regex]::Match($Output, '^\s*(\d+)\s+Warning\(s\)',
264+
[System.Text.RegularExpressions.RegexOptions]::Multiline)
265+
$eMatch = [regex]::Match($Output, '^\s*(\d+)\s+Error\(s\)',
266+
[System.Text.RegularExpressions.RegexOptions]::Multiline)
267+
if ($wMatch.Success) { $warnings = [int]$wMatch.Groups[1].Value }
268+
if ($eMatch.Success) { $errors = [int]$eMatch.Groups[1].Value }
269+
}
270+
return [pscustomobject]@{ Warnings = $warnings; Errors = $errors }
271+
}
272+
229273
# Guard: skip top-level execution when the script is dot-sourced for testing.
230274
if ($MyInvocation.InvocationName -eq '.') { return }
231275

@@ -277,6 +321,11 @@ try {
277321
-Define $Define `
278322
-ShowOutput:$ShowOutput
279323

324+
$parsedDirs = Get-BuildOutputDir `
325+
-Output $buildResult.Output `
326+
-ProjectFileDir (Split-Path $resolvedProjectFile -Parent)
327+
$counts = Get-BuildCount -Output $buildResult.Output
328+
280329
$resultObj = [pscustomobject]@{
281330
scriptVersion = $script:Version
282331
projectFile = $resolvedProjectFile
@@ -286,11 +335,13 @@ try {
286335
define = $Define
287336
rootDir = $resolvedRootDir
288337
rsvarsPath = $rsvarsPath
289-
exeOutputDir = if ([string]::IsNullOrWhiteSpace($ExeOutputDir)) { $null } else { $ExeOutputDir }
290-
dcuOutputDir = if ([string]::IsNullOrWhiteSpace($DcuOutputDir)) { $null } else { $DcuOutputDir }
338+
exeOutputDir = if (-not [string]::IsNullOrWhiteSpace($ExeOutputDir)) { $ExeOutputDir } else { $parsedDirs.ExeOutputDir }
339+
dcuOutputDir = if (-not [string]::IsNullOrWhiteSpace($DcuOutputDir)) { $DcuOutputDir } else { $parsedDirs.DcuOutputDir }
291340
unitSearchPath = if ($UnitSearchPath.Count -eq 0) { $null } else { $UnitSearchPath }
292341
exitCode = $buildResult.ExitCode
293342
success = ($buildResult.ExitCode -eq 0)
343+
warnings = $counts.Warnings
344+
errors = $counts.Errors
294345
output = $buildResult.Output
295346
}
296347

tests/pwsh/delphi-msbuild.Tests.ps1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,8 @@ Describe 'Invoke-MsbuildProject' {
413413
-UnitSearchPath @('C:\Libs\MyLib')
414414
}
415415

416-
It 'includes /p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\MyLib' {
417-
$script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\MyLib'
416+
It 'includes /p:DCC_UnitSearchPath="$(DCC_UnitSearchPath);C:\Libs\MyLib"' {
417+
$script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath="$(DCC_UnitSearchPath);C:\Libs\MyLib"'
418418
}
419419

420420
}
@@ -437,8 +437,8 @@ Describe 'Invoke-MsbuildProject' {
437437
-UnitSearchPath @('C:\Libs\A', 'C:\Libs\B')
438438
}
439439

440-
It 'includes /p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B' {
441-
$script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B'
440+
It 'includes /p:DCC_UnitSearchPath="$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B"' {
441+
$script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath="$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B"'
442442
}
443443

444444
}

0 commit comments

Comments
 (0)