[release/v7.6] Replace fpm with native macOS packaging tools (pkgbuild/productbuild) by TravisEz13 · Pull Request #26495 · PowerShell/PowerShell · GitHub
Skip to content
Closed
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
48 changes: 47 additions & 1 deletion .github/instructions/build-configuration-guide.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
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 @@ -157,7 +157,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 @@ -166,12 +166,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
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
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)"
}
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