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
48 changes: 47 additions & 1 deletion .github/instructions/build-configuration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@

### For Release/Packaging

**Use: Release with version tag**
**Use: Release with version tag and public NuGet feeds**

```yaml
- name: Build for Release
Comment on lines 28 to 33
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is under .github/instructions/ but it doesn’t follow the repo’s instruction-file requirements (must be named *.instructions.md and include YAML frontmatter with applyTo). As-is, it may not be picked up as a Copilot instruction file. Rename to build-configuration-guide.instructions.md and add the required frontmatter (see custom-instructions/repo/.github/instructions/instruction-file-format.instructions.md).

Copilot uses AI. Check for mistakes.
shell: pwsh
run: |
Import-Module ./build.psm1
Import-Module ./tools/ci.psm1
Switch-PSNugetConfig -Source Public
$releaseTag = Get-ReleaseTag
Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag
```
Expand All @@ -43,6 +45,11 @@
- No debug symbols (smaller size)
- Production-ready

**Why Switch-PSNugetConfig -Source Public:**
- Switches NuGet package sources to public feeds (nuget.org and public Azure DevOps feeds)
- Required for CI/CD environments that don't have access to private feeds
- Uses publicly available packages instead of Microsoft internal feeds

### For Code Coverage

**Use: CodeCoverage configuration**
Expand Down Expand Up @@ -93,3 +100,42 @@ src/powershell-win-core/bin/Debug/<netversion>/<runtime>/publish/
3. Match configuration to purpose
4. Use `-CI` only when needed
5. Always specify `-ReleaseTag` for release or packaging builds
6. Use `Switch-PSNugetConfig -Source Public` in CI/CD for release builds

## NuGet Feed Configuration

### Switch-PSNugetConfig

The `Switch-PSNugetConfig` function in `build.psm1` manages NuGet package source configuration.

**Available Sources:**

- **Public**: Uses public feeds (nuget.org and public Azure DevOps feeds)
- Required for: CI/CD environments, public builds, packaging
- Does not require authentication

- **Private**: Uses internal PowerShell team feeds
- Required for: Internal development with preview packages
- Requires authentication credentials

- **NuGetOnly**: Uses only nuget.org
- Required for: Minimal dependency scenarios

**Usage:**

```powershell
# Switch to public feeds (most common for CI/CD)
Switch-PSNugetConfig -Source Public

# Switch to private feeds with authentication
Switch-PSNugetConfig -Source Private -UserName $userName -ClearTextPAT $pat

# Switch to nuget.org only
Switch-PSNugetConfig -Source NuGetOnly
```

**When to Use:**

- **Always use `-Source Public`** before building in CI/CD workflows
- Use before any build that will create packages for distribution
- Use in forks or environments without access to Microsoft internal feeds
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/
59 changes: 55 additions & 4 deletions .github/workflows/macos-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,72 @@ jobs:
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' }}
runs-on:
- macos-latest
steps:
- name: checkout
uses: actions/checkout@v4.1.0
- name: Bootstrap packaging
if: success() || failure()
uses: actions/checkout@v5
with:
fetch-depth: 1000
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json

if: success()
run: |-
import-module ./build.psm1
start-psbootstrap -Scenario package
Comment on lines +166 to 173
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job step is syntactically invalid: a single GitHub Actions step can't have both uses: (setup-dotnet) and run:. It looks like the bootstrap command lost its own - name: entry / indentation. Split bootstrap into a separate step (with run:/shell:) after the setup-dotnet step.

Suggested change
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json
if: success()
run: |-
import-module ./build.psm1
start-psbootstrap -Scenario package
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json
- name: Bootstrap
if: success()
run: |-
Import-Module ./build.psm1
Start-PSBootstrap -Scenario package

Copilot uses AI. Check for mistakes.
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
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,18 @@ The output of `Start-PSBuild` includes a `powershell.exe` executable which can s
#### Linux / macOS

The `Start-PSPackage` function delegates to `New-UnixPackage`.
It relies on the [Effing Package Management][fpm] project,
which makes building packages for any (non-Windows) platform a breeze.
Similarly, the PowerShell man-page is generated from the Markdown-like file

For **Linux** (Debian-based distributions), it relies on the [Effing Package Management][fpm] project,
which makes building packages a breeze.

For **macOS**, it uses native packaging tools (`pkgbuild` and `productbuild`) from Xcode Command Line Tools,
eliminating the need for Ruby or fpm.

For **Linux** (Red Hat-based distributions), it uses `rpmbuild` directly.

The PowerShell man-page is generated from the Markdown-like file
[`assets/pwsh.1.ronn`][man] using [Ronn][].
The function `Start-PSBootstrap -Package` will install both these tools.
The function `Start-PSBootstrap -Package` will install these tools.

To modify any property of the packages, edit the `New-UnixPackage` function.
Please also refer to the function for details on the package properties
Expand Down Expand Up @@ -131,7 +138,7 @@ Without `-Name` specified, the primary `powershell`
package will instead be created.

[fpm]: https://github.com/jordansissel/fpm
[man]: ../../assets/pwsh.1.ronn
[man]: ../../assets/manpage/pwsh.1.ronn
[ronn]: https://github.com/rtomayko/ronn

### Build and Packaging Examples
Expand Down
Loading
Loading