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
13 changes: 13 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 All @@ -75,6 +78,16 @@ runs:
Invoke-CIFinish
shell: pwsh

- name: Validate Package Names
run: |-
# Run Pester tests to validate package names
Import-Module Pester -Force
$testResults = Invoke-Pester -Path ./test/packaging/linux/package-validation.tests.ps1 -PassThru
if ($testResults.FailedCount -gt 0) {
throw "Package validation tests failed"
}
shell: pwsh

- name: Upload deb packages
uses: actions/upload-artifact@v4
with:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/linux-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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@v4
Expand Down Expand Up @@ -237,7 +238,7 @@ jobs:
needs:
- ci_build
- 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"
# Install fpm on Debian-based systems, macOS, and Mariner (where DEB packages are built)
if (($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) -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.IsLinux -and ($environment.IsRedHatFamily -or $environment.IsSUSEFamily -or $environment.IsMariner)) {
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
91 changes: 91 additions & 0 deletions test/packaging/linux/package-validation.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe "Linux Package Name Validation" {
BeforeAll {
# Determine artifacts directory (GitHub Actions or Azure DevOps)
$artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') {
"$env:GITHUB_WORKSPACE/../packages"
} else {
$env:SYSTEM_ARTIFACTSDIRECTORY
}

if (-not $artifactsDir) {
throw "Artifacts directory not found. GITHUB_WORKSPACE or SYSTEM_ARTIFACTSDIRECTORY must be set."
Comment on lines +10 to +14
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.

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.

Suggested change
$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."

Copilot uses AI. Check for mistakes.
}

Write-Verbose "Artifacts directory: $artifactsDir" -Verbose
}

Context "RPM Package Names" {
It "Should have valid RPM package names" {
$rpmPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.rpm -ErrorAction SilentlyContinue

if ($rpmPackages.Count -eq 0) {
Set-ItResult -Skipped -Because "No RPM packages found in artifacts directory"
return
}

$invalidPackages = @()
# Regex pattern for valid RPM package names.
# Breakdown:
# ^powershell\- : Starts with 'powershell-'
# (preview-|lts-)? : Optionally 'preview-' or 'lts-'
# \d+\.\d+\.\d+ : Version number (e.g., 7.6.0)
# (_[a-z]*\.\d+)? : Optional underscore, letters, dot, and digits (e.g., _alpha.1)
# -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'
Comment on lines +36 to +40
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 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.

Suggested change
# -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'

Copilot uses AI. Check for mistakes.

foreach ($package in $rpmPackages) {
if ($package.Name -notmatch $rpmPackageNamePattern) {
$invalidPackages += "$($package.Name) is not a valid RPM package name"
Write-Warning "$($package.Name) is not a valid RPM package name"
}
}

if ($invalidPackages.Count -gt 0) {
throw ($invalidPackages | Out-String)
}

$rpmPackages.Count | Should -BeGreaterThan 0
}
}

Context "Tar.Gz Package Names" {
It "Should have valid tar.gz package names" {
$tarPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.tar.gz -ErrorAction SilentlyContinue

if ($tarPackages.Count -eq 0) {
Set-ItResult -Skipped -Because "No tar.gz packages found in artifacts directory"
return
}

$invalidPackages = @()
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)') {
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 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.

Suggested change
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)') {

Copilot uses AI. Check for mistakes.
$invalidPackages += "$($package.Name) is not a valid tar.gz package name"
Write-Warning "$($package.Name) is not a valid tar.gz package name"
}
}

if ($invalidPackages.Count -gt 0) {
throw ($invalidPackages | Out-String)
}

$tarPackages.Count | Should -BeGreaterThan 0
}
}

Context "Package Existence" {
It "Should find at least one package in artifacts directory" {
$allPackages = Get-ChildItem -Path $artifactsDir -Recurse -Include *.rpm, *.tar.gz, *.deb -ErrorAction SilentlyContinue

$allPackages.Count | Should -BeGreaterThan 0 -Because "At least one package should exist in the artifacts directory"
}
}
}
26 changes: 23 additions & 3 deletions tools/ci.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -874,16 +874,36 @@ function New-LinuxPackage
$packageObj = $package
}

Write-Log -message "Artifacts directory: ${env:BUILD_ARTIFACTSTAGINGDIRECTORY}"
Copy-Item $packageObj.FullName -Destination "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" -Force
# Determine artifacts directory (GitHub Actions or Azure DevOps)
$artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') {
"${env:GITHUB_WORKSPACE}/../packages"
} else {
"${env:BUILD_ARTIFACTSTAGINGDIRECTORY}"
}

# Ensure artifacts directory exists
if (-not (Test-Path $artifactsDir)) {
New-Item -ItemType Directory -Path $artifactsDir -Force | Out-Null
}

Write-Log -message "Artifacts directory: $artifactsDir"
Copy-Item $packageObj.FullName -Destination $artifactsDir -Force
}

if ($IsLinux)
{
# Determine artifacts directory (GitHub Actions or Azure DevOps)
$artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') {
"${env:GITHUB_WORKSPACE}/../packages"
} else {
"${env:BUILD_ARTIFACTSTAGINGDIRECTORY}"
}
Comment on lines +895 to +900
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 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.

Copilot uses AI. Check for mistakes.

# Create and package Raspbian .tgz
# Build must be clean for Raspbian
Start-PSBuild -PSModuleRestore -Clean -Runtime linux-arm -Configuration 'Release'
$armPackage = Start-PSPackage @packageParams -Type tar-arm -SkipReleaseChecks
Copy-Item $armPackage -Destination "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" -Force
Copy-Item $armPackage -Destination $artifactsDir -Force
}
}

Expand Down
Loading
Loading