Publish NuGet package #285
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish NuGet package | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry-run: | |
| description: 'Dry run' | |
| required: true | |
| type: boolean | |
| default: true | |
| schedule: | |
| - cron: '21 3 * * 1' # 3:21 AM UTC every Monday | |
| jobs: | |
| preflight: | |
| name: Preflight | |
| runs-on: ubuntu-latest | |
| outputs: | |
| dry-run: ${{ steps.get-dry-run.outputs.dry-run }} | |
| project-version: ${{ steps.get-version.outputs.project-version }} | |
| package-version: ${{ steps.get-version.outputs.package-version }} | |
| steps: | |
| - name: Checkout ${{ github.repository }} | |
| uses: actions/checkout@v4 | |
| - name: Get dry run | |
| id: get-dry-run | |
| shell: pwsh | |
| run: | | |
| $IsDryRun = '${{ github.event.inputs.dry-run }}' -Eq 'true' -Or '${{ github.event_name }}' -Eq 'schedule' | |
| if ($IsDryRun) { | |
| echo "dry-run=true" >> $Env:GITHUB_OUTPUT | |
| } else { | |
| echo "dry-run=false" >> $Env:GITHUB_OUTPUT | |
| } | |
| - name: Get version | |
| id: get-version | |
| shell: pwsh | |
| run: | | |
| $CsprojXml = [Xml] (Get-Content .\ffi\dotnet\Devolutions.Picky\Devolutions.Picky.csproj) | |
| $ProjectVersion = $CsprojXml.Project.PropertyGroup.Version | Select-Object -First 1 | |
| $PackageVersion = $ProjectVersion -Replace "^(\d+)\.(\d+)\.(\d+).(\d+)$", "`$1.`$2.`$3" | |
| echo "project-version=$ProjectVersion" >> $Env:GITHUB_OUTPUT | |
| echo "package-version=$PackageVersion" >> $Env:GITHUB_OUTPUT | |
| build-native: | |
| name: Native build | |
| runs-on: ${{matrix.runner}} | |
| needs: preflight | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ win, osx, linux, ios, android ] | |
| arch: [ x86, x64, arm, arm64 ] | |
| include: | |
| - os: win | |
| runner: windows-2022 | |
| - os: osx | |
| runner: macos-14 | |
| - os: linux | |
| runner: ubuntu-22.04 | |
| - os: ios | |
| runner: macos-14 | |
| - os: android | |
| runner: ubuntu-22.04 | |
| exclude: | |
| - arch: arm | |
| os: win | |
| - arch: arm | |
| os: osx | |
| - arch: arm | |
| os: linux | |
| - arch: arm | |
| os: ios | |
| - arch: x86 | |
| os: win | |
| - arch: x86 | |
| os: osx | |
| - arch: x86 | |
| os: linux | |
| - arch: x86 | |
| os: ios | |
| steps: | |
| - name: Checkout ${{ github.repository }} | |
| uses: actions/checkout@v4 | |
| - name: Configure Android NDK | |
| if: matrix.os == 'android' | |
| shell: pwsh | |
| run: | | |
| $CargoConfigFile = "~/.cargo/config.toml" | |
| $AndroidToolchain="${Env:ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64" | |
| Get-ChildItem -Path $AndroidToolchain "libunwind.a" -Recurse | ForEach-Object { | |
| $libunwind = $_.FullName | |
| $libgcc = Join-Path $_.DirectoryName "libgcc.a" | |
| if (-Not (Test-Path $libgcc)) { | |
| Write-Host $libgcc | |
| Copy-Item $libunwind $libgcc | |
| } | |
| } | |
| Get-ChildItem -Path "$AndroidToolchain/bin/" | |
| echo "[target.i686-linux-android]" >> $CargoConfigFile | |
| echo "linker=`"$AndroidToolchain/bin/i686-linux-android21-clang`"" >> $CargoConfigFile | |
| echo "rustflags = [`"-C`", `"link-args=-z max-page-size=16384`"]" >> $CargoConfigFile | |
| echo "[target.x86_64-linux-android]" >> $CargoConfigFile | |
| echo "linker=`"$AndroidToolchain/bin/x86_64-linux-android21-clang`"" >> $CargoConfigFile | |
| echo "rustflags = [`"-C`", `"link-args=-z max-page-size=16384`"]" >> $CargoConfigFile | |
| echo "[target.armv7-linux-androideabi]" >> $CargoConfigFile | |
| echo "linker=`"$AndroidToolchain/bin/armv7a-linux-androideabi21-clang`"" >> $CargoConfigFile | |
| echo "rustflags = [`"-C`", `"link-args=-z max-page-size=16384`"]" >> $CargoConfigFile | |
| echo "[target.aarch64-linux-android]" >> $CargoConfigFile | |
| echo "linker=`"$AndroidToolchain/bin/aarch64-linux-android21-clang`"" >> $CargoConfigFile | |
| echo "rustflags = [`"-C`", `"link-args=-z max-page-size=16384`"]" >> $CargoConfigFile | |
| - name: Setup build environment | |
| shell: pwsh | |
| run: | | |
| if ('${{ matrix.os }}' -Eq 'osx') { | |
| echo "MACOSX_DEPLOYMENT_TARGET=10.10" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append | |
| } elseif ('${{ matrix.os }}' -Eq 'ios') { | |
| echo "IPHONEOS_DEPLOYMENT_TARGET=12.1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append | |
| } | |
| - name: Install clang+llvm | |
| if: matrix.os == 'linux' | |
| shell: pwsh | |
| run: | | |
| $ClangLlvmVersion='18.1.8' | |
| $ClangLlvmArch='x86_64' | |
| $ClangLlvmPlatform=@{'win'='windows';'osx'='macos';'linux'='ubuntu-22.04'}['${{matrix.os}}'] | |
| $ClangLlvmBaseUrl="https://github.com/awakecoding/llvm-prebuilt/releases/download/v2025.2.0" | |
| $ClangLlvmName="clang+llvm-${ClangLlvmVersion}-${ClangLlvmArch}-${ClangLlvmPlatform}" | |
| wget -q "${ClangLlvmBaseUrl}/${ClangLlvmName}.tar.xz" | |
| $LLVM_PREFIX="/opt/llvm" | |
| tar -xf "${ClangLlvmName}.tar.xz" -C /tmp | |
| sudo mv "/tmp/${ClangLlvmName}" $LLVM_PREFIX | |
| $LLVM_BIN_PATH="$LLVM_PREFIX/bin" | |
| echo "PATH=$LLVM_BIN_PATH$([IO.Path]::PathSeparator)$Env:PATH" >> $Env:GITHUB_ENV | |
| - name: Configure CBake | |
| if: matrix.os == 'linux' | |
| shell: pwsh | |
| run: | | |
| $CBakeVersion = "v2025.02.18.0" | |
| $CBakeRepoUrl = "https://github.com/Devolutions/CBake" | |
| $CBakeDownloadUrl = "$CBakeRepoUrl/releases/download/$CBakeVersion" | |
| git clone -b $CBakeVersion "https://github.com/Devolutions/CBake" cbake | |
| @('ubuntu-20.04-amd64-sysroot.tar.xz', 'ubuntu-20.04-arm64-sysroot.tar.xz') | % { | |
| curl -L --output "cbake/sysroots/$_" "$CBakeDownloadUrl/$_" | |
| tar -C "cbake/sysroots" -xf "cbake/sysroots/$_" | |
| Remove-Item "cbake/sysroots/$_" | Out-Null | |
| } | |
| $CBakeHome = Join-Path (Get-Location) 'cbake' | |
| echo "CBAKE_HOME=$CBakeHome" >> $Env:GITHUB_ENV | |
| sudo apt install ninja-build g++-aarch64-linux-gnu | |
| - name: Build picky (${{matrix.os}}-${{matrix.arch}}) | |
| shell: pwsh | |
| run: | | |
| $DotNetOs = '${{matrix.os}}' | |
| $DotNetArch = '${{matrix.arch}}' | |
| $DotNetRid = '${{matrix.os}}-${{matrix.arch}}' | |
| $RustArch = @{'x64'='x86_64';'arm64'='aarch64'; | |
| 'x86'='i686';'arm'='armv7'}[$DotNetArch] | |
| $RustPlatform = @{'win'='pc-windows-msvc'; | |
| 'osx'='apple-darwin';'ios'='apple-ios'; | |
| 'linux'='unknown-linux-gnu';'android'='linux-android'}[$DotNetOs] | |
| $LibPrefix = @{'win'='';'osx'='lib';'ios'='lib'; | |
| 'linux'='lib';'android'='lib'}[$DotNetOs] | |
| $LibSuffix = @{'win'='.dll';'osx'='.dylib';'ios'='.dylib'; | |
| 'linux'='.so';'android'='.so'}[$DotNetOs] | |
| $RustTarget = "$RustArch-$RustPlatform" | |
| if (($DotNetOs -eq 'android') -and ($DotNetArch -eq 'arm')) { | |
| $RustTarget = "armv7-linux-androideabi" | |
| } | |
| rustup target add $RustTarget | |
| if ($DotNetOs -eq 'win') { | |
| $Env:RUSTFLAGS="-C target-feature=+crt-static" | |
| } | |
| if ($DotNetOs -eq 'linux') { | |
| $LinuxArch = @{'x64'='amd64';'arm64'='arm64'}[$DotNetArch] | |
| $CMakeToolchainFile = Join-Path $Env:CBAKE_HOME "cmake/linux.toolchain.cmake" | |
| $Env:SYSROOT_NAME = "ubuntu-20.04-$LinuxArch" | |
| $CMakeSourceDir = "$Env:CBAKE_HOME/cargo" | |
| $CMakeBuildDir = "$Env:CBAKE_HOME/cargo/${Env:SYSROOT_NAME}" | |
| $CMakeArgs = @( | |
| "-DSYSROOT_NAME=$Env:SYSROOT_NAME", | |
| "-DCMAKE_TOOLCHAIN_FILE=$CMakeToolchainFile" | |
| ) | |
| cmake $CMakeSourceDir -G Ninja -B $CMakeBuildDir $CMakeArgs | |
| . "$HOME/.cargo/cbake/${RustTarget}-enter.ps1" | |
| } | |
| $ProjectVersion = '${{ needs.preflight.outputs.project-version }}' | |
| $PackageVersion = '${{ needs.preflight.outputs.package-version }}' | |
| $CargoToml = Get-Content .\ffi\Cargo.toml | |
| $CargoToml = $CargoToml | ForEach-Object { | |
| if ($_.StartsWith("version =")) { "version = `"$PackageVersion`"" } else { $_ } | |
| } | |
| Set-Content -Path .\ffi\Cargo.toml -Value $CargoToml | |
| $CargoParams = @( | |
| "build", | |
| "-p", "picky-ffi", | |
| "--release", | |
| "--target", "$RustTarget" | |
| ) | |
| & cargo $CargoParams | |
| $OutputLibraryName = "${LibPrefix}picky$LibSuffix" | |
| $RenamedLibraryName = "${LibPrefix}DevolutionsPicky$LibSuffix" | |
| $OutputLibrary = Join-Path "target" $RustTarget 'release' $OutputLibraryName | |
| $OutputPath = Join-Path "dependencies" "runtimes" $DotNetRid "native" | |
| New-Item -ItemType Directory -Path $OutputPath | Out-Null | |
| Copy-Item $OutputLibrary $(Join-Path $OutputPath $RenamedLibraryName) | |
| - name: Upload native components | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: picky-${{matrix.os}}-${{matrix.arch}} | |
| path: dependencies/runtimes/${{matrix.os}}-${{matrix.arch}} | |
| build-universal: | |
| name: Universal build | |
| runs-on: ubuntu-24.04 | |
| needs: [preflight, build-native] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ osx, ios ] | |
| steps: | |
| - name: Checkout ${{ github.repository }} | |
| uses: actions/checkout@v4 | |
| - name: Configure runner | |
| shell: pwsh | |
| run: | | |
| wget -q https://github.com/awakecoding/llvm-prebuilt/releases/download/v2025.2.0/cctools-x86_64-ubuntu-24.04.tar.xz | |
| tar -xf cctools-x86_64-ubuntu-24.04.tar.xz -C /tmp | |
| sudo mv /tmp/cctools-x86_64-ubuntu-24.04/bin/lipo /usr/local/bin | |
| sudo mv /tmp/cctools-x86_64-ubuntu-24.04/bin/install_name_tool /usr/local/bin | |
| - name: Download native components | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dependencies/runtimes | |
| - name: Lipo native components | |
| shell: pwsh | |
| run: | | |
| Set-Location "dependencies/runtimes" | |
| # No RID for universal binaries, see: https://github.com/dotnet/runtime/issues/53156 | |
| $OutputPath = Join-Path "${{ matrix.os }}-universal" "native" | |
| New-Item -ItemType Directory -Path $OutputPath | Out-Null | |
| $Libraries = Get-ChildItem -Recurse -Path "picky-${{ matrix.os }}-*" -Filter "*.dylib" | Foreach-Object { $_.FullName } | Select -Unique | |
| $LipoCmd = $(@('lipo', '-create', '-output', (Join-Path -Path $OutputPath -ChildPath "libDevolutionsPicky.dylib")) + $Libraries) -Join ' ' | |
| Write-Host $LipoCmd | |
| Invoke-Expression $LipoCmd | |
| - name: Framework | |
| shell: pwsh | |
| if: ${{ matrix.os == 'ios' }} | |
| run: | | |
| $Version = '${{ needs.preflight.outputs.project-version }}' | |
| $ShortVersion = '${{ needs.preflight.outputs.package-version }}' | |
| $BundleName = "libDevolutionsPicky" | |
| $RuntimesDir = Join-Path "dependencies" "runtimes" "ios-universal" "native" | |
| $FrameworkDir = Join-Path "$RuntimesDir" "$BundleName.framework" | |
| New-Item -Path $FrameworkDir -ItemType "directory" -Force | |
| $FrameworkExecutable = Join-Path $FrameworkDir $BundleName | |
| Copy-Item -Path (Join-Path "$RuntimesDir" "$BundleName.dylib") -Destination $FrameworkExecutable -Force | |
| $RPathCmd = $(@('install_name_tool', '-id', "@rpath/$BundleName.framework/$BundleName", "$FrameworkExecutable")) -Join ' ' | |
| Write-Host $RPathCmd | |
| Invoke-Expression $RPathCmd | |
| [xml] $InfoPlistXml = Get-Content (Join-Path "ffi" "dotnet" "Devolutions.Picky" "Info.plist") | |
| Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleIdentifier']/following-sibling::string[1]" | | |
| %{ | |
| $_.Node.InnerXml = "com.devolutions.picky" | |
| } | |
| Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleExecutable']/following-sibling::string[1]" | | |
| %{ | |
| $_.Node.InnerXml = $BundleName | |
| } | |
| Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" | | |
| %{ | |
| $_.Node.InnerXml = $Version | |
| } | |
| Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleShortVersionString']/following-sibling::string[1]" | | |
| %{ | |
| $_.Node.InnerXml = $ShortVersion | |
| } | |
| # Write the plist *without* a BOM | |
| $Encoding = New-Object System.Text.UTF8Encoding($false) | |
| $Writer = New-Object System.IO.StreamWriter((Join-Path $FrameworkDir "Info.plist"), $false, $Encoding) | |
| $InfoPlistXml.Save($Writer) | |
| $Writer.Close() | |
| # .NET XML document inserts two square brackets at the end of the DOCTYPE tag | |
| # It's perfectly valid XML, but we're dealing with plists here and dyld will not be able to read the file | |
| ((Get-Content -Path (Join-Path $FrameworkDir "Info.plist") -Raw) -Replace 'PropertyList-1.0.dtd"\[\]', 'PropertyList-1.0.dtd"') | Set-Content -Path (Join-Path $FrameworkDir "Info.plist") | |
| - name: Upload native components | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: picky-${{ matrix.os }}-universal | |
| path: dependencies/runtimes/${{ matrix.os }}-universal | |
| build-managed: | |
| name: Managed build | |
| runs-on: windows-2022 | |
| needs: build-universal | |
| steps: | |
| - name: Check out ${{ github.repository }} | |
| uses: actions/checkout@v4 | |
| - name: Install ios workload | |
| run: dotnet workload install ios | |
| - name: Prepare dependencies | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Path "dependencies/runtimes" | Out-Null | |
| - name: Download native components | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dependencies/runtimes | |
| - name: Rename dependencies | |
| shell: pwsh | |
| run: | | |
| Set-Location "dependencies/runtimes" | |
| $(Get-Item ".\picky-*") | ForEach-Object { Rename-Item $_ $_.Name.Replace("picky-", "") } | |
| Get-ChildItem * -Recurse | |
| - name: Build picky (managed) | |
| shell: pwsh | |
| run: | | |
| dotnet build .\ffi\dotnet\Devolutions.Picky\Devolutions.Picky.csproj -c Release | |
| - name: Upload managed components | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: picky-nupkg | |
| path: ffi/dotnet/Devolutions.Picky/bin/Release/*.nupkg | |
| publish: | |
| name: Publish NuGet package | |
| runs-on: ubuntu-latest | |
| environment: nuget-publish | |
| if: needs.preflight.outputs.dry-run == 'false' | |
| needs: | |
| - preflight | |
| - build-managed | |
| steps: | |
| - name: Download NuGet package artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: picky-nupkg | |
| path: package | |
| - name: Publish to nuget.org | |
| shell: pwsh | |
| run: | | |
| $Files = Get-ChildItem -Recurse package/*.nupkg | |
| foreach ($File in $Files) { | |
| $PushCmd = @( | |
| 'dotnet', | |
| 'nuget', | |
| 'push', | |
| "$File", | |
| '--api-key', | |
| '${{ secrets.NUGET_API_KEY }}', | |
| '--source', | |
| 'https://api.nuget.org/v3/index.json', | |
| '--skip-duplicate' | |
| ) | |
| Write-Host "Publishing $($File.Name)..." | |
| $PushCmd = $PushCmd -Join ' ' | |
| Invoke-Expression $PushCmd | |
| } | |
| notify: | |
| name: Notify failure | |
| runs-on: ubuntu-latest | |
| if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }} | |
| needs: | |
| - preflight | |
| - build-universal | |
| - build-managed | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| steps: | |
| - name: Send slack notification | |
| id: slack | |
| uses: slackapi/slack-github-action@v1.26.0 | |
| with: | |
| payload: | | |
| { | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*${{ github.repository }}* :fire::fire::fire::fire::fire: \n The scheduled build for *${{ github.repository }}* is <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|broken>" | |
| } | |
| } | |
| ] | |
| } |