Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

---

## [0.3.0] - Unreleased

- Ensure `PowerShell 5.1` compatibility for the delphi-dccbuild.ps1 script
(Tests remain the newer `pwsh`)
[#6](https://github.com/continuous-delphi/delphi-dccbuild/issues/6)

## [0.2.0] - 2026-03-16

- Add `-Namespace` parameter to specify unit scope names for unqualified unit
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
![Status](https://img.shields.io/badge/status-incubator-orange)
![License](https://img.shields.io/github/license/continuous-delphi/delphi-inspect.svg)
![Delphi](https://img.shields.io/badge/delphi-red)
![PowerShell](https://img.shields.io/badge/powershell-7.4%2B-blue)
![PowerShell](https://img.shields.io/badge/powershell-blue)
![Continuous Delphi](https://img.shields.io/badge/org-continuous--delphi-red)

Quick-start, or enhance your Delphi build automation with a standalone,
Expand Down Expand Up @@ -45,6 +45,13 @@ delphi-inspect.ps1 -DetectLatest -Platform Win32 -BuildSystem DCC |
delphi-dccbuild.ps1 -ProjectFile .\src\MyApp.dpr
```

## PowerShell Compatibility

Runs on the widely available Windows PowerShell 5.1 (`powershell.exe`)
and the newer PowerShell 7+ (`pwsh`).

Note: the test suite requires `pwsh`.

# Usage

```powershell
Expand Down
4 changes: 2 additions & 2 deletions source/delphi-dccbuild.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ function Resolve-RootDir {
# Derive the expected rsvars.bat path from the Delphi root dir.
function Get-RsvarsPath {
param([string]$RootDir)
return Join-Path -Path $RootDir -ChildPath 'bin' -AdditionalChildPath 'rsvars.bat'
return Join-Path (Join-Path $RootDir 'bin') 'rsvars.bat'
}

# Invoke cmd.exe to source rsvars.bat and capture the resulting environment.
Expand Down Expand Up @@ -232,7 +232,7 @@ function Get-CompilerPath {
param([string]$RootDir, [string]$Platform)
$name = Get-CompilerName -Platform $Platform
$folder = Get-CompilerBinFolder -CompilerName $name
return Join-Path -Path $RootDir -ChildPath $folder -AdditionalChildPath "$name.exe"
return Join-Path (Join-Path $RootDir $folder) "$name.exe"
}

# Invoke the DCC compiler with the given arguments.
Expand Down
14 changes: 11 additions & 3 deletions tests/pwsh/TestHelpers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# Get-DccBuildScriptPath - alias of Get-ScriptUnderTestPath
# Invoke-ToolProcess - runs a .ps1 as a child process and returns
# [pscustomobject]@{ ExitCode; StdOut; StdErr }
# Optional -Shell parameter selects the host
# executable (default: 'pwsh').

function Get-ScriptUnderTestPath {
$path = Join-Path $PSScriptRoot '..\..\source\delphi-dccbuild.ps1'
Expand All @@ -24,12 +26,18 @@ function Get-DccBuildScriptPath { Get-ScriptUnderTestPath }
function Invoke-ToolProcess {
param(
[Parameter(Mandatory=$true)][string]$ScriptPath,
[Parameter()][string[]]$Arguments = @()
[Parameter()][string[]]$Arguments = @(),
[Parameter()][string]$Shell = 'pwsh',
[Parameter()][string]$ExecutionPolicy = ''
)

$shellArgs = @('-NoProfile', '-NonInteractive')
if ($ExecutionPolicy) { $shellArgs += @('-ExecutionPolicy', $ExecutionPolicy) }
$shellArgs += @('-File', $ScriptPath)

$psi = [System.Diagnostics.ProcessStartInfo]::new()
$psi.FileName = 'pwsh'
foreach ($a in @('-NoProfile', '-NonInteractive', '-File', $ScriptPath) + $Arguments) {
$psi.FileName = $Shell
foreach ($a in $shellArgs + $Arguments) {
[void]$psi.ArgumentList.Add($a)
}
$psi.RedirectStandardOutput = $true
Expand Down
193 changes: 193 additions & 0 deletions tests/pwsh/WindowsPS51Compat.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#Requires -Modules @{ ModuleName='Pester'; ModuleVersion='5.7.0' }
<#
.SYNOPSIS
Windows PowerShell 5.1 compatibility tests for delphi-dccbuild.ps1.

.DESCRIPTION
Verifies that delphi-dccbuild.ps1 can be launched under powershell.exe
(Windows PowerShell 5.1) and produces correct exit codes.
All tests in this file skip automatically on platforms where
powershell.exe is absent (e.g. Linux CI runners).

The test suite itself continues to require pwsh 7+ (see run-tests.ps1);
these tests only invoke the script-under-test via powershell.exe.

Scenarios tested:
Exits 3 when no -RootDir and no pipeline input.
Exits 3 when -RootDir directory does not exist on disk.
Exits 3 when -RootDir exists but rsvars.bat is absent.
Exits 3 when rsvars.bat exists but the DCC compiler exe is absent.
Exits 4 when rsvars.bat and compiler exist but -ProjectFile does not.

.NOTES
Get-Command is NOT used to locate powershell.exe. The Invoke-RsvarsEnvironment
unit test applies fake environment variables (including a truncated PATH) to the
live process, which removes C:\Windows\System32\WindowsPowerShell\v1.0 from PATH
and breaks Get-Command resolution for external commands. Instead, powershell.exe
is located via its well-known fixed path under $env:SystemRoot. On Linux/macOS
$env:SystemRoot is absent so Test-Path returns $false and all tests skip cleanly.

$skipTests is a discovery-time local variable captured by -Skip:.
$script:winPS51Exe is set in BeforeAll (run time) so it is visible in
It and Context BeforeAll blocks.

-ExecutionPolicy Bypass is passed to powershell.exe because the machine's
default execution policy may not permit running unsigned scripts.
#>

Describe 'Windows PowerShell 5.1 compatibility' {

# Evaluated at Pester discovery time -- captured by -Skip: on each It.
# Uses Test-Path (filesystem) rather than Get-Command (PATH-dependent) to
# locate powershell.exe safely regardless of prior process PATH changes.
$ps51Path = if ($env:SystemRoot) {
[System.IO.Path]::Combine($env:SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
} else { $null }
$skipTests = -not ($ps51Path -and (Test-Path -LiteralPath $ps51Path))

BeforeAll {
. "$PSScriptRoot/TestHelpers.ps1"
$script:scriptPath = Get-ScriptUnderTestPath

$script:winPS51Exe = $null
$sysRoot = $env:SystemRoot
if ($sysRoot) {
$candidate = [System.IO.Path]::Combine($sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
if (Test-Path -LiteralPath $candidate) { $script:winPS51Exe = $candidate }
}
}

It 'powershell.exe (Windows PowerShell 5.1) is present on this machine' -Skip:$skipTests {
$script:winPS51Exe | Should -Not -BeNullOrEmpty
}

Context 'exits 3 when no rootDir is provided and no pipeline input' {

BeforeAll {
if (-not $script:winPS51Exe) { return }
$script:result = Invoke-ToolProcess `
-Shell $script:winPS51Exe `
-ExecutionPolicy 'Bypass' `
-ScriptPath $script:scriptPath `
-Arguments @('-ProjectFile', 'C:\Fake\MyApp.dpr')
}

It 'exit code is 3' -Skip:$skipTests {
$script:result.ExitCode | Should -Be 3
}

It 'stderr contains helpful message' -Skip:$skipTests {
$script:result.StdErr -join ' ' | Should -Match 'root dir'
}

}

Context 'exits 3 when rootDir directory does not exist on disk' {

BeforeAll {
if (-not $script:winPS51Exe) { return }
$script:result = Invoke-ToolProcess `
-Shell $script:winPS51Exe `
-ExecutionPolicy 'Bypass' `
-ScriptPath $script:scriptPath `
-Arguments @('-ProjectFile', 'C:\Fake\MyApp.dpr', '-RootDir', 'C:\DoesNotExist\AtAll\9999')
}

It 'exit code is 3' -Skip:$skipTests {
$script:result.ExitCode | Should -Be 3
}

It 'stderr mentions the missing directory' -Skip:$skipTests {
$script:result.StdErr -join ' ' | Should -Match 'not found'
}

}

Context 'exits 3 when rootDir exists but rsvars.bat is absent' {

BeforeAll {
if (-not $script:winPS51Exe) { return }
$script:result = Invoke-ToolProcess `
-Shell $script:winPS51Exe `
-ExecutionPolicy 'Bypass' `
-ScriptPath $script:scriptPath `
-Arguments @('-ProjectFile', 'C:\Fake\MyApp.dpr', '-RootDir', ([System.IO.Path]::GetTempPath()))
}

It 'exit code is 3' -Skip:$skipTests {
$script:result.ExitCode | Should -Be 3
}

It 'stderr mentions rsvars.bat' -Skip:$skipTests {
$script:result.StdErr -join ' ' | Should -Match 'rsvars\.bat'
}

}

Context 'exits 3 when rsvars.bat exists but DCC compiler exe is absent' {

BeforeAll {
if (-not $script:winPS51Exe) { return }
$script:tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) 'delphi-dccbuild-winps51-nocompiler'
$script:tempBin = Join-Path $script:tempRoot 'bin'
$null = New-Item -ItemType Directory -Path $script:tempBin -Force
$null = New-Item -ItemType File -Path (Join-Path $script:tempBin 'rsvars.bat') -Force
# dcc32.exe is intentionally absent

$script:result = Invoke-ToolProcess `
-Shell $script:winPS51Exe `
-ExecutionPolicy 'Bypass' `
-ScriptPath $script:scriptPath `
-Arguments @('-ProjectFile', 'C:\Fake\MyApp.dpr', '-RootDir', $script:tempRoot)
}

AfterAll {
if ($script:tempRoot) {
Remove-Item -LiteralPath $script:tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}

It 'exit code is 3' -Skip:$skipTests {
$script:result.ExitCode | Should -Be 3
}

It 'stderr mentions the missing compiler' -Skip:$skipTests {
$script:result.StdErr -join ' ' | Should -Match 'not found'
}

}

Context 'exits 4 when rsvars.bat and compiler exist but project file does not' {

BeforeAll {
if (-not $script:winPS51Exe) { return }
$script:tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) 'delphi-dccbuild-winps51-noproj'
$script:tempBin = Join-Path $script:tempRoot 'bin'
$null = New-Item -ItemType Directory -Path $script:tempBin -Force
$null = New-Item -ItemType File -Path (Join-Path $script:tempBin 'rsvars.bat') -Force
$null = New-Item -ItemType File -Path (Join-Path $script:tempBin 'dcc32.exe') -Force

$script:result = Invoke-ToolProcess `
-Shell $script:winPS51Exe `
-ExecutionPolicy 'Bypass' `
-ScriptPath $script:scriptPath `
-Arguments @('-ProjectFile', 'C:\Fake\DoesNotExist.dpr', '-RootDir', $script:tempRoot)
}

AfterAll {
if ($script:tempRoot) {
Remove-Item -LiteralPath $script:tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}

It 'exit code is 4' -Skip:$skipTests {
$script:result.ExitCode | Should -Be 4
}

It 'stderr mentions the missing project file' -Skip:$skipTests {
$script:result.StdErr -join ' ' | Should -Match 'not found'
}

}

}
Loading