name: Build on: push: branches: [main, master] pull_request: workflow_dispatch: # Reused by release.yml, which builds from the release tag. workflow_call: inputs: ref: description: Git ref to build (defaults to the triggering commit) type: string required: false env: CARGO_TERM_COLOR: always jobs: build: name: Build (${{ matrix.artifact }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # The Linux x86_64 binary is built in package-linux below, which # shares one cargo build between the standalone binary and packages. include: # The .dmg is already a compressed container — no extra zip. - os: macos-latest artifact: filegram-macos-universal binary: target/release/Filegram.dmg asset: filegram-macos-universal.dmg - os: windows-latest artifact: filegram-windows-x86_64 binary: target/release/filegram.exe asset: filegram-windows-x86_64.exe zip: filegram-windows-x86_64.zip arch: x64 setup: filegram-windows-x86_64-setup.exe - os: windows-latest target: i686-pc-windows-msvc artifact: filegram-windows-i686 binary: target/i686-pc-windows-msvc/release/filegram.exe asset: filegram-windows-i686.exe zip: filegram-windows-i686.zip arch: x86 setup: filegram-windows-i686-setup.exe steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.sha }} - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: key: ${{ matrix.target }} # On macOS the bundle script builds both architectures itself. - name: Build release if: runner.os != 'macOS' run: cargo build --release ${{ matrix.target && format('--target {0}', matrix.target) || '' }} - name: Bundle macOS app (universal) if: runner.os == 'macOS' run: ./scripts/bundle-macos.sh - name: Rename binary to asset name shell: bash run: cp '${{ matrix.binary }}' '${{ matrix.asset }}' - name: Create zip archive if: matrix.zip shell: bash run: | if [[ "$RUNNER_OS" == "Windows" ]]; then 7z a '${{ matrix.zip }}' '${{ matrix.asset }}' else zip '${{ matrix.zip }}' '${{ matrix.asset }}' fi # Inno Setup ships preinstalled on windows-latest; iscc is on PATH. # The installer registers App Paths + Apps & Features and drops a Start # Menu shortcut so the app is reachable from Windows Search. - name: Build Inno Setup installer if: matrix.setup shell: pwsh run: | $version = (Select-String -Path Cargo.toml -Pattern '^version = "([^"]+)"').Matches[0].Groups[1].Value $numeric = $version -replace '-dev$','' $root = $env:GITHUB_WORKSPACE $outputBase = '${{ matrix.setup }}' -replace '\.exe$','' iscc ` "/DAppVersion=$numeric" ` "/DAppFullVersion=$version" ` "/DAppArch=${{ matrix.arch }}" ` "/DSourceExe=$root\${{ matrix.asset }}" ` "/DIconFile=$root\assets\icon\filegram.ico" ` "/DOutputDir=$root" ` "/DOutputName=$outputBase" ` packaging\windows\filegram.iss - name: Upload artifact uses: actions/upload-artifact@v7 with: name: ${{ matrix.artifact }} path: | ${{ matrix.asset }} ${{ matrix.zip }} ${{ matrix.setup }} package-linux: name: Build & package (Linux x86_64) # Pinned (not ubuntu-latest) to keep the glibc baseline of the AppImage # and the standalone binary stable (and as old as practical). runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.sha }} # The artifact names below hardcode x86_64. - name: Verify runner architecture matches artifact names run: test "$(uname -m)" = "x86_64" - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 with: # The image version is not part of the default cache key; a target/ # built on another image restores as corrupt (E0463 on deps). key: ubuntu-22.04 - name: Install Linux dependencies run: | sudo apt-get update # dpkg-dev provides dpkg-shlibdeps for cargo-deb's $auto depends. sudo apt-get install -y libxkbcommon-dev libwayland-dev pkg-config dpkg-dev # Built from source: the prebuilt cargo-deb binaries target a glibc # newer than ubuntu-22.04 ships. The compiled tools are cached. - name: Cache packaging tools id: tools-cache uses: actions/cache@v4 with: path: | ~/.cargo/bin/cargo-deb ~/.cargo/bin/cargo-generate-rpm key: packaging-tools-ubuntu-22.04-cargo-deb-3.7.0-generate-rpm-0.21.0 - name: Install packaging tools if: steps.tools-cache.outputs.cache-hit != 'true' run: cargo install --locked cargo-deb@3.7.0 cargo-generate-rpm@0.21.0 - name: Build release run: cargo build --release - name: Create standalone binary zip run: | cp target/release/filegram filegram-linux-x86_64 zip filegram-linux-x86_64.zip filegram-linux-x86_64 - name: Upload standalone binary artifact uses: actions/upload-artifact@v7 with: name: filegram-linux-x86_64 path: | filegram-linux-x86_64 filegram-linux-x86_64.zip - name: Build .deb package run: | cargo deb --no-build cp target/debian/*.deb filegram-linux-x86_64.deb # RPM forbids '-' in versions; '~' is its pre-release marker # (0.1.0~dev sorts below 0.1.0), so dev builds map -dev to ~dev. - name: Build .rpm package run: | rpm_version=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2 | tr -- '-' '~') cargo generate-rpm --set-metadata "version = \"$rpm_version\"" cp target/generate-rpm/*.rpm filegram-linux-x86_64.rpm - name: Build AppImage env: LINUXDEPLOY_VERSION: 1-alpha-20251107-1 LINUXDEPLOY_SHA256: c20cd71e3a4e3b80c3483cef793cda3f4e990aca14014d23c544ca3ce1270b4d run: | curl -fsSLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/${LINUXDEPLOY_VERSION}/linuxdeploy-x86_64.AppImage" echo "${LINUXDEPLOY_SHA256} linuxdeploy-x86_64.AppImage" | sha256sum -c - chmod +x linuxdeploy-x86_64.AppImage # The full hicolor icon set, same as in the deb/rpm packages. # linuxdeploy places each icon by its detected resolution; the files # must be named after the Icon= entry, hence the size-keyed copies. icon_args=() for size in 16 32 48 64 128 256 512; do mkdir -p "icons/$size" cp "assets/icon/icon-$size.png" "icons/$size/filegram.png" icon_args+=(--icon-file "icons/$size/filegram.png") done cp assets/icon/icon.svg icons/filegram.svg icon_args+=(--icon-file icons/filegram.svg) APPIMAGE_EXTRACT_AND_RUN=1 OUTPUT=filegram-linux-x86_64.AppImage \ ./linuxdeploy-x86_64.AppImage --appdir AppDir \ --executable target/release/filegram \ --desktop-file packaging/linux/filegram.desktop \ "${icon_args[@]}" \ --output appimage - name: Upload artifact uses: actions/upload-artifact@v7 with: name: filegram-linux-packages path: | filegram-linux-x86_64.deb filegram-linux-x86_64.rpm filegram-linux-x86_64.AppImage test: name: Unit tests (Linux) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.sha }} - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 with: key: test-ubuntu-latest # Compiling the crate (and iced) for the tests needs the same X/Wayland # dev libraries the Linux build does. - name: Install Linux dependencies run: | sudo apt-get update sudo apt-get install -y libxkbcommon-dev libwayland-dev pkg-config - name: Run tests run: cargo test --locked smoke-test: name: Headless smoke test (Linux container) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.sha }} - uses: docker/setup-buildx-action@v3 # Build with a persistent layer cache: cargo-chef's dependency layer # (iced/wgpu — most of the build) is restored from the GitHub Actions # cache, so only filegram itself recompiles. See docker/smoke.Dockerfile. - name: Build the smoke image uses: docker/build-push-action@v6 # The build record is a debugging summary in the Actions UI; without # this it is also uploaded as a `*.dockerbuild` artifact, which the # dev-release job's unfiltered download then chokes on. env: DOCKER_BUILD_RECORD_UPLOAD: false with: context: . file: docker/smoke.Dockerfile tags: filegram-smoke:latest load: true cache-from: type=gha,scope=smoke cache-to: type=gha,scope=smoke,mode=max # Runs the binary headless under Xvfb + lavapipe; a non-zero exit (crash, # hang past the entrypoint timeout) fails the job. - name: Run the headless smoke test run: docker run --rm filegram-smoke:latest dev-release: name: Update dev pre-release needs: [build, package-linux] if: github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) runs-on: ubuntu-latest permissions: contents: write actions: read concurrency: group: dev-release cancel-in-progress: true steps: - uses: actions/checkout@v6 - name: Download all artifacts uses: actions/download-artifact@v8 with: # Only the binary artifacts; never any stray build records. pattern: filegram-* path: dist merge-multiple: true # The rolling dev pre-release is tagged with the dev version from # Cargo.toml (e.g. v0.2.1-dev) and replaced on every push to master. # Stale dev releases left behind by a version bump are removed too, # as is the legacy "latest" release from the previous scheme. - name: Recreate dev pre-release with fresh binaries env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | version=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2) case "$version" in *-dev) ;; *) echo "error: version '$version' in Cargo.toml is not a -dev version" >&2; exit 1 ;; esac gh release list --repo "$GITHUB_REPOSITORY" --limit 100 --json tagName --jq '.[].tagName' \ | { grep -e '-dev$' -e '^latest$' || true; } \ | while read -r stale; do gh release delete "$stale" --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes done gh release create "v$version" --repo "$GITHUB_REPOSITORY" \ --title "Filegram $version" \ --notes "Automatic dev build from \`$GITHUB_SHA\`" \ --prerelease \ --target "$GITHUB_SHA" \ dist/*