[release/v7.5] Replace fpm with native rpmbuild for RPM package generation#26793
Conversation
…ll#26233) Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: TravisEz13 <[email protected]> Co-authored-by: Travis Plunk <[email protected]> Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR backports #26233 to the release/v7.5 branch, replacing the Ruby gem fpm with native rpmbuild for generating RPM packages. This eliminates the Ruby dependency for RPM-based systems (RHEL, CentOS, Fedora, SUSE), reducing build complexity and installation requirements.
Changes:
- Implements native rpmbuild support with a new
New-RpmSpecfunction that generates complete RPM spec files - Updates bootstrap logic to install fpm only on Debian-based systems and macOS, while ensuring rpmbuild is available on RPM-based systems
- Adds package name validation tests to catch naming issues early in the CI pipeline
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/packaging/packaging.psm1 | Adds New-RpmSpec function and updates New-UnixPackage to use rpmbuild directly for RPM packages while keeping fpm for DEB/macOS packages; includes cross-architecture build support |
| tools/ci.psm1 | Updates New-LinuxPackage to detect GitHub Actions vs Azure DevOps environment and use appropriate artifacts directory paths |
| test/packaging/linux/package-validation.tests.ps1 | Adds Pester tests to validate RPM and tar.gz package naming conventions before artifact upload |
| build.psm1 | Updates Start-PSBootstrap to conditionally install fpm only on Debian/macOS systems and ensure rpmbuild is available on RPM-based systems |
| .github/workflows/linux-ci.yml | Adds packagingChanged output to trigger linux_packaging job when packaging-related files are modified |
| .github/actions/test/linux-packaging/action.yml | Explicitly imports build.psm1 and packaging.psm1 before ci.psm1 and runs package validation tests |
| $Output = bash -c $buildCmd 2>&1 | ||
| $exitCode = $LASTEXITCODE |
There was a problem hiding this comment.
The rpmbuild command should use Start-NativeExecution instead of manual bash invocation with LASTEXITCODE checking. Start-NativeExecution provides consistent error handling, better diagnostics with caller information, and is the standard pattern used throughout this codebase for executing native commands. Replace the bash -c approach with Start-NativeExecution for better error messages and consistent behavior.
| [Parameter(Mandatory,HelpMessage='Script to run after the package removal.')] | ||
| [String]$AfterRemoveScript, | ||
|
|
||
| [String]$Distribution = 'rhel.7', |
There was a problem hiding this comment.
The default distribution value 'rhel.7' doesn't match the valid RedHat distribution values defined at the top of the module (lines 10-14: "rh" and "cm"). This will cause generated RPM packages to have filenames like "powershell-7.6.0-1.rhel.7.x86_64.rpm" which will fail the package name validation test that expects only "rh" or "cm". The default should be 'rh' instead of 'rhel.7' to match $RedhatFullDistributions.
| [String]$Distribution = 'rhel.7', | |
| [String]$Distribution = 'rh', |
| # -1\. : Literal '-1.' | ||
| # (preview\.\d+\.)? : Optional 'preview.' and digits, followed by a dot | ||
| # (rh|cm)\. : Either 'rh.' or 'cm.' | ||
| # (x86_64|aarch64)\.rpm$ : Architecture and file extension | ||
| $rpmPackageNamePattern = 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1\.(preview\.\d+\.)?(rh|cm)\.(x86_64|aarch64)\.rpm' |
There was a problem hiding this comment.
The regex pattern includes (preview\.\d+\.)? which suggests an optional "preview.N." component between "-1." and the distribution suffix. However, based on the RPM filename format ({Name}-{Version}-{Release}.{Arch}.rpm) and the spec file generation in New-RpmSpec (Release: $Iteration.$Distribution), this component wouldn't appear in the filename. For a preview version, "preview" appears in the Name (powershell-preview) and the version field (7.6.0_preview.6), not in a separate component of the release field. This pattern component should likely be removed unless there's a specific case where it's needed.
| # -1\. : Literal '-1.' | |
| # (preview\.\d+\.)? : Optional 'preview.' and digits, followed by a dot | |
| # (rh|cm)\. : Either 'rh.' or 'cm.' | |
| # (x86_64|aarch64)\.rpm$ : Architecture and file extension | |
| $rpmPackageNamePattern = 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1\.(preview\.\d+\.)?(rh|cm)\.(x86_64|aarch64)\.rpm' | |
| # -1\. : Literal '-1.' (iteration part of Release) | |
| # (rh|cm)\. : Either 'rh.' or 'cm.' (distribution part of Release) | |
| # (x86_64|aarch64)\.rpm$ : Architecture and file extension | |
| $rpmPackageNamePattern = 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1\.(rh|cm)\.(x86_64|aarch64)\.rpm' |
| # Determine artifacts directory (GitHub Actions or Azure DevOps) | ||
| $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { | ||
| "${env:GITHUB_WORKSPACE}/../packages" | ||
| } else { | ||
| "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" | ||
| } |
There was a problem hiding this comment.
The artifacts directory determination logic is duplicated on lines 878-882 and 895-900. This code duplication makes the function harder to maintain. Consider extracting this logic to a variable at the beginning of the function, outside the foreach loop, so it's only determined once and reused in both places.
| $env:SYSTEM_ARTIFACTSDIRECTORY | ||
| } | ||
|
|
||
| if (-not $artifactsDir) { | ||
| throw "Artifacts directory not found. GITHUB_WORKSPACE or SYSTEM_ARTIFACTSDIRECTORY must be set." |
There was a problem hiding this comment.
There's a mismatch in the Azure DevOps environment variable used for the artifacts directory. The test uses $env:SYSTEM_ARTIFACTSDIRECTORY (line 10), but the packaging code in tools/ci.psm1 uses $env:BUILD_ARTIFACTSTAGINGDIRECTORY. In Azure DevOps, BUILD_ARTIFACTSTAGINGDIRECTORY is the standard variable for the staging directory where artifacts are placed before upload. The test should use the same variable to ensure it looks in the correct location. Change line 10 to use $env:BUILD_ARTIFACTSTAGINGDIRECTORY to match the packaging code.
| $env:SYSTEM_ARTIFACTSDIRECTORY | |
| } | |
| if (-not $artifactsDir) { | |
| throw "Artifacts directory not found. GITHUB_WORKSPACE or SYSTEM_ARTIFACTSDIRECTORY must be set." | |
| $env:BUILD_ARTIFACTSTAGINGDIRECTORY | |
| } | |
| if (-not $artifactsDir) { | |
| throw "Artifacts directory not found. GITHUB_WORKSPACE or BUILD_ARTIFACTSTAGINGDIRECTORY must be set." |
| foreach ($package in $tarPackages) { | ||
| # Pattern matches: powershell-7.6.0-preview.6-linux-x64.tar.gz or powershell-7.6.0-linux-x64.tar.gz | ||
| # Also matches various runtime configurations | ||
| if ($package.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?(linux|osx|linux-musl)+\-(x64\-fxdependent|x64|arm32|arm64|x64\-musl-noopt\-fxdependent)\.(tar\.gz)') { |
There was a problem hiding this comment.
The regex pattern for tar.gz package names has an unescaped dot in ([a-z]*.\d+\-)?. The dot should be escaped as \. to match a literal dot (e.g., "preview.6"). Currently, [a-z]*. matches any single character, not specifically a dot. This should be ([a-z]*\.\d+\-)? to correctly match preview versions like "preview.6-" in the package name.
| if ($package.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?(linux|osx|linux-musl)+\-(x64\-fxdependent|x64|arm32|arm64|x64\-musl-noopt\-fxdependent)\.(tar\.gz)') { | |
| if ($package.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*\.\d+\-)?(linux|osx|linux-musl)+\-(x64\-fxdependent|x64|arm32|arm64|x64\-musl-noopt\-fxdependent)\.(tar\.gz)') { |
Backport of #26233 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
Eliminates Ruby dependency for RPM package generation and uses native rpmbuild tooling instead. Reduces build complexity on RHEL/CentOS/Fedora/SUSE systems.
Customer Impact
Regression
REQUIRED: Check exactly one box.
This is not a regression.
Testing
Comprehensive testing was performed including spec file generation, building actual RPM packages, package validation, module loading checks, cross-architecture builds, and Pester tests for package name validation.
Risk
REQUIRED: Check exactly one box.
This is a significant tooling change that replaces Ruby gem fpm with native rpmbuild. However, it has been tested extensively with comprehensive tests and has already been backported to 7.4 and 7.6 successfully.