Skip to content

[release/v7.5] Replace fpm with native macOS packaging tools (pkgbuild/productbuild)#26801

Merged
daxian-dbw merged 1 commit intoPowerShell:release/v7.5from
daxian-dbw:backport/release/v7.5/26268-47e8e900a
Feb 13, 2026
Merged

[release/v7.5] Replace fpm with native macOS packaging tools (pkgbuild/productbuild)#26801
daxian-dbw merged 1 commit intoPowerShell:release/v7.5from
daxian-dbw:backport/release/v7.5/26268-47e8e900a

Conversation

@daxian-dbw
Copy link
Member

Backport of #26268 to release/v7.5

Triggered by @daxian-dbw on behalf of @app/copilot-swe-agent

Original CL Label: CL-BuildPackaging

/cc @PowerShell/powershell-maintainers

Impact

REQUIRED: Choose either Tooling Impact or Customer Impact (or both). At least one checkbox must be selected.

Tooling Impact

  • Required tooling change
  • Optional tooling change (include reasoning)

This change replaces the Ruby-based fpm tool with native macOS packaging tools (pkgbuild/productbuild). It eliminates the Ruby dependency and uses Apple-supported tools for better maintainability and integration with macOS.

Customer Impact

  • Customer reported
  • Found internally

Regression

REQUIRED: Check exactly one box.

  • Yes
  • No

This is not a regression.

Testing

Comprehensive testing via macOS CI workflow with Pester tests validating package creation and contents. Already validated in master, 7.4, and 7.6 branches.

Risk

REQUIRED: Check exactly one box.

  • High
  • Medium
  • Low

This is a significant change to macOS packaging infrastructure that replaces fpm with native macOS tools. However, it has been tested in master, 7.4, and 7.6 branches with improved reliability and maintainability. The change eliminates Ruby dependency and uses Apple-supported tools.

Merge Conflicts

Resolved conflict in macos-ci.yml: updated checkout action to v5 and added setup-dotnet step.

…PowerShell#26268)

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: TravisEz13 <[email protected]>
Co-authored-by: Travis Plunk <[email protected]>
@daxian-dbw daxian-dbw requested a review from a team as a code owner February 12, 2026 23:27
@daxian-dbw daxian-dbw added the CL-BuildPackaging Indicates that a PR should be marked as a build or packaging change in the Change Log label Feb 12, 2026
Copilot AI review requested due to automatic review settings February 12, 2026 23:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Backport to release/v7.5 that replaces macOS packaging via fpm with native macOS tooling (pkgbuild/productbuild), and updates CI + tests/docs accordingly.

Changes:

  • Add native macOS package creation flow in tools/packaging/packaging.psm1 and route osxpkg builds through it.
  • Add Pester-based validation for macOS .pkg contents and wire it into the macOS CI workflow.
  • Update maintainer documentation and add Copilot instruction docs for Start-NativeExecution.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tools/packaging/packaging.psm1 Adds New-MacOSPackage, refactors distribution package creation, updates dependency checks, and switches osxpkg to native tools.
tools/packaging/releaseTests/macOSPackage.tests.ps1 Adds Pester validation that expands/extracts .pkg and verifies required contents/permissions.
.github/workflows/macos-ci.yml Adds a macOS packaging + validation job, including test result publishing and artifact upload.
docs/maintainers/releasing.md Updates packaging tooling description per-platform and fixes manpage link.
.github/instructions/start-native-execution.instructions.md Adds guidance for using Start-NativeExecution for native command execution.
.github/instructions/build-configuration-guide.md Updates release build guidance to include Switch-PSNugetConfig -Source Public.

Comment on lines 28 to 33
### For Release/Packaging

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

```yaml
- name: Build for Release
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.
Comment on lines +1800 to +1804
# Copy staging files to destination path in package root
$destInPkg = Join-Path $pkgRoot $Destination
New-Item -ItemType Directory -Path $destInPkg -Force | Out-Null
Write-Verbose "Copying staging files from $Staging to $destInPkg" -Verbose
Copy-Item -Path "$Staging/*" -Destination $destInPkg -Recurse -Force
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.

In macOS packaging, $Destination is an absolute path (e.g. /usr/local/...). Join-Path $pkgRoot $Destination will ignore $pkgRoot and resolve to the real system path, causing the packaging step to create/copy files under /usr/local/... on the build machine instead of inside the package root. Strip the leading / (or otherwise convert to a relative path) before joining so all writes stay under $pkgRoot.

Copilot uses AI. Check for mistakes.
Comment on lines +1807 to +1809
$manDir = Join-Path $pkgRoot (Split-Path $ManDestination -Parent)
New-Item -ItemType Directory -Path $manDir -Force | Out-Null
Copy-Item -Path $ManGzipFile -Destination (Join-Path $pkgRoot $ManDestination) -Force
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.

$ManDestination is an absolute install path (/usr/local/share/...). Using it with Join-Path $pkgRoot ... will escape the temp package root for the same reason as $Destination, writing to the host filesystem. Convert $ManDestination to a relative path under $pkgRoot before creating directories / copying the gzipped man page.

Suggested change
$manDir = Join-Path $pkgRoot (Split-Path $ManDestination -Parent)
New-Item -ItemType Directory -Path $manDir -Force | Out-Null
Copy-Item -Path $ManGzipFile -Destination (Join-Path $pkgRoot $ManDestination) -Force
$relativeManDestination = if ([System.IO.Path]::IsPathRooted($ManDestination)) {
$root = [System.IO.Path]::GetPathRoot($ManDestination)
$ManDestination.Substring($root.Length).TrimStart('\', '/')
} else {
$ManDestination
}
$manDir = Join-Path $pkgRoot (Split-Path $relativeManDestination -Parent)
New-Item -ItemType Directory -Path $manDir -Force | Out-Null
Copy-Item -Path $ManGzipFile -Destination (Join-Path $pkgRoot $relativeManDestination) -Force

Copilot uses AI. Check for mistakes.
Comment on lines +1814 to +1816
$linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent)
New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null
$finalLinkPath = Join-Path $pkgRoot $link.Destination
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.

$link.Destination values are absolute (e.g. /usr/local/bin/pwsh). Join-Path $pkgRoot $link.Destination will ignore $pkgRoot, so symlinks may be created on the build host instead of in the package root. Ensure link destinations are made relative to $pkgRoot (for example by trimming the leading /) before joining/creating items.

Suggested change
$linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent)
New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null
$finalLinkPath = Join-Path $pkgRoot $link.Destination
# $link.Destination may be an absolute path (e.g. /usr/local/bin/pwsh).
# Trim leading directory separators so paths are relative to $pkgRoot.
$relativeLinkDestination = $link.Destination.TrimStart('\', '/')
$linkDestDir = Join-Path $pkgRoot (Split-Path $relativeLinkDestination -Parent)
New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null
$finalLinkPath = Join-Path $pkgRoot $relativeLinkDestination

Copilot uses AI. Check for mistakes.
Comment on lines +1746 to +1750
[Parameter(Mandatory)]
[string]$Iteration,

[Parameter(Mandatory)]
[string]$Staging,
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.

New-MacOSPackage declares Iteration as a mandatory parameter but the function never uses it. Either remove it (and stop passing it) or incorporate it into the package version/filename so the parameter has an effect.

Copilot uses AI. Check for mistakes.
Comment on lines +166 to 173
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json

if: success()
run: |-
import-module ./build.psm1
start-psbootstrap -Scenario package
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.
Comment on lines +2119 to +2126
# These tools are not added to the path automatically on OpenSUSE 13.2
# try adding them to the path and re-tesing first
[string] $gemsPath = $null
[string] $depenencyPath = $null
$gemsPath = Get-ChildItem -Path /usr/lib64/ruby/gems | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
if ($gemsPath) {
$depenencyPath = Get-ChildItem -Path (Join-Path -Path $gemsPath -ChildPath "gems" -AdditionalChildPath $Dependency) -Recurse | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty DirectoryName
$originalPath = $env:PATH
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.

The fallback PATH-augmentation block has typos (re-tesing, $depenencyPath) and references OpenSUSE even though it only runs under $Environment.IsDebianFamily. Please fix the typos and update the comment/logic so it accurately reflects the platform/path it is trying to handle.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +56
$payloadFile = Join-Path $componentPkg.FullName "Payload"
Get-Content -Path $payloadFile -Raw -AsByteStream | & cpio -i 2>&1 | Out-Null
} finally {
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.

Payload extraction pipes the raw Payload blob directly into cpio -i. For pkgbuild-created component packages the Payload is typically a gzip-compressed cpio archive, so this can fail silently (especially since output is discarded). Consider explicitly decompressing (e.g., via gzip -dc/gunzip -dc) and run it under Start-NativeExecution so failures surface with a non-zero exit code and useful diagnostics.

Copilot uses AI. Check for mistakes.
@daxian-dbw daxian-dbw merged commit c96a282 into PowerShell:release/v7.5 Feb 13, 2026
32 of 34 checks passed
@daxian-dbw daxian-dbw deleted the backport/release/v7.5/26268-47e8e900a branch February 13, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-BuildPackaging Indicates that a PR should be marked as a build or packaging change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants