diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9c0eb..6fee961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,20 +5,29 @@ All notable changes to this project will be documented in this file. --- ## [0.3.0] - Unreleased +- Add `-ExeOutputDir` parameter to set the compiled executable output directory + via `/p:DCC_ExeOutput` +- Add `-DcuOutputDir` parameter to set the compiled DCU output directory + via `/p:DCC_DcuOutput` +- Add `-UnitSearchPath` parameter to append additional unit search paths + via `/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);...`, preserving paths + already set by the project's PropertyGroups +[#11](https://github.com/continuous-delphi/delphi-msbuild/issues/11) + - Add support for passing compiler defines to MSBUILD -[#9](https://github.com/continuous-delphi/delphi-msbuild/issues/9) + [#9](https://github.com/continuous-delphi/delphi-msbuild/issues/9) ## [0.2.0] - 2026-03-16 - Added `delphi-msbuild.ps1` to be a direct download on the release page -[#5](https://github.com/continuous-delphi/delphi-msbuild/issues/5) + [#5](https://github.com/continuous-delphi/delphi-msbuild/issues/5) ## [0.1.0] - 2026-03-16 - Initial release of `delphi-msbuild.ps1` -- build Delphi `.dproj` projects via MSBuild from the command line, with support for piped output from `delphi-inspect` and automatic `rsvars.bat` environment sourcing. -[#1](https://github.com/continuous-delphi/delphi-msbuild/issues/1) + [#1](https://github.com/continuous-delphi/delphi-msbuild/issues/1)
diff --git a/README.md b/README.md index 59b4413..78a0f59 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,59 @@ The MSBuild verbosity level. Passed to MSBuild as `/v:`. Valid values: `quiet`, `minimal`, `normal`, `detailed`, `diagnostic` +## -ExeOutputDir + +```text +-ExeOutputDir +``` + +Output directory for the compiled executable or library. Passed to MSBuild as +`/p:DCC_ExeOutput=`. + +When omitted, MSBuild uses the output location defined in the project's +PropertyGroups. The result object's `.exeOutputDir` is `$null` when this +parameter is not supplied. + +## -DcuOutputDir + +```text +-DcuOutputDir +``` + +Output directory for compiled `.dcu` files. Passed to MSBuild as +`/p:DCC_DcuOutput=`. + +When omitted, MSBuild uses the DCU location from the project's PropertyGroups. +The result object's `.dcuOutputDir` is `$null` when this parameter is not +supplied. + +## -UnitSearchPath + +```text +-UnitSearchPath +``` + +Additional unit search paths appended to the project's existing unit path. +Accepts an array of path strings. Multiple paths are joined with semicolons +and passed as: + +```text +/p:DCC_UnitSearchPath="$(DCC_UnitSearchPath);path1;path2" +``` + +The `$(DCC_UnitSearchPath)` prefix preserves the paths already set in the +project's PropertyGroups. Without it, the assignment would replace them +entirely. + +When omitted (or an empty array), no `/p:DCC_UnitSearchPath` argument is added. +The result object's `.unitSearchPath` is `$null` when no paths are supplied. + +Example: + +```powershell +-UnitSearchPath @('C:\Libs\A', 'C:\Libs\B') +``` + ## -Define ```text @@ -191,17 +244,20 @@ On success or build failure (exit codes 0 and 5), a single `pscustomobject` is written to the pipeline before the script exits. This allows downstream pipeline steps to consume the build result. -| Property | Type | Description | -|---------------|---------|------------------------------------------------------| -| `projectFile` | string | Absolute path to the project file | -| `platform` | string | Platform value used (e.g. `Win32`) | -| `config` | string | Config value used (e.g. `Debug`) | -| `target` | string | Target used (e.g. `Build`) | -| `rootDir` | string | Resolved Delphi installation root | -| `rsvarsPath` | string | Derived path to `rsvars.bat` | -| `exitCode` | int | MSBuild process exit code | -| `success` | bool | `$true` when `exitCode` is 0 | -| `output` | string | Captured MSBuild output; `$null` when `-ShowOutput` | +| Property | Type | Description | +|------------------|----------|----------------------------------------------------------| +| `projectFile` | string | Absolute path to the project file | +| `platform` | string | Platform value used (e.g. `Win32`) | +| `config` | string | Config value used (e.g. `Debug`) | +| `target` | string | Target used (e.g. `Build`) | +| `rootDir` | string | Resolved Delphi installation root | +| `rsvarsPath` | string | Derived path to `rsvars.bat` | +| `exeOutputDir` | string | Value of `-ExeOutputDir`; `$null` when not supplied | +| `dcuOutputDir` | string | Value of `-DcuOutputDir`; `$null` when not supplied | +| `unitSearchPath` | string[] | Value of `-UnitSearchPath`; `$null` when not supplied | +| `exitCode` | int | MSBuild process exit code | +| `success` | bool | `$true` when `exitCode` is 0 | +| `output` | string | Captured MSBuild output; `$null` when `-ShowOutput` | Note: On fata errors before MSBuild is invoked (exit codes 2, 3, 4) no result object is emitted. diff --git a/source/delphi-msbuild.ps1 b/source/delphi-msbuild.ps1 index 6278f00..21c442f 100644 --- a/source/delphi-msbuild.ps1 +++ b/source/delphi-msbuild.ps1 @@ -74,6 +74,17 @@ param( [ValidateSet('quiet','minimal','normal','detailed','diagnostic')] [string]$Verbosity = 'normal', + # Output directory for the compiled executable or DLL (/p:DCC_ExeOutput property). + [string]$ExeOutputDir, + + # Output directory for compiled DCU files (/p:DCC_DcuOutput property). + [string]$DcuOutputDir, + + # Additional unit search paths (/p:DCC_UnitSearchPath property). Multiple paths are + # joined with semicolons and appended to the paths already set by the project's + # PropertyGroups. + [string[]]$UnitSearchPath = @(), + [string[]]$Define = @(), [switch]$ShowOutput @@ -179,7 +190,10 @@ function Invoke-MsbuildProject { [string]$Config, [string]$Target, [string]$Verbosity, - [string[]]$Define = @(), + [string]$ExeOutputDir, + [string]$DcuOutputDir, + [string[]]$UnitSearchPath = @(), + [string[]]$Define = @(), [switch]$ShowOutput ) @@ -191,6 +205,14 @@ function Invoke-MsbuildProject { "/v:$Verbosity" ) + if (-not [string]::IsNullOrWhiteSpace($ExeOutputDir)) { $msbuildArgs += "/p:DCC_ExeOutput=$ExeOutputDir" } + if (-not [string]::IsNullOrWhiteSpace($DcuOutputDir)) { $msbuildArgs += "/p:DCC_DcuOutput=$DcuOutputDir" } + + if ($UnitSearchPath.Count -gt 0) { + $unitSearchValue = '$(DCC_UnitSearchPath);' + ($UnitSearchPath -join ';') + $msbuildArgs += "/p:DCC_UnitSearchPath=$unitSearchValue" + } + if ($Define.Count -gt 0) { $defineValue = '$(DCC_Define);' + ($Define -join ';') $msbuildArgs += "/p:DCC_Define=$defineValue" @@ -239,25 +261,31 @@ try { Invoke-RsvarsEnvironment -RsvarsPath $rsvarsPath $buildResult = Invoke-MsbuildProject ` - -ProjectFile $resolvedProjectFile ` - -Platform $Platform ` - -Config $Config ` - -Target $Target ` - -Verbosity $Verbosity ` - -Define $Define ` + -ProjectFile $resolvedProjectFile ` + -Platform $Platform ` + -Config $Config ` + -Target $Target ` + -Verbosity $Verbosity ` + -ExeOutputDir $ExeOutputDir ` + -DcuOutputDir $DcuOutputDir ` + -UnitSearchPath $UnitSearchPath ` + -Define $Define ` -ShowOutput:$ShowOutput $resultObj = [pscustomobject]@{ - scriptVersion = $script:Version - projectFile = $resolvedProjectFile - platform = $Platform - config = $Config - target = $Target - rootDir = $resolvedRootDir - rsvarsPath = $rsvarsPath - exitCode = $buildResult.ExitCode - success = ($buildResult.ExitCode -eq 0) - output = $buildResult.Output + scriptVersion = $script:Version + projectFile = $resolvedProjectFile + platform = $Platform + config = $Config + target = $Target + rootDir = $resolvedRootDir + rsvarsPath = $rsvarsPath + exeOutputDir = if ([string]::IsNullOrWhiteSpace($ExeOutputDir)) { $null } else { $ExeOutputDir } + dcuOutputDir = if ([string]::IsNullOrWhiteSpace($DcuOutputDir)) { $null } else { $DcuOutputDir } + unitSearchPath = if ($UnitSearchPath.Count -eq 0) { $null } else { $UnitSearchPath } + exitCode = $buildResult.ExitCode + success = ($buildResult.ExitCode -eq 0) + output = $buildResult.Output } Write-Output $resultObj diff --git a/tests/pwsh/delphi-msbuild.Tests.ps1 b/tests/pwsh/delphi-msbuild.Tests.ps1 index 519a862..29d315e 100644 --- a/tests/pwsh/delphi-msbuild.Tests.ps1 +++ b/tests/pwsh/delphi-msbuild.Tests.ps1 @@ -26,6 +26,11 @@ Passes correct MSBuild arguments to Invoke-MsbuildExe. Forwards -ShowOutput switch to Invoke-MsbuildExe. Returns the result object from Invoke-MsbuildExe. + ExeOutputDir adds /p:DCC_ExeOutput; omitted adds nothing. + DcuOutputDir adds /p:DCC_DcuOutput; omitted adds nothing. + UnitSearchPath single entry appends with $(DCC_UnitSearchPath) prefix. + UnitSearchPath multiple entries joined with semicolons. + UnitSearchPath omitted adds no /p:DCC_UnitSearchPath argument. Omits /p:DCC_Define when no defines are supplied. Appends /p:DCC_Define with $(DCC_Define) prefix for a single define. Appends /p:DCC_Define with $(DCC_Define) prefix for multiple defines. @@ -296,6 +301,171 @@ Describe 'Invoke-MsbuildProject' { } + Context 'ExeOutputDir adds /p:DCC_ExeOutput' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' ` + -ExeOutputDir 'C:\Build\bin' + } + + It 'includes /p:DCC_ExeOutput=C:\Build\bin' { + $script:capturedArgs | Should -Contain '/p:DCC_ExeOutput=C:\Build\bin' + } + + } + + Context 'ExeOutputDir omitted adds no /p:DCC_ExeOutput argument' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' + } + + It 'no argument contains DCC_ExeOutput' { + ($script:capturedArgs | Where-Object { $_ -like '*DCC_ExeOutput*' }) | Should -BeNullOrEmpty + } + + } + + Context 'DcuOutputDir adds /p:DCC_DcuOutput' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' ` + -DcuOutputDir 'C:\Build\dcu' + } + + It 'includes /p:DCC_DcuOutput=C:\Build\dcu' { + $script:capturedArgs | Should -Contain '/p:DCC_DcuOutput=C:\Build\dcu' + } + + } + + Context 'DcuOutputDir omitted adds no /p:DCC_DcuOutput argument' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' + } + + It 'no argument contains DCC_DcuOutput' { + ($script:capturedArgs | Where-Object { $_ -like '*DCC_DcuOutput*' }) | Should -BeNullOrEmpty + } + + } + + Context 'UnitSearchPath single entry appends with $(DCC_UnitSearchPath) prefix' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' ` + -UnitSearchPath @('C:\Libs\MyLib') + } + + It 'includes /p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\MyLib' { + $script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\MyLib' + } + + } + + Context 'UnitSearchPath multiple entries are joined with semicolons' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' ` + -UnitSearchPath @('C:\Libs\A', 'C:\Libs\B') + } + + It 'includes /p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B' { + $script:capturedArgs | Should -Contain '/p:DCC_UnitSearchPath=$(DCC_UnitSearchPath);C:\Libs\A;C:\Libs\B' + } + + } + + Context 'UnitSearchPath omitted adds no /p:DCC_UnitSearchPath argument' { + + BeforeAll { + $script:capturedArgs = $null + Mock Invoke-MsbuildExe { + $script:capturedArgs = $Arguments + return [pscustomobject]@{ ExitCode = 0; Output = '' } + } + + Invoke-MsbuildProject ` + -ProjectFile 'C:\Projects\MyApp.dproj' ` + -Platform 'Win32' ` + -Config 'Debug' ` + -Target 'Build' ` + -Verbosity 'normal' + } + + It 'no argument contains DCC_UnitSearchPath' { + ($script:capturedArgs | Where-Object { $_ -like '*DCC_UnitSearchPath*' }) | Should -BeNullOrEmpty + } + + } + Context 'omits /p:DCC_Define when no -Define values are supplied' { BeforeAll {