diff --git a/.github/actions/install-build-deps/action.yml b/.github/actions/install-build-deps/action.yml new file mode 100644 index 000000000..e055089cd --- /dev/null +++ b/.github/actions/install-build-deps/action.yml @@ -0,0 +1,20 @@ +name: Install build dependencies +description: Install the native build dependencies required to build IronRDP (Linux ALSA headers, Windows NASM). + +runs: + using: composite + steps: + - name: Install devel packages + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + sudo apt-get update -qq + sudo apt-get -y install libasound2-dev + + - name: Install NASM + if: ${{ runner.os == 'Windows' }} + shell: pwsh + run: | + choco install nasm + $Env:PATH += ";$Env:ProgramFiles\NASM" + echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bca3fadfe..7580c589a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,19 +74,8 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Install devel packages - if: ${{ runner.os == 'Linux' }} - run: | - sudo apt-get update -qq - sudo apt-get -y install libasound2-dev - - - name: Install NASM - if: ${{ runner.os == 'Windows' }} - run: | - choco install nasm - $Env:PATH += ";$Env:ProgramFiles\NASM" - echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV - shell: pwsh + - name: Install build dependencies + uses: ./.github/actions/install-build-deps - name: Rust cache uses: Swatinem/rust-cache@v2.7.3 @@ -232,10 +221,8 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install devel packages - run: | - sudo apt-get update -qq - sudo apt-get -y install libasound2-dev + - name: Install build dependencies + uses: ./.github/actions/install-build-deps - name: Rust cache uses: Swatinem/rust-cache@v2.7.3 diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index e58278d58..182bb9528 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -51,20 +51,29 @@ jobs: strategy: fail-fast: false matrix: + # `target` is each runner's native triple; it is passed to cargo (see the build step) so a + # runner/target mismatch fails the build loudly instead of silently mislabeling an asset. + # `os_arch` is the friendly, triple-free label used for the asset and artifact names. include: - runner: windows-2022 target: x86_64-pc-windows-msvc + os_arch: windows-x64 - runner: windows-11-arm target: aarch64-pc-windows-msvc + os_arch: windows-arm64 - runner: ubuntu-22.04 target: x86_64-unknown-linux-gnu + os_arch: linux-x64 - runner: ubuntu-22.04-arm target: aarch64-unknown-linux-gnu + os_arch: linux-arm64 - runner: macos-15-intel target: x86_64-apple-darwin + os_arch: macos-x64 macos_deployment_target: '10.13' - runner: macos-14 target: aarch64-apple-darwin + os_arch: macos-arm64 macos_deployment_target: '11.0' steps: @@ -73,19 +82,8 @@ jobs: with: ref: ${{ github.event.release.tag_name }} - - name: Install Linux build dependencies - if: ${{ runner.os == 'Linux' }} - run: | - sudo apt-get update -qq - sudo apt-get -y install libasound2-dev - - - name: Install NASM - if: ${{ runner.os == 'Windows' }} - run: | - choco install nasm - $Env:PATH += ";$Env:ProgramFiles\NASM" - echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV - shell: pwsh + - name: Install build dependencies + uses: ./.github/actions/install-build-deps - name: Rust cache uses: Swatinem/rust-cache@v2.7.3 @@ -101,7 +99,8 @@ jobs: $env:MACOSX_DEPLOYMENT_TARGET = '${{ matrix.macos_deployment_target }}' } - cargo build --locked --release --package '${{ needs.select-package.outputs.package }}' + rustup target add '${{ matrix.target }}' + cargo build --locked --release --target '${{ matrix.target }}' --package '${{ needs.select-package.outputs.package }}' - name: Package binary shell: pwsh @@ -109,10 +108,11 @@ jobs: PACKAGE: ${{ needs.select-package.outputs.package }} VERSION: ${{ needs.select-package.outputs.version }} TARGET: ${{ matrix.target }} + OS_ARCH: ${{ matrix.os_arch }} run: | $extension = if ($env:RUNNER_OS -eq 'Windows') { '.exe' } else { '' } - $binary = Join-Path (Join-Path 'target' 'release') "$env:PACKAGE$extension" - $assetName = "$env:PACKAGE-$env:VERSION-$env:TARGET.tar.gz" + $binary = Join-Path (Join-Path (Join-Path 'target' $env:TARGET) 'release') "$env:PACKAGE$extension" + $assetName = "$env:PACKAGE-$env:VERSION-$env:OS_ARCH.tar.gz" $assetDirectory = 'release-assets' New-Item -ItemType Directory -Force -Path $assetDirectory | Out-Null @@ -128,14 +128,22 @@ jobs: - name: Upload release asset uses: actions/upload-artifact@v7 with: - name: release-assets-${{ matrix.target }} + name: release-assets-${{ matrix.os_arch }} path: release-assets/* if-no-files-found: error retention-days: 1 + # Allow a full "Re-run all jobs" (the recovery path) to re-upload over a prior attempt's + # artifact of the same name instead of failing. + overwrite: true publish: name: Upload release assets needs: [select-package, build] + # All-or-nothing on purpose: with `fail-fast: false`, if any one of the matrix legs fails, + # `needs.build.result` is not 'success' and NO assets are uploaded, rather than publishing a + # partial set. The Release itself is already published by release-plz, so a failed build leaves + # it without binaries; recovery is re-running this workflow. The final `gh release upload + # --clobber` and the notes step below are both idempotent, so re-runs are safe. if: ${{ always() && needs.select-package.outputs.package != '' && needs.build.result == 'success' }} runs-on: ubuntu-latest @@ -152,3 +160,88 @@ jobs: GH_TOKEN: ${{ github.token }} TAG_NAME: ${{ github.event.release.tag_name }} run: gh release upload "$TAG_NAME" release-assets/* --clobber + + - name: Update release notes with install instructions + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ github.event.release.tag_name }} + PACKAGE: ${{ needs.select-package.outputs.package }} + VERSION: ${{ needs.select-package.outputs.version }} + run: | + $ErrorActionPreference = 'Stop' + + $begin = '' + $end = '' + + # Literal template: the ${PACKAGE}/${VERSION} placeholders are substituted below; the tag is + # derived as ${PACKAGE}-v${VERSION}. Example variables ($ASSET, $BASE, $Asset) stay literal + # because the here-string is single-quoted (no interpolation). + $template = @' + + ## Prebuilt binaries + + Prebuilt, checksummed archives of `${PACKAGE}` are attached to this release: + + ${ASSET_LIST} + + Each archive has a matching `.sha256` sidecar. The Windows archive contains `${PACKAGE}.exe`; + the others contain the bare `${PACKAGE}` executable. + + ### Download and verify + + Pick an archive from the list above, then download, verify, and extract it. The two blocks + below are self-contained; use the one for your platform. + + Linux and macOS (POSIX shell): + + ```shell + ASSET="" + BASE="https://github.com/Devolutions/IronRDP/releases/download/${PACKAGE}-v${VERSION}" + curl -fLO "$BASE/$ASSET" + curl -fLO "$BASE/$ASSET.sha256" + sha256sum --check "$ASSET.sha256" # on macOS: shasum -a 256 --check "$ASSET.sha256" + tar -xzf "$ASSET" + ``` + + Windows (PowerShell): + + ```powershell + $Asset = "" + $Base = "https://github.com/Devolutions/IronRDP/releases/download/${PACKAGE}-v${VERSION}" + curl.exe -fLO "$Base/$Asset" + curl.exe -fLO "$Base/$Asset.sha256" + if ((Get-FileHash -Algorithm SHA256 $Asset).Hash.ToLowerInvariant() -ne (Get-Content "$Asset.sha256").Split(' ')[0]) { throw 'checksum mismatch' } + tar -xzf $Asset + ``` + + ### Build baselines + + - Linux archives are built on Ubuntu 22.04 and require glibc 2.35 or later. + - macOS archives target macOS 10.13 or later on Intel and macOS 11.0 or later on Apple Silicon. + + Assets are attached to this release (tag `${PACKAGE}-v${VERSION}`). Don't rely on the `latest` release URL: + releases are tagged per package (`ironrdp-agent-v*`, `ironrdp-viewer-v*`), so `latest` may resolve + to the other package. + + '@ + + # Build the asset list from the archives actually attached to this release, so it can never + # drift from the build matrix (adding/renaming a target updates the notes automatically). + $assetList = (Get-ChildItem -Path release-assets -Filter *.tar.gz | Sort-Object Name | ForEach-Object { "- ``$($_.Name)``" }) -join "`n" + + $block = $template.Replace('${PACKAGE}', $env:PACKAGE).Replace('${VERSION}', $env:VERSION).Replace('${ASSET_LIST}', $assetList) + + $body = (gh release view $env:TAG_NAME --json body -q .body | Out-String).TrimEnd() + + # Idempotent inject: replace the sentinel-delimited region if present, otherwise append. + $beginIndex = $body.IndexOf($begin) + $endIndex = $body.IndexOf($end) + if ($beginIndex -ge 0 -and $endIndex -ge 0) { + $newBody = $body.Substring(0, $beginIndex) + $block + $body.Substring($endIndex + $end.Length) + } else { + $newBody = if ($body) { "$body`n`n$block" } else { $block } + } + + Set-Content -Path newbody.md -Value $newBody -Encoding utf8 + gh release edit $env:TAG_NAME --notes-file newbody.md diff --git a/README.md b/README.md index a74a82601..fb3b3e8ce 100644 --- a/README.md +++ b/README.md @@ -67,39 +67,15 @@ Alternatively, you may change a few group policies using `gpedit.msc`: ## Binary releases -Standalone archives are attached to GitHub Releases for the executable packages: +Prebuilt, checksummed `.tar.gz` archives are attached to each GitHub Release for the executable +packages, one per supported platform: -- [`ironrdp-agent`](./crates/ironrdp-agent) provides the agentic, daemon-backed CLI. -- [`ironrdp-viewer`](./crates/ironrdp-viewer) provides the windowed RDP client CLI. +- [`ironrdp-agent`](./crates/ironrdp-agent) — the agentic, daemon-backed CLI. +- [`ironrdp-viewer`](./crates/ironrdp-viewer) — the windowed RDP client CLI. -Each release provides one `.tar.gz` archive and a SHA-256 sidecar for these native target triples: - -| Platform | Target triple | -| --- | --- | -| Windows x64 | `x86_64-pc-windows-msvc` | -| Windows ARM64 | `aarch64-pc-windows-msvc` | -| Linux x64 | `x86_64-unknown-linux-gnu` | -| Linux ARM64 | `aarch64-unknown-linux-gnu` | -| macOS x64 | `x86_64-apple-darwin` | -| macOS ARM64 | `aarch64-apple-darwin` | - -Linux archives use an Ubuntu 22.04 build baseline and require glibc 2.35 or later. macOS archives -target macOS 10.13 or later on Intel and macOS 11.0 or later on Apple Silicon. - -For example, download and extract the Linux x64 agent from its release: - -```shell -VERSION= -ASSET="ironrdp-agent-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" -curl -fLO "https://github.com/Devolutions/IronRDP/releases/download/ironrdp-agent-v${VERSION}/${ASSET}" -curl -fLO "https://github.com/Devolutions/IronRDP/releases/download/ironrdp-agent-v${VERSION}/${ASSET}.sha256" -sha256sum --check "${ASSET}.sha256" -tar -xzf "${ASSET}" -``` - -Replace `ironrdp-agent` with `ironrdp-viewer` to download the windowed client from its corresponding -package release. Windows archives contain an `.exe`; all other archives contain the executable without -an extension. +Each package is released under its own tag (`ironrdp-agent-v*`, `ironrdp-viewer-v*`). See the +[Releases page](https://github.com/Devolutions/IronRDP/releases) to pick a release and follow the +per-platform download, checksum, and extraction instructions included in its notes. ## Rust version (MSRV) diff --git a/crates/ironrdp-agent/README.md b/crates/ironrdp-agent/README.md index ba376a9ea..4d59f08de 100644 --- a/crates/ironrdp-agent/README.md +++ b/crates/ironrdp-agent/README.md @@ -13,6 +13,12 @@ The single `ironrdp-agent` binary bundles two roles: Run `ironrdp-agent --help-agent` for a structured, machine-readable description of every operation. +## Prebuilt binaries + +Prebuilt, checksummed archives are attached to each GitHub Release under the `ironrdp-agent-v*` +tags. See the [Releases page](https://github.com/Devolutions/IronRDP/releases) for per-platform +download and verification instructions. + ## Wire format Messages are encoded with [`ironrdp-core`]'s `Encode`/`Decode` traits, length-delimited with a diff --git a/crates/ironrdp-viewer/README.md b/crates/ironrdp-viewer/README.md index 18710c2fb..2361ec078 100644 --- a/crates/ironrdp-viewer/README.md +++ b/crates/ironrdp-viewer/README.md @@ -6,6 +6,12 @@ This is a a full-fledged RDP client based on IronRDP crates suite, and implement non-blocking, asynchronous I/O. Portability is achieved by using softbuffer for rendering and winit for windowing. +## Prebuilt binaries + +Prebuilt, checksummed archives are attached to each GitHub Release under the `ironrdp-viewer-v*` +tags. See the [Releases page](https://github.com/Devolutions/IronRDP/releases) for per-platform +download and verification instructions. + ## Sample usage ```shell diff --git a/release-plz.toml b/release-plz.toml index 080b4d51a..4f3a3395e 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -7,7 +7,8 @@ pr_name = "chore(release): prepare for publishing" changelog_config = "cliff.toml" release_commits = "^(feat|docs|fix|build|perf)" -# Flagship crate for which we push a GitHub release. +# Executable crates for which we push a GitHub release (and attach prebuilt binaries via +# release-binaries.yml). Each is released under its own tag (ironrdp-agent-v*, ironrdp-viewer-v*). [[package]] name = "ironrdp-agent" git_release_enable = true