Replace fpm with native macOS packaging tools (pkgbuild/productbuild) by Copilot · Pull Request #26268 · PowerShell/PowerShell · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
42add6e
Initial plan
Copilot Oct 21, 2025
ab910f5
Replace fpm with native macOS packaging tools (pkgbuild/productbuild)
Copilot Oct 21, 2025
626e6f9
Fix symlink handling in New-MacOSPackage
Copilot Oct 21, 2025
cbd9889
Update documentation to reflect native macOS packaging tools
Copilot Oct 21, 2025
507955f
Add macOS package creation and validation to CI workflow
Copilot Oct 21, 2025
f583bad
Combine build and package steps in same task
Copilot Oct 21, 2025
039aa8d
Convert package validation to Pester test
Copilot Oct 21, 2025
bf63843
Add -SkipReleaseChecks to Start-PSPackage call
Copilot Oct 21, 2025
5f1dfd9
Use process-pester-results action for test result publishing
Copilot Oct 21, 2025
bd2875f
Fix symlink creation in macOS package - recreate instead of copy
Copilot Oct 21, 2025
67cc0af
Fix directory cleanup race condition in macOS package test
Copilot Oct 21, 2025
3894307
Use Pester TestDrive for package expansion directories
Copilot Oct 21, 2025
08d989f
Use FullName instead of DirectoryName in verbose log
Copilot Oct 22, 2025
9dfd4c5
Use Join-Path for constructing payload file path
Copilot Oct 22, 2025
abb8f6d
Apply suggestion from @TravisEz13
TravisEz13 Oct 22, 2025
77d1387
Update man page path in releasing documentation
TravisEz13 Oct 22, 2025
4409398
Refactor to reuse New-MacOsDistributionPackage function
Copilot Oct 27, 2025
d1888e5
Use Start-NativeExecution for pkgbuild and productbuild commands
Copilot Oct 27, 2025
41624e0
Merge branch 'master' into copilot/update-macos-packaging-tool
TravisEz13 Oct 27, 2025
74c2190
Add Switch-PSNugetConfig to macOS CI and update build guide
Copilot Oct 27, 2025
3b45318
Use Start-NativeExecution for pkgutil and chmod commands
Copilot Oct 27, 2025
e874759
Merge branch 'master' into copilot/update-macos-packaging-tool
TravisEz13 Oct 28, 2025
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
48 changes: 47 additions & 1 deletion .github/instructions/build-configuration-guide.instructions.md
149 changes: 149 additions & 0 deletions .github/instructions/start-native-execution.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
applyTo:
- "**/*.ps1"
- "**/*.psm1"
---

# Using Start-NativeExecution for Native Command Execution

## Purpose

`Start-NativeExecution` is the standard function for executing native commands (external executables) in PowerShell scripts within this repository. It provides consistent error handling and better diagnostics when native commands fail.

## When to Use

Use `Start-NativeExecution` whenever you need to:
- Execute external commands (e.g., `git`, `dotnet`, `pkgbuild`, `productbuild`, `fpm`, `rpmbuild`)
- Ensure proper exit code checking
- Get better error messages with caller information
- Handle verbose output on error

## Basic Usage

```powershell
Start-NativeExecution {
git clone https://github.com/PowerShell/PowerShell.git
}
```

## With Parameters

Use backticks for line continuation within the script block:

```powershell
Start-NativeExecution {
pkgbuild --root $pkgRoot `
--identifier $pkgIdentifier `
--version $Version `
--scripts $scriptsDir `
$outputPath
}
```

## Common Parameters

### -VerboseOutputOnError

Captures command output and displays it only if the command fails:

```powershell
Start-NativeExecution -VerboseOutputOnError {
dotnet build --configuration Release
}
```

### -IgnoreExitcode

Allows the command to fail without throwing an exception:

```powershell
Start-NativeExecution -IgnoreExitcode {
git diff --exit-code # Returns 1 if differences exist
}
```

## Availability

The function is defined in `tools/buildCommon/startNativeExecution.ps1` and is available in:
- `build.psm1` (dot-sourced automatically)
- `tools/packaging/packaging.psm1` (dot-sourced automatically)
- Test modules that include `HelpersCommon.psm1`

To use in other scripts, dot-source the function:

```powershell
. "$PSScriptRoot/../buildCommon/startNativeExecution.ps1"
```

## Error Handling

When a native command fails (non-zero exit code), `Start-NativeExecution`:
1. Captures the exit code
2. Identifies the calling location (file and line number)
3. Throws a descriptive error with full context

Example error message:
```
Execution of {git clone ...} by /path/to/script.ps1: line 42 failed with exit code 1
```

## Examples from the Codebase

### Git Operations
```powershell
Start-NativeExecution {
git fetch --tags --quiet upstream
}
```

### Build Operations
```powershell
Start-NativeExecution -VerboseOutputOnError {
dotnet publish --configuration Release
}
```

### Packaging Operations
```powershell
Start-NativeExecution -VerboseOutputOnError {
pkgbuild --root $pkgRoot --identifier $pkgId --version $version $outputPath
}
```

### Permission Changes
```powershell
Start-NativeExecution {
find $staging -type d | xargs chmod 755
find $staging -type f | xargs chmod 644
}
```

## Anti-Patterns

**Don't do this:**
```powershell
& somecommand $args
if ($LASTEXITCODE -ne 0) {
throw "Command failed"
}
```

**Do this instead:**
```powershell
Start-NativeExecution {
somecommand $args
}
```

## Best Practices

1. **Always use Start-NativeExecution** for native commands to ensure consistent error handling
2. **Use -VerboseOutputOnError** for commands with useful diagnostic output
Comment thread
TravisEz13 marked this conversation as resolved.
3. **Use backticks for readability** when commands have multiple arguments
4. **Don't capture output unnecessarily** - let the function handle it
5. **Use -IgnoreExitcode sparingly** - only when non-zero exit codes are expected and acceptable

## Related Documentation

- Source: `tools/buildCommon/startNativeExecution.ps1`
- Blog post: https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/
55 changes: 53 additions & 2 deletions .github/workflows/macos-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ jobs:
runner_os: macos-15-large
test_results_artifact_name: testResults-xunit
PackageMac-macos_packaging:
name: macOS packaging (bootstrap only)
name: macOS packaging and testing
needs:
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
Expand All @@ -162,12 +162,63 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v5
with:
fetch-depth: 1000
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json
- name: Bootstrap packaging
if: success() || failure()
if: success()
run: |-
import-module ./build.psm1
start-psbootstrap -Scenario package
Comment thread
TravisEz13 marked this conversation as resolved.
shell: pwsh
- name: Build PowerShell and Create macOS package
if: success()
run: |-
import-module ./build.psm1
import-module ./tools/ci.psm1
import-module ./tools/packaging/packaging.psm1
Switch-PSNugetConfig -Source Public
Sync-PSTags -AddRemoteIfMissing
$releaseTag = Get-ReleaseTag
Start-PSBuild -Configuration Release -PSModuleRestore -ReleaseTag $releaseTag
$macOSRuntime = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'osx-arm64' } else { 'osx-x64' }
Start-PSPackage -Type osxpkg -ReleaseTag $releaseTag -MacOSRuntime $macOSRuntime -SkipReleaseChecks
Comment thread
TravisEz13 marked this conversation as resolved.
shell: pwsh
- name: Test package contents
if: success()
run: |-
$env:PACKAGE_FOLDER = Get-Location
$testResultsPath = Join-Path $env:RUNNER_WORKSPACE "testResults"
if (-not (Test-Path $testResultsPath)) {
New-Item -ItemType Directory -Path $testResultsPath -Force | Out-Null
}
Import-Module Pester
$pesterConfig = New-PesterConfiguration
$pesterConfig.Run.Path = './tools/packaging/releaseTests/macOSPackage.tests.ps1'
$pesterConfig.Run.PassThru = $true
$pesterConfig.Output.Verbosity = 'Detailed'
$pesterConfig.TestResult.Enabled = $true
$pesterConfig.TestResult.OutputFormat = 'NUnitXml'
$pesterConfig.TestResult.OutputPath = Join-Path $testResultsPath "macOSPackage.xml"
$result = Invoke-Pester -Configuration $pesterConfig
if ($result.FailedCount -gt 0) {
throw "Package validation failed with $($result.FailedCount) failed test(s)"
}
Comment thread
TravisEz13 marked this conversation as resolved.
shell: pwsh
- name: Publish and Upload Pester Test Results
if: always()
uses: "./.github/actions/test/process-pester-results"
with:
name: "macOSPackage"
testResultsFolder: "${{ runner.workspace }}/testResults"
- name: Upload package artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: macos-package
path: "*.pkg"
ready_to_merge:
name: macos ready to merge
needs:
Expand Down
17 changes: 12 additions & 5 deletions docs/maintainers/releasing.md
Loading
Loading