diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 2ee96079049..bdb7c128d12 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -205,7 +205,7 @@ jobs: } Import-Module Pester $pesterConfig = New-PesterConfiguration - $pesterConfig.Run.Path = './tools/packaging/releaseTests/macOSPackage.tests.ps1' + $pesterConfig.Run.Path = './test/packaging/macos/package-validation.tests.ps1' $pesterConfig.Run.PassThru = $true $pesterConfig.Output.Verbosity = 'Detailed' $pesterConfig.TestResult.Enabled = $true diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index 669ab3c8437..d429be91284 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -116,13 +116,25 @@ jobs: Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS $pkgNameFilter = "powershell-*$macosRuntime.pkg" - $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$pkgPath" + Write-Verbose -Verbose "Looking for pkg packages with filter: $pkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File + + foreach($p in $pkgPath) { + $file = $p.FullName + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } Start-PSPackage -Type tar -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS $tarPkgNameFilter = "powershell-*$macosRuntime.tar.gz" - $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$tarPkgPath" + Write-Verbose -Verbose "Looking for tar packages with filter: $tarPkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File + + foreach($t in $tarPkgPath) { + $file = $t.FullName + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } displayName: 'Package ${{ parameters.buildArchitecture}}' env: @@ -195,14 +207,14 @@ jobs: - pwsh: | $signedPkg = Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*osx*.zip" -File - + $signedPkg | ForEach-Object { Write-Verbose -Verbose "Signed package zip: $_" - + if (-not (Test-Path $_)) { throw "Package not found: $_" } - + if (-not (Test-Path $(ob_outputDirectory))) { $null = New-Item -Path $(ob_outputDirectory) -ItemType Directory } diff --git a/build.psm1 b/build.psm1 index c56303524be..dd2cf0f351e 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2399,7 +2399,7 @@ function Start-PSBootstrap { Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y rpm-build")) -IgnoreExitcode } } - + # For Debian-based systems and Mariner, ensure dpkg-deb is available if ($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) { Write-Verbose -Verbose "Checking for dpkg-deb..." @@ -2407,9 +2407,14 @@ function Start-PSBootstrap { Write-Warning "dpkg-deb not found. Installing dpkg package..." if ($environment.IsMariner) { # For Mariner (Azure Linux), install the extended repo first to access dpkg. + Write-Verbose -verbose "BEGIN: /etc/os-release content:" + Get-Content /etc/os-release | Write-Verbose -verbose + Write-Verbose -verbose "END: /etc/os-release content" + Write-Verbose -Verbose "Installing azurelinux-repos-extended for Mariner..." - Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y azurelinux-repos-extended")) -IgnoreExitcode - Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y dpkg")) -IgnoreExitcode + + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager azurelinux-repos-extended")) -IgnoreExitcode -Verbose + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager dpkg")) -IgnoreExitcode -Verbose } else { Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo apt-get install -y dpkg")) -IgnoreExitcode } diff --git a/tools/packaging/releaseTests/macOSPackage.tests.ps1 b/test/packaging/macos/package-validation.tests.ps1 similarity index 85% rename from tools/packaging/releaseTests/macOSPackage.tests.ps1 rename to test/packaging/macos/package-validation.tests.ps1 index c1de1091562..02b09fbb078 100644 --- a/tools/packaging/releaseTests/macOSPackage.tests.ps1 +++ b/test/packaging/macos/package-validation.tests.ps1 @@ -1,51 +1,54 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + Describe "Verify macOS Package" { BeforeAll { Write-Verbose "In Describe BeforeAll" -Verbose Import-Module $PSScriptRoot/../../../build.psm1 - + # Find the macOS package $packagePath = $env:PACKAGE_FOLDER if (-not $packagePath) { $packagePath = Get-Location } - + Write-Verbose "Looking for package in: $packagePath" -Verbose $package = Get-ChildItem -Path $packagePath -Filter "*.pkg" -ErrorAction SilentlyContinue | Select-Object -First 1 - + if (-not $package) { Write-Warning "No .pkg file found in $packagePath" } else { Write-Verbose "Found package: $($package.FullName)" -Verbose } - + # Set up test directories $script:package = $package $script:expandDir = $null $script:payloadDir = $null $script:extractedFiles = @() - + if ($package) { # Use TestDrive for temporary directories - pkgutil will create the expand directory $script:expandDir = Join-Path "TestDrive:" -ChildPath "package-contents-test" $expandDirResolved = (Resolve-Path "TestDrive:").ProviderPath $script:expandDir = Join-Path $expandDirResolved -ChildPath "package-contents-test" - + Write-Verbose "Expanding package to: $($script:expandDir)" -Verbose # pkgutil will create the directory itself, so don't pre-create it Start-NativeExecution { pkgutil --expand $package.FullName $script:expandDir } - + # Extract the payload to verify files $script:payloadDir = Join-Path "TestDrive:" -ChildPath "package-payload-test" $payloadDirResolved = (Resolve-Path "TestDrive:").ProviderPath $script:payloadDir = Join-Path $payloadDirResolved -ChildPath "package-payload-test" - + # Create payload directory since cpio needs it if (-not (Test-Path $script:payloadDir)) { $null = New-Item -ItemType Directory -Path $script:payloadDir -Force } - + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse | Select-Object -First 1 if ($componentPkg) { Write-Verbose "Extracting payload from: $($componentPkg.FullName)" -Verbose @@ -57,40 +60,60 @@ Describe "Verify macOS Package" { Pop-Location } } - + # Get all extracted files for verification $script:extractedFiles = Get-ChildItem -Path $script:payloadDir -Recurse -ErrorAction SilentlyContinue Write-Verbose "Extracted $($script:extractedFiles.Count) files" -Verbose } } - + AfterAll { # TestDrive automatically cleans up, but we can ensure cleanup happens # No manual cleanup needed as TestDrive handles it } - + Context "Package existence and structure" { It "Package file should exist" { $script:package | Should -Not -BeNullOrEmpty -Because "A .pkg file should be created" $script:package.Extension | Should -Be ".pkg" } - + + It "Package name should follow correct naming convention" { + $script:package | Should -Not -BeNullOrEmpty + + # Regex pattern for valid macOS PKG package names. + # Valid examples: + # - powershell-7.4.13-osx-x64.pkg (Intel x64 - note: x64 with hyphens for compatibility) + # - powershell-7.4.13-osx-arm64.pkg (Apple Silicon) + # - powershell-preview-7.6.0-preview.6-osx-x64.pkg + # - powershell-lts-7.4.13-osx-arm64.pkg + $pkgPackageNamePattern = '^powershell(-preview|-lts)?-\d+\.\d+\.\d+(-[a-z]+\.\d+)?-osx-(x64|arm64)\.pkg$' + + $script:package.Name | Should -Match $pkgPackageNamePattern -Because "Package name should follow the standard naming convention" + } + + It "Package name should NOT use x86_64 with underscores" { + $script:package | Should -Not -BeNullOrEmpty + + $script:package.Name | Should -Not -Match 'x86_64' -Because "Package should use 'x64' not 'x86_64' (with underscores) for compatibility" + } + It "Package should expand successfully" { $script:expandDir | Should -Exist Get-ChildItem -Path $script:expandDir | Should -Not -BeNullOrEmpty } - + It "Package should have a component package" { $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse -ErrorAction SilentlyContinue $componentPkg | Should -Not -BeNullOrEmpty -Because "Package should contain a component.pkg" } - + It "Payload should extract successfully" { $script:payloadDir | Should -Exist $script:extractedFiles | Should -Not -BeNullOrEmpty -Because "Package payload should contain files" } } - + Context "Required files in package" { BeforeAll { $expectedFilePatterns = @{ @@ -99,7 +122,7 @@ Describe "Verify macOS Package" { "Man page" = "usr/local/share/man/man1/pwsh*.gz" "Launcher application plist" = "Applications/PowerShell*.app/Contents/Info.plist" } - + $testCases = @() foreach ($key in $expectedFilePatterns.Keys) { $testCases += @{ @@ -107,23 +130,23 @@ Describe "Verify macOS Package" { Pattern = $expectedFilePatterns[$key] } } - + $script:testCases = $testCases } - + It "Should contain " -TestCases $script:testCases { param($Description, $Pattern) - + $found = $script:extractedFiles | Where-Object { $_.FullName -like "*$Pattern*" } $found | Should -Not -BeNullOrEmpty -Because "$Description should exist in the package at path matching '$Pattern'" } } - + Context "PowerShell binary verification" { It "PowerShell executable should be executable" { $pwshBinary = $script:extractedFiles | Where-Object { $_.FullName -like "*/pwsh" -and $_.FullName -like "*/microsoft/powershell/*" } $pwshBinary | Should -Not -BeNullOrEmpty - + # Check if file has executable permissions (on Unix-like systems) if ($IsLinux -or $IsMacOS) { $permissions = (Get-Item $pwshBinary[0].FullName).UnixFileMode @@ -132,27 +155,27 @@ Describe "Verify macOS Package" { } } } - + Context "Launcher application" { It "Launcher app should have proper bundle structure" { $plistFile = $script:extractedFiles | Where-Object { $_.FullName -like "*PowerShell*.app/Contents/Info.plist" } $plistFile | Should -Not -BeNullOrEmpty - + # Verify the bundle has required components $appPath = Split-Path (Split-Path $plistFile[0].FullName -Parent) -Parent $macOSDir = Join-Path $appPath "Contents/MacOS" $resourcesDir = Join-Path $appPath "Contents/Resources" - + Test-Path $macOSDir | Should -Be $true -Because "App bundle should have Contents/MacOS directory" Test-Path $resourcesDir | Should -Be $true -Because "App bundle should have Contents/Resources directory" } - + It "Launcher script should exist and be executable" { - $launcherScript = $script:extractedFiles | Where-Object { - $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" + $launcherScript = $script:extractedFiles | Where-Object { + $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" } $launcherScript | Should -Not -BeNullOrEmpty -Because "Launcher script should exist" - + if ($IsLinux -or $IsMacOS) { $permissions = (Get-Item $launcherScript[0].FullName).UnixFileMode $permissions.ToString() | Should -Match 'x' -Because "Launcher script should have execute permissions" diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index ccdc4e1cf08..e229aa493ae 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1239,15 +1239,15 @@ function New-UnixPackage { # 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 ` @@ -1264,11 +1264,11 @@ function New-UnixPackage { -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 - + # Log the spec file content if ($env:GITHUB_ACTIONS -eq 'true') { Write-Host "::group::RPM Spec File Content" @@ -1277,7 +1277,7 @@ function New-UnixPackage { } else { Write-Verbose "RPM Spec File Content:`n$specContent" -Verbose } - + # Build RPM package try { # Use bash to properly handle rpmbuild arguments @@ -1290,16 +1290,16 @@ function New-UnixPackage { 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 | - Sort-Object -Property LastWriteTime -Descending | + $rpmFile = Get-ChildItem -Path (Join-Path $rpmsDir $HostArchitecture) -Filter "*.rpm" -ErrorAction Stop | + Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 - + if ($rpmFile) { # Copy RPM to current location Copy-Item -Path $rpmFile.FullName -Destination $CurrentLocation -Force @@ -1337,7 +1337,7 @@ function New-UnixPackage { -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` -HostArchitecture $HostArchitecture ` -CurrentLocation $CurrentLocation - + $Output = @("Created package {:path=>""$($result.PackageName)""}") } catch { @@ -1348,7 +1348,7 @@ function New-UnixPackage { # Use native macOS packaging tools if ($PSCmdlet.ShouldProcess("Create macOS package with pkgbuild/productbuild")) { Write-Log "Creating macOS package with native tools..." - + $macPkgArgs = @{ Name = $Name Version = $packageVersion @@ -1363,7 +1363,7 @@ function New-UnixPackage { HostArchitecture = $HostArchitecture CurrentLocation = $CurrentLocation } - + try { $packageFile = New-MacOSPackage @macPkgArgs $Output = @("Created package {:path=>""$($packageFile.Name)""}") @@ -1386,7 +1386,7 @@ function New-UnixPackage { Clear-MacOSLauncher } } - + # Clean up rpmbuild directory if it was created if ($Type -eq 'rpm') { $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" @@ -1395,7 +1395,7 @@ function New-UnixPackage { Remove-Item -Path $rpmBuildRoot -Recurse -Force -ErrorAction SilentlyContinue } } - + if ($AfterScriptInfo.AfterInstallScript) { Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterInstallScript -Force } @@ -1489,13 +1489,13 @@ function New-MacOsDistributionPackage $resourcesDir = Join-Path -Path $tempDir -ChildPath 'resources' New-Item -ItemType Directory -Path $resourcesDir -Force > $null - + # Copy background file to temp directory $backgroundFile = "$RepoRoot/assets/macDialog.png" if (Test-Path $backgroundFile) { Copy-Item -Path $backgroundFile -Destination $resourcesDir -Force } - + # Copy the component package to temp directory $componentFileName = Split-Path -Leaf -Path $ComponentPackage $tempComponentPath = Join-Path -Path $tempDir -ChildPath $componentFileName @@ -1511,7 +1511,7 @@ function New-MacOsDistributionPackage # Minimum OS version $minOSVersion = "11.0" # macOS Big Sur minimum - + # format distribution template with: # 0 - title # 1 - version @@ -1522,8 +1522,10 @@ function New-MacOsDistributionPackage $PackagingStrings.OsxDistributionTemplate -f $PackageName, $Version, $componentFileName, $minOSVersion, $PackageIdentifier, $HostArchitecture | Out-File -Encoding utf8 -FilePath $distributionXmlPath -Force # Build final package path - $finalPackagePath = Join-Path $OutputDirectory "$PackageName-$Version-osx-$HostArchitecture.pkg" - + # Rename x86_64 to x64 for compatibility + $packageArchName = if ($HostArchitecture -eq "x86_64") { "x64" } else { $HostArchitecture } + $finalPackagePath = Join-Path $OutputDirectory "$PackageName-$Version-osx-$packageArchName.pkg" + # Remove existing package if it exists if (Test-Path $finalPackagePath) { Write-Warning "Removing existing package: $finalPackagePath" @@ -1542,7 +1544,7 @@ function New-MacOsDistributionPackage --resources $resourcesDir ` $finalPackagePath } - + if (Test-Path $finalPackagePath) { Write-Log "Successfully created macOS package: $finalPackagePath" } @@ -1612,7 +1614,7 @@ function New-RpmSpec # RPM doesn't allow hyphens in version, so convert them to underscores # e.g., "7.6.0-preview.6" becomes Version: 7.6.0_preview.6 $rpmVersion = $Version -replace '-', '_' - + # Build Release field with distribution suffix (e.g., "1.cm" or "1.rh") # Don't use RPM macros - build the full release string in PowerShell $rpmRelease = "$Iteration.$Distribution" @@ -1638,7 +1640,7 @@ AutoReq: no } else { # For cross-architecture builds, don't specify BuildArch in spec # The --target option will handle the architecture - + # Disable automatic binary stripping for cross-arch builds # The native /bin/strip on x86_64 cannot process ARM64 binaries and would fail with: # "Unable to recognise the format of the input file" @@ -1646,7 +1648,7 @@ AutoReq: no # __strip: This macro controls the command used for stripping binaries during the build process. # /bin/true: A command that does nothing and always exits successfully, effectively bypassing the stripping process. $specContent += "%define __strip /bin/true`n" - + # Disable debug package generation to prevent strip-related errors # Debug packages require binary stripping which fails for cross-arch builds # See: https://rpm-packaging-guide.github.io/#debugging @@ -1903,11 +1905,11 @@ $(if ($extendedDescription) { $extendedDescription + "`n" }) # Copy DEBIAN directory and data files to build root $buildDir = Join-Path $debBuildRoot "build" New-Item -ItemType Directory -Path $buildDir -Force | Out-Null - + Write-Verbose "debianDir: $debianDir" -Verbose Write-Verbose "dataDir: $dataDir" -Verbose Write-Verbose "buildDir: $buildDir" -Verbose - + # Use cp to preserve symlinks Start-NativeExecution { cp -a $debianDir "$buildDir/DEBIAN" } Start-NativeExecution { cp -a $dataDir/* $buildDir } @@ -2017,14 +2019,14 @@ function New-MacOSPackage $linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent) New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null $finalLinkPath = Join-Path $pkgRoot $link.Destination - + Write-Verbose "Creating symlink at $finalLinkPath" -Verbose - + # Remove if exists if (Test-Path $finalLinkPath) { Remove-Item $finalLinkPath -Force } - + # Get the target of the original symlink and recreate it in the package root if (Test-Path $link.Source) { $linkTarget = (Get-Item $link.Source).Target @@ -2050,10 +2052,10 @@ function New-MacOSPackage # Build the component package using pkgbuild $pkgIdentifier = Get-MacOSPackageId -IsPreview:($Name -like '*-preview') - + if ($PSCmdlet.ShouldProcess("Build component package with pkgbuild")) { Write-Log "Running pkgbuild to create component package..." - + Start-NativeExecution -VerboseOutputOnError { pkgbuild --root $pkgRoot ` --identifier $pkgIdentifier ` @@ -2062,7 +2064,7 @@ function New-MacOSPackage --install-location "/" ` $componentPkgPath } - + Write-Verbose "Component package created: $componentPkgPath" -Verbose } @@ -2075,7 +2077,7 @@ function New-MacOSPackage -HostArchitecture $HostArchitecture ` -PackageIdentifier $pkgIdentifier ` -IsPreview:($Name -like '*-preview') - + return $distributionPackage } finally {