Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0350978
Initial plan
Copilot Oct 17, 2025
545c522
Replace fpm with native rpmbuild for RPM package generation
Copilot Oct 17, 2025
a9b6632
Fix RPM spec file generation and cleanup
Copilot Oct 17, 2025
e8a1be0
Fix rpmbuild command execution and verify with comprehensive test
Copilot Oct 17, 2025
21511f9
Update linux packaging workflow to explicitly import packaging.psm1
Copilot Oct 18, 2025
3dbd356
Update Linux CI workflow to trigger packaging job for packaging changes
Copilot Oct 18, 2025
42ace23
Add back ci_build dependency to linux_packaging job
Copilot Oct 18, 2025
08cd012
Fix macOS and Linux packaging issues
Copilot Oct 18, 2025
0415c7f
Fix RPM package path duplication issue
Copilot Oct 18, 2025
fad01c2
Fix cross-architecture RPM builds with --target option
Copilot Oct 18, 2025
a1803b8
Fix cross-architecture RPM builds by omitting BuildArch for non-nativ…
Copilot Oct 18, 2025
9473c91
Fix package artifacts handling for GitHub Actions
Copilot Oct 18, 2025
6a78c10
Add Pester tests for Linux package name validation
Copilot Oct 18, 2025
16fb48b
Fix RPM package name validation regex
Copilot Oct 18, 2025
3c6d2e6
Simplify RPM version handling - just convert hyphens to underscores
Copilot Oct 18, 2025
563431c
Revert "Fix RPM package name validation regex"
Copilot Oct 18, 2025
4ef96b9
Add RPM spec file logging with GitHub Actions group support
Copilot Oct 18, 2025
fa902b5
Remove RPM macro and build release field with distribution suffix
Copilot Oct 19, 2025
404cde8
Add Azure Linux (Mariner) support for RPM builds
Copilot Oct 19, 2025
3c28c83
Merge branch 'master' into copilot/update-code-to-generate-rpm
TravisEz13 Oct 20, 2025
1419128
Disable binary stripping for cross-architecture RPM builds
Copilot Oct 20, 2025
5b0e023
Add detailed comments explaining cross-arch RPM build directives
Copilot Oct 20, 2025
6248d4b
Install fpm on Mariner for DEB package builds
Copilot Oct 20, 2025
a959d39
Apply suggestion from @TravisEz13
TravisEz13 Oct 20, 2025
2601b32
Apply suggestion from @TravisEz13
TravisEz13 Oct 20, 2025
5289ca4
Update tools/packaging/packaging.psm1
TravisEz13 Oct 20, 2025
3d79676
Update test/packaging/linux/package-validation.tests.ps1
TravisEz13 Oct 20, 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
3 changes: 3 additions & 0 deletions .github/actions/test/linux-packaging/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ runs:
# Create the artifacts staging directory
New-Item -ItemType Directory -Path "$env:BUILD_ARTIFACTSTAGINGDIRECTORY" -Force | Out-Null

# Import packaging module to ensure RPM packaging changes are loaded
Import-Module ./build.psm1 -Force
Import-Module ./tools/packaging/packaging.psm1 -Force
Import-Module ./tools/ci.psm1
Restore-PSOptions -PSOptionsPath '${{ runner.workspace }}/build/psoptions.json'
$options = (Get-PSOptions)
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/linux-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
# Set job outputs to values from filter step
outputs:
source: ${{ steps.filter.outputs.source }}
packagingChanged: ${{ steps.filter.outputs.packagingChanged }}
steps:
- name: checkout
uses: actions/checkout@v5
Expand Down Expand Up @@ -237,9 +238,8 @@ jobs:
linux_packaging:
name: Linux Packaging
needs:
- ci_build
Comment thread
TravisEz13 marked this conversation as resolved.
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.packagingChanged == 'true' }}
runs-on: ubuntu-latest
steps:
- name: checkout
Expand Down
21 changes: 17 additions & 4 deletions build.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -2401,11 +2401,24 @@ function Start-PSBootstrap {
}

# Install [fpm](https://github.com/jordansissel/fpm)
# Note: fpm is now only needed for DEB and macOS packages; RPM packages use rpmbuild directly
if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') {
Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1"
Install-GlobalGem -Sudo $sudo -GemName "ffi" -GemVersion "1.16.3"
Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1"
Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5"
# Only install fpm on Debian-based systems and macOS
if ($environment.IsDebianFamily -or $environment.IsMacOS) {
Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1"
Install-GlobalGem -Sudo $sudo -GemName "ffi" -GemVersion "1.16.3"
Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1"
Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5"
}

# For RPM-based systems, ensure rpmbuild is available
if ($environment.IsRedHatFamily -or $environment.IsSUSEFamily) {
Write-Verbose -Verbose "Checking for rpmbuild..."
if (!(Get-Command rpmbuild -ErrorAction SilentlyContinue)) {
Write-Warning "rpmbuild not found. Installing rpm-build package..."
Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y rpm-build")) -IgnoreExitcode
}
}
}
}

Expand Down
281 changes: 249 additions & 32 deletions tools/packaging/packaging.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -1245,40 +1245,110 @@ function New-UnixPackage {
# Setup package dependencies
$Dependencies = @(Get-PackageDependencies @packageDependenciesParams)

$Arguments = @()


$Arguments += Get-FpmArguments `
-Name $Name `
-Version $packageVersion `
-Iteration $Iteration `
-Description $Description `
-Type $Type `
-Dependencies $Dependencies `
-AfterInstallScript $AfterScriptInfo.AfterInstallScript `
-AfterRemoveScript $AfterScriptInfo.AfterRemoveScript `
-Staging $Staging `
-Destination $Destination `
-ManGzipFile $ManGzipInfo.GzipFile `
-ManDestination $ManGzipInfo.ManFile `
-LinkInfo $Links `
-AppsFolder $AppsFolder `
-Distribution $DebDistro `
-HostArchitecture $HostArchitecture `
-ErrorAction Stop

# Build package
try {
if ($PSCmdlet.ShouldProcess("Create $type package")) {
Write-Log "Creating package with fpm $Arguments..."
try {
$Output = Start-NativeExecution { fpm $Arguments }
if ($Type -eq 'rpm') {
# Use rpmbuild directly for RPM packages
if ($PSCmdlet.ShouldProcess("Create RPM package with rpmbuild")) {
Write-Log "Creating RPM package with rpmbuild..."

# Create rpmbuild directory structure
$rpmBuildRoot = Join-Path $env:HOME "rpmbuild"
$specsDir = Join-Path $rpmBuildRoot "SPECS"
$rpmsDir = Join-Path $rpmBuildRoot "RPMS"

New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
New-Item -ItemType Directory -Path $rpmsDir -Force | Out-Null

# Generate RPM spec file
$specContent = New-RpmSpec `
-Name $Name `
-Version $packageVersion `
-Iteration $Iteration `
-Description $Description `
-Dependencies $Dependencies `
-AfterInstallScript $AfterScriptInfo.AfterInstallScript `
-AfterRemoveScript $AfterScriptInfo.AfterRemoveScript `
-Staging $Staging `
-Destination $Destination `
-ManGzipFile $ManGzipInfo.GzipFile `
-ManDestination $ManGzipInfo.ManFile `
-LinkInfo $Links `
-Distribution $DebDistro `
-HostArchitecture $HostArchitecture

$specFile = Join-Path $specsDir "$Name.spec"
$specContent | Out-File -FilePath $specFile -Encoding ascii
Write-Verbose "Generated spec file: $specFile" -Verbose

# Build RPM package
try {
# Use bash to properly handle rpmbuild arguments
$buildCmd = "rpmbuild -bb --quiet --define '_topdir $rpmBuildRoot' --buildroot '$rpmBuildRoot/BUILDROOT' '$specFile'"
Write-Verbose "Running: $buildCmd" -Verbose
$Output = bash -c $buildCmd 2>&1
$exitCode = $LASTEXITCODE

if ($exitCode -ne 0) {
throw "rpmbuild failed with exit code $exitCode"
}

# Find the generated RPM
$rpmFile = Get-ChildItem -Path (Join-Path $rpmsDir $HostArchitecture) -Filter "*.rpm" -ErrorAction Stop |
Comment thread
TravisEz13 marked this conversation as resolved.
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First 1

if ($rpmFile) {
# Copy RPM to current location
Copy-Item -Path $rpmFile.FullName -Destination $CurrentLocation -Force
$Output = @("Created package {:path=>""$(Join-Path $CurrentLocation $rpmFile.Name)""}")
} else {
throw "RPM file not found after build"
}
}
catch {
Write-Verbose -Message "!!!Handling error in rpmbuild!!!" -Verbose -ErrorAction SilentlyContinue
if ($Output) {
Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue
}
Get-Error -InputObject $_
throw
}
}
catch {
Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue
Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue
Get-Error -InputObject $_
throw
} else {
# Use fpm for DEB and macOS packages
$Arguments = @()

$Arguments += Get-FpmArguments `
-Name $Name `
-Version $packageVersion `
-Iteration $Iteration `
-Description $Description `
-Type $Type `
-Dependencies $Dependencies `
-AfterInstallScript $AfterScriptInfo.AfterInstallScript `
-AfterRemoveScript $AfterScriptInfo.AfterRemoveScript `
-Staging $Staging `
-Destination $Destination `
-ManGzipFile $ManGzipInfo.GzipFile `
-ManDestination $ManGzipInfo.ManFile `
-LinkInfo $Links `
-AppsFolder $AppsFolder `
-Distribution $DebDistro `
-HostArchitecture $HostArchitecture `
-ErrorAction Stop

if ($PSCmdlet.ShouldProcess("Create $type package")) {
Write-Log "Creating package with fpm $Arguments..."
try {
$Output = Start-NativeExecution { fpm $Arguments }
}
catch {
Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue
Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue
Get-Error -InputObject $_
throw
}
}
}
} finally {
Expand All @@ -1295,6 +1365,16 @@ function New-UnixPackage {
Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo mv $hack_dest $symlink_dest")) -VerboseOutputOnError
}
}

# Clean up rpmbuild directory if it was created
if ($Type -eq 'rpm') {
$rpmBuildRoot = Join-Path $env:HOME "rpmbuild"
if (Test-Path $rpmBuildRoot) {
Write-Verbose "Cleaning up rpmbuild directory: $rpmBuildRoot" -Verbose
Remove-Item -Path $rpmBuildRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}

if ($AfterScriptInfo.AfterInstallScript) {
Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterInstallScript -Force
}
Expand Down Expand Up @@ -1439,6 +1519,134 @@ Class LinkInfo
[string] $Destination
}

function New-RpmSpec
{
param(
[Parameter(Mandatory,HelpMessage='Package Name')]
[String]$Name,

[Parameter(Mandatory,HelpMessage='Package Version')]
[String]$Version,

[Parameter(Mandatory)]
[String]$Iteration,

[Parameter(Mandatory,HelpMessage='Package description')]
[String]$Description,

[Parameter(Mandatory,HelpMessage='Staging folder for installation files')]
[String]$Staging,

[Parameter(Mandatory,HelpMessage='Install path on target machine')]
[String]$Destination,

[Parameter(Mandatory,HelpMessage='The built and gzipped man file.')]
[String]$ManGzipFile,

[Parameter(Mandatory,HelpMessage='The destination of the man file')]
[String]$ManDestination,

[Parameter(Mandatory,HelpMessage='Symlink to powershell executable')]
[LinkInfo[]]$LinkInfo,

[Parameter(Mandatory,HelpMessage='Packages required to install this package')]
[String[]]$Dependencies,

[Parameter(Mandatory,HelpMessage='Script to run after the package installation.')]
[String]$AfterInstallScript,

[Parameter(Mandatory,HelpMessage='Script to run after the package removal.')]
[String]$AfterRemoveScript,

[String]$Distribution = 'rhel.7',
[string]$HostArchitecture
)

$specContent = @"
# RPM spec file for PowerShell
# Generated by PowerShell build system

Name: $Name
Version: $Version
Release: $Iteration%{?dist}
Summary: PowerShell - Cross-platform automation and configuration tool/framework
License: MIT
URL: https://microsoft.com/powershell
BuildArch: $HostArchitecture
AutoReq: no

"@

# Add dependencies
foreach ($dep in $Dependencies) {
$specContent += "Requires: $dep`n"
}

$specContent += @"

%description
$Description

%prep
# No prep needed - files are already staged

%build
# No build needed - binaries are pre-built

%install
rm -rf `$RPM_BUILD_ROOT
mkdir -p `$RPM_BUILD_ROOT$Destination
mkdir -p `$RPM_BUILD_ROOT$(Split-Path -Parent $ManDestination)

# Copy all files from staging to destination
cp -r $Staging/* `$RPM_BUILD_ROOT$Destination/

# Copy man page
cp $ManGzipFile `$RPM_BUILD_ROOT$ManDestination

"@

# Add symlinks - we need to get the target of the temp symlink
foreach ($link in $LinkInfo) {
$linkDir = Split-Path -Parent $link.Destination
$specContent += "mkdir -p `$RPM_BUILD_ROOT$linkDir`n"
# For RPM, we copy the symlink itself (which fpm does by including it in the source)
# The symlink at $link.Source points to the actual target, so we'll copy it
Comment thread
TravisEz13 marked this conversation as resolved.
$specContent += "cp -P $($link.Source) `$RPM_BUILD_ROOT$($link.Destination)`n"
}

# Post-install script
$postInstallContent = Get-Content -Path $AfterInstallScript -Raw
$specContent += "`n%post`n"
$specContent += $postInstallContent
$specContent += "`n"

# Post-uninstall script
$postUninstallContent = Get-Content -Path $AfterRemoveScript -Raw
$specContent += "%postun`n"
$specContent += $postUninstallContent
$specContent += "`n"

# Files section
$specContent += "%files`n"
$specContent += "%defattr(-,root,root,-)`n"
$specContent += "$Destination/*`n"
$specContent += "$ManDestination`n"

# Add symlinks to files
foreach ($link in $LinkInfo) {
$specContent += "$($link.Destination)`n"
}

# Changelog with correct date format for RPM
$changelogDate = Get-Date -Format "ddd MMM dd yyyy"
$specContent += "`n%changelog`n"
$specContent += "* $changelogDate PowerShell Team <PowerShellTeam@hotmail.com> - $Version-$Iteration`n"
$specContent += "- Automated build`n"

return $specContent
Comment thread
TravisEz13 marked this conversation as resolved.
}

function Get-FpmArguments
{
param(
Expand Down Expand Up @@ -1651,7 +1859,16 @@ function Get-PackageDependencies

function Test-Dependencies
{
foreach ($Dependency in "fpm") {
# Note: RPM packages no longer require fpm; they use rpmbuild directly
# DEB packages still use fpm
$Dependencies = @()

# Only check for fpm on Debian-based systems
if ($Environment.IsDebianFamily) {
$Dependencies += "fpm"
}

foreach ($Dependency in $Dependencies) {
if (!(precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) {
# These tools are not added to the path automatically on OpenSUSE 13.2
# try adding them to the path and re-tesing first
Expand Down