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 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 } shell: pwsh - name: Get version id: get-version run: | $CsprojXml = [Xml] (Get-Content .\ffi\dotnet\Devolutions.IronRdp\Devolutions.IronRdp.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 shell: pwsh build-native: name: Native build needs: [preflight] runs-on: ${{matrix.runner}} 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' }} uses: Devolutions/actions-public/cargo-android-ndk@v1 with: android_api_level: "21" - name: Configure macOS deployement target if: ${{ matrix.os == 'osx' }} run: Write-Output "MACOSX_DEPLOYMENT_TARGET=10.10" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append shell: pwsh - name: Configure iOS deployement target if: ${{ matrix.os == 'ios' }} run: Write-Output "IPHONEOS_DEPLOYMENT_TARGET=12.1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append shell: pwsh - name: Update runner if: ${{ matrix.os == 'linux' }} run: sudo apt update - name: Install dependencies for rustls if: ${{ runner.os == 'Windows' }} run: | choco install ninja nasm # We need to add the NASM binary folder to the PATH manually. # We don't need to do that for ninja. Write-Output "PATH=$Env:PATH;$Env:ProgramFiles\NASM" >> $Env:GITHUB_ENV # libclang / LLVM is a requirement for AWS LC. # https://aws.github.io/aws-lc-rs/requirements/windows.html#libclang--llvm $VSINSTALLDIR = $(vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Llvm.Clang -property installationPath) Write-Output "LIBCLANG_PATH=$VSINSTALLDIR\VC\Tools\Llvm\x64\bin" >> $Env:GITHUB_ENV # Install Visual Studio Developer PowerShell Module for cmdlets such as Enter-VsDevShell Install-Module VsDevShell -Force shell: pwsh # No pre-generated bindings for Android and iOS. # https://aws.github.io/aws-lc-rs/platform_support.html#pre-generated-bindings - name: Install bindgen-cli for aws-lc-sys if: ${{ matrix.os == 'android' || matrix.os == 'ios' }} run: cargo install --force --locked bindgen-cli # For aws-lc-sys. Error returned otherwise: # > Unable to generate bindings: ClangDiagnostic("/usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found\n") - name: Install gcc-multilib if: ${{ matrix.os == 'android' }} run: | sudo apt-get update sudo apt-get install gcc-multilib - name: Setup LLVM if: ${{ matrix.os == 'linux' }} uses: Devolutions/actions-public/setup-llvm@v1 with: version: "18.1.8" - name: Setup CBake if: ${{ matrix.os == 'linux' }} uses: Devolutions/actions-public/setup-cbake@v1 with: cargo_env_scripts: true - name: Build native lib (${{matrix.os}}-${{matrix.arch}}) 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" } $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 if ($DotNetOs -eq 'linux') { $LinuxArch = @{'x64'='amd64';'arm64'='arm64'}[$DotNetArch] $Env:SYSROOT_NAME = "ubuntu-20.04-$LinuxArch" . "$HOME/.cargo/cbake/${RustTarget}-enter.ps1" $Env:AWS_LC_SYS_CMAKE_BUILDER="true" } $CargoParams = @( "build", "-p", "ffi", "--profile", "production-ffi", "--target", "$RustTarget" ) & cargo $CargoParams $OutputLibraryName = "${LibPrefix}ironrdp$LibSuffix" $RenamedLibraryName = "${LibPrefix}DevolutionsIronRdp$LibSuffix" $OutputLibrary = Join-Path "target" $RustTarget 'production-ffi' $OutputLibraryName $OutputPath = Join-Path "dependencies" "runtimes" $DotNetRid "native" New-Item -ItemType Directory -Path $OutputPath | Out-Null Copy-Item $OutputLibrary $(Join-Path $OutputPath $RenamedLibraryName) shell: pwsh - name: Upload native components uses: actions/upload-artifact@v4 with: name: ironrdp-${{matrix.os}}-${{matrix.arch}} path: dependencies/runtimes/${{matrix.os}}-${{matrix.arch}} build-universal: name: Universal build needs: [preflight, build-native] runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: os: [ osx, ios ] steps: - name: Checkout ${{ github.repository }} uses: actions/checkout@v4 - name: Setup CCTools uses: Devolutions/actions-public/setup-cctools@v1 - name: Download native components uses: actions/download-artifact@v4 with: path: dependencies/runtimes - name: Lipo native components 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 "ironrdp-${{ matrix.os }}-*" -Filter "*.dylib" | Foreach-Object { $_.FullName } | Select -Unique $LipoCmd = $(@('lipo', '-create', '-output', (Join-Path -Path $OutputPath -ChildPath "libDevolutionsIronRdp.dylib")) + $Libraries) -Join ' ' Write-Host $LipoCmd Invoke-Expression $LipoCmd shell: pwsh - name: Framework if: ${{ matrix.os == 'ios' }} run: | $Version = '${{ needs.preflight.outputs.project-version }}' $ShortVersion = '${{ needs.preflight.outputs.package-version }}' $BundleName = "libDevolutionsIronRdp" $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.IronRdp" "Info.plist") Select-Xml -xml $InfoPlistXml -XPath "/plist/dict/key[. = 'CFBundleIdentifier']/following-sibling::string[1]" | %{ $_.Node.InnerXml = "com.devolutions.ironrdp" } 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") shell: pwsh - name: Upload native components uses: actions/upload-artifact@v4 with: name: ironrdp-${{ matrix.os }}-universal path: dependencies/runtimes/${{ matrix.os }}-universal build-managed: name: Managed build needs: [build-universal] runs-on: windows-2022 steps: - name: Check out ${{ github.repository }} uses: actions/checkout@v4 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v2 - name: Install ios workload run: dotnet workload install ios - name: Prepare dependencies run: | New-Item -ItemType Directory -Path "dependencies/runtimes" | Out-Null shell: pwsh - name: Download native components uses: actions/download-artifact@v4 with: path: dependencies/runtimes - name: Rename dependencies run: | Set-Location "dependencies/runtimes" $(Get-Item ".\ironrdp-*") | ForEach-Object { Rename-Item $_ $_.Name.Replace("ironrdp-", "") } Get-ChildItem * -Recurse shell: pwsh - name: Build Devolutions.IronRdp (managed) run: | # net8.0 target packaged as Devolutions.IronRdp dotnet build .\ffi\dotnet\Devolutions.IronRdp\Devolutions.IronRdp.csproj -c Release # net9.0-ios target packaged as Devolutions.IronRdp.iOS dotnet build .\ffi\dotnet\Devolutions.IronRdp\Devolutions.IronRdp.csproj -c Release /p:PackageId=Devolutions.IronRdp.iOS shell: pwsh - name: Upload managed components uses: actions/upload-artifact@v4 with: name: ironrdp-nupkg path: ffi/dotnet/Devolutions.IronRdp/bin/Release/*.nupkg publish: name: Publish NuGet package environment: nuget-publish if: ${{ needs.preflight.outputs.dry-run == 'false' }} needs: [preflight, build-managed] runs-on: ubuntu-latest permissions: id-token: write steps: - name: Download NuGet package artifact uses: actions/download-artifact@v4 with: name: ironrdp-nupkg path: package - name: NuGet login (OIDC) uses: NuGet/login@v1 id: nuget-login with: user: ${{ secrets.NUGET_BOT_USERNAME }} - name: Publish to nuget.org run: | $Files = Get-ChildItem -Recurse package/*.nupkg foreach ($File in $Files) { $PushCmd = @( 'dotnet', 'nuget', 'push', "$File", '--api-key', '${{ steps.nuget-login.outputs.NUGET_API_KEY }}', '--source', 'https://api.nuget.org/v3/index.json', '--skip-duplicate' ) Write-Host "Publishing $($File.Name)..." $PushCmd = $PushCmd -Join ' ' Invoke-Expression $PushCmd } shell: pwsh notify: name: Notify failure if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }} needs: [preflight, build-native, build-universal, build-managed] runs-on: ubuntu-latest 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>" } } ] }