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
296 changes: 296 additions & 0 deletions .github/workflows/cli-packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
name: CLI NuGet Packages

on:
push:
tags-ignore:
- 'v*'
pull_request:
workflow_dispatch:
inputs:
dry-run:
description: Build and package only; skip publish/release actions.
required: false
default: true
type: boolean

permissions:
contents: read

env:
DOTNET_NOLOGO: true
CARGO_TERM_COLOR: always

jobs:
rust-cli:
name: Rust CLI (${{ matrix.rid }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- rid: linux-x64
os: ubuntu-latest
rust-target: x86_64-unknown-linux-gnu
- rid: linux-arm64
os: ubuntu-latest
rust-target: aarch64-unknown-linux-gnu
- rid: win-x64
os: windows-latest
rust-target: x86_64-pc-windows-msvc
- rid: win-arm64
os: windows-latest
rust-target: aarch64-pc-windows-msvc
- rid: osx-x64
os: macos-14
rust-target: x86_64-apple-darwin
- rid: osx-arm64
os: macos-14
rust-target: aarch64-apple-darwin

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install Rust toolchain
shell: pwsh
run: |
rustup toolchain install stable --profile minimal --target "${{ matrix.rust-target }}"
rustup default stable

- name: Install Linux ARM64 linker
if: matrix.rid == 'linux-arm64'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu

- name: Configure static MSVC runtime
if: contains(matrix.rust-target, 'windows-msvc')
shell: pwsh
run: |
"RUSTFLAGS=-C target-feature=+crt-static" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

- name: Build and stage Rust CLI
shell: pwsh
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
run: |
pwsh -NoLogo -NoProfile -File (Resolve-Path 'scripts\Build-CliNativeNuGetPackages.ps1') `
-Package Rust `
-RustRuntimeIdentifiers '${{ matrix.rid }}' `
-NoPack `
-Clean

- name: Upload Rust CLI artifact
uses: actions/upload-artifact@v7
with:
name: rust-cli-${{ matrix.rid }}
path: artifacts/cli/rust/${{ matrix.rid }}/*
if-no-files-found: error

dotnet-cli:
name: .NET NativeAOT CLI (${{ matrix.rid }})
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
rid:
- win-x64
- win-arm64

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x

- name: Build and stage .NET NativeAOT CLI
shell: pwsh
run: |
pwsh -NoLogo -NoProfile -File (Resolve-Path 'scripts\Build-CliNativeNuGetPackages.ps1') `
-Package DotNet `
-DotNetRuntimeIdentifiers '${{ matrix.rid }}' `
-NoPack `
-Clean

- name: Upload .NET CLI artifact
uses: actions/upload-artifact@v7
with:
name: dotnet-cli-${{ matrix.rid }}
path: artifacts/cli/dotnet-nativeaot/${{ matrix.rid }}/*
if-no-files-found: error

package:
name: Pack and smoke test
runs-on: ubuntu-latest
needs:
- rust-cli
- dotnet-cli

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x

- name: Download CLI artifacts
uses: actions/download-artifact@v8
with:
path: artifacts/download

- name: Arrange staged artifacts
shell: pwsh
run: |
$Mappings = @{
'rust-cli-linux-x64' = 'artifacts/cli/rust/linux-x64'
'rust-cli-linux-arm64' = 'artifacts/cli/rust/linux-arm64'
'rust-cli-win-x64' = 'artifacts/cli/rust/win-x64'
'rust-cli-win-arm64' = 'artifacts/cli/rust/win-arm64'
'rust-cli-osx-x64' = 'artifacts/cli/rust/osx-x64'
'rust-cli-osx-arm64' = 'artifacts/cli/rust/osx-arm64'
'dotnet-cli-win-x64' = 'artifacts/cli/dotnet-nativeaot/win-x64'
'dotnet-cli-win-arm64' = 'artifacts/cli/dotnet-nativeaot/win-arm64'
}

foreach ($mapping in $Mappings.GetEnumerator()) {
$source = Join-Path 'artifacts/download' $mapping.Key
if (-not (Test-Path -Path $source)) {
throw "Downloaded artifact directory not found: $source"
}

New-Item -Path $mapping.Value -ItemType Directory -Force | Out-Null
Copy-Item -Path (Join-Path $source '*') -Destination $mapping.Value -Recurse -Force
}

- name: Pack CLI NuGet packages
shell: pwsh
run: |
pwsh -NoLogo -NoProfile -File (Resolve-Path 'scripts\Build-CliNativeNuGetPackages.ps1') -NoBuild -Clean:$false

- name: Smoke test PackageReference copy targets
shell: pwsh
run: |
$packageSource = (Resolve-Path 'artifacts/cli-nuget').Path
$smokeRoot = Join-Path $env:RUNNER_TEMP 'pinget-cli-package-smoke'
$env:NUGET_PACKAGES = Join-Path $env:RUNNER_TEMP 'pinget-cli-package-smoke-nuget-cache'
New-Item -Path $smokeRoot -ItemType Directory -Force | Out-Null
New-Item -Path $env:NUGET_PACKAGES -ItemType Directory -Force | Out-Null
$rustPackage = Get-ChildItem -Path $packageSource -Filter 'Devolutions.Pinget.Cli.Rust.*.nupkg' | Select-Object -First 1
$dotNetPackage = Get-ChildItem -Path $packageSource -Filter 'Devolutions.Pinget.Cli.DotNet.*.nupkg' | Select-Object -First 1
if (($null -eq $rustPackage) -or ($null -eq $dotNetPackage)) {
throw 'Smoke test packages were not found.'
}
$rustVersion = $rustPackage.BaseName.Substring('Devolutions.Pinget.Cli.Rust.'.Length)
$dotNetVersion = $dotNetPackage.BaseName.Substring('Devolutions.Pinget.Cli.DotNet.'.Length)

dotnet new console --framework net10.0 --output $smokeRoot
if ($LASTEXITCODE -ne 0) { throw 'Smoke project creation failed.' }

$projectPath = Join-Path $smokeRoot 'pinget-cli-package-smoke.csproj'
$project = Get-Content -Path $projectPath -Raw
$project = $project -replace '</Project>', @"
<PropertyGroup>
<DevolutionsPingetRustCliWindowsOutputName>pinget-rust.exe</DevolutionsPingetRustCliWindowsOutputName>
<DevolutionsPingetDotNetCliWindowsOutputName>pinget-dotnet.exe</DevolutionsPingetDotNetCliWindowsOutputName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Devolutions.Pinget.Cli.Rust" Version="$rustVersion" />
<PackageReference Include="Devolutions.Pinget.Cli.DotNet" Version="$dotNetVersion" />
</ItemGroup>
</Project>
"@
Set-Content -Path $projectPath -Value $project -Encoding utf8

$nugetConfig = Join-Path $smokeRoot 'NuGet.Config'
@"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="$packageSource" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
"@ | Set-Content -Path $nugetConfig -Encoding utf8

dotnet build $projectPath --configfile $nugetConfig
if ($LASTEXITCODE -ne 0) { throw 'Smoke project build failed.' }

$expectedFiles = @(
'bin/Debug/net10.0/runtimes/win-x64/native/pinget.exe',
'bin/Debug/net10.0/runtimes/win-x64/native/pinget-rust.exe',
'bin/Debug/net10.0/runtimes/win-x64/native/pinget-dotnet.exe',
'bin/Debug/net10.0/runtimes/win-x64/native/e_sqlite3.dll',
'bin/Debug/net10.0/runtimes/linux-x64/native/pinget'
)

foreach ($relativePath in $expectedFiles) {
$path = Join-Path $smokeRoot $relativePath
if (-not (Test-Path -Path $path -PathType Leaf)) {
throw "Expected smoke output file was not found: $path"
}
}

- name: Smoke test dotnet tool package
shell: pwsh
run: |
$packageSource = (Resolve-Path 'artifacts/cli-nuget').Path
$toolPackage = Get-ChildItem -Path $packageSource -Filter 'Devolutions.Pinget.Tool.*.nupkg' | Select-Object -First 1
if ($null -eq $toolPackage) {
throw 'Devolutions.Pinget.Tool package was not found.'
}

Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = [System.IO.Compression.ZipFile]::OpenRead($toolPackage.FullName)
try {
$entries = @($archive.Entries | ForEach-Object { $_.FullName })
$nuspecEntry = $archive.Entries | Where-Object { $_.FullName -match '\.nuspec$' } | Select-Object -First 1
if ($null -eq $nuspecEntry) {
throw "Unable to find nuspec entry in $($toolPackage.Name)."
}

$reader = [System.IO.StreamReader]::new($nuspecEntry.Open())
try {
$nuspec = $reader.ReadToEnd()
}
finally {
$reader.Dispose()
}

if ($nuspec -notmatch '<packageType name="DotnetTool"') {
throw "Expected DotnetTool package type in $($toolPackage.Name)."
}

$ridSpecificEntries = @($entries | Where-Object { $_ -match '^runtimes/' -or $_ -match '^tools/[^/]+/(win|linux|osx)-' })
if ($ridSpecificEntries.Count -gt 0) {
throw "Framework-dependent tool package contains RID-specific entries: $($ridSpecificEntries -join ', ')"
}
}
finally {
$archive.Dispose()
}

$toolVersion = $toolPackage.BaseName.Substring('Devolutions.Pinget.Tool.'.Length)
$toolPath = Join-Path $env:RUNNER_TEMP 'pinget-dotnet-tool'
Remove-Item -Path $toolPath -Recurse -Force -ErrorAction SilentlyContinue
New-Item -Path $toolPath -ItemType Directory -Force | Out-Null

dotnet tool install --tool-path $toolPath Devolutions.Pinget.Tool --add-source $packageSource --version $toolVersion
if ($LASTEXITCODE -ne 0) { throw 'dotnet tool install failed.' }

$toolCommand = Join-Path $toolPath 'pinget'
& $toolCommand --help
if ($LASTEXITCODE -ne 0) { throw 'dotnet tool smoke test failed.' }

- name: Upload CLI NuGet packages
uses: actions/upload-artifact@v7
with:
name: cli-nuget
path: artifacts/cli-nuget/*.nupkg
if-no-files-found: error
Loading
Loading