From d762cb6f6b23cfea727bed12c4895c30fba199ed Mon Sep 17 00:00:00 2001 From: fraa2a Date: Wed, 21 Oct 2026 18:02:20 +0200 Subject: [PATCH 1/4] build: streamline CI and add AUR development package --- .gitattributes | 2 + .github/workflows/aur-git.yml | 98 +++++++++++++++ .github/workflows/ci.yml | 100 ++++++++++++--- .github/workflows/release.yml | 209 ++++++++----------------------- README.md | 15 ++- THIRD_PARTY_NOTICES.md | 18 ++- _meta/docs/TESTING.md | 5 +- _meta/docs/UPDATING.md | 8 +- package.json | 1 + packaging/aur-git/PKGBUILD | 41 ++++++ packaging/aur-git/README.md | 15 +++ packaging/aur-git/legio-launcher | 3 + scripts/README.md | 7 +- scripts/ci_plan.py | 90 +++++++++++++ scripts/test_ci_plan.py | 98 +++++++++++++++ scripts/test_documentation.mjs | 42 +++++++ scripts/test_packaging.py | 35 ++++++ src-tauri/tauri.conf.json | 8 +- 18 files changed, 605 insertions(+), 190 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/aur-git.yml create mode 100644 packaging/aur-git/PKGBUILD create mode 100644 packaging/aur-git/README.md create mode 100644 packaging/aur-git/legio-launcher create mode 100644 scripts/ci_plan.py create mode 100644 scripts/test_ci_plan.py create mode 100644 scripts/test_documentation.mjs create mode 100644 scripts/test_packaging.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..58669a43 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +packaging/aur-git/PKGBUILD text eol=lf +packaging/aur-git/legio-launcher text eol=lf diff --git a/.github/workflows/aur-git.yml b/.github/workflows/aur-git.yml new file mode 100644 index 00000000..4930c86b --- /dev/null +++ b/.github/workflows/aur-git.yml @@ -0,0 +1,98 @@ +name: AUR development package + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - packaging/aur-git/PKGBUILD + - packaging/aur-git/legio-launcher + - .github/workflows/aur-git.yml + pull_request: + branches: [main] + paths: + - packaging/aur-git/PKGBUILD + - packaging/aur-git/legio-launcher + - .github/workflows/aur-git.yml + +permissions: + contents: read + +defaults: + run: + shell: bash + +concurrency: + group: aur-git-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + package: + name: Build Arch source package + runs-on: ubuntu-latest + container: archlinux:base-devel + steps: + - name: Install packaging tools + run: | + printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf + pacman -Syu --noconfirm --needed git openssh sudo + useradd -m builder + printf 'builder ALL=(ALL:ALL) NOPASSWD: ALL\n' > /etc/sudoers.d/builder + + - name: Check out recipe + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Build and generate metadata + run: | + cp -r packaging/aur-git /tmp/aur-package + chown -R builder:builder /tmp/aur-package + bash -n /tmp/aur-package/PKGBUILD + su builder -c 'cd /tmp/aur-package && makepkg --syncdeps --noconfirm' + su builder -c 'cd /tmp/aur-package && makepkg --printsrcinfo > .SRCINFO' + mkdir aur-files + cp /tmp/aur-package/{PKGBUILD,.SRCINFO,legio-launcher} aur-files/ + + - name: Upload validated recipe + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: aur-git-recipe + path: aur-files/ + include-hidden-files: true + retention-days: 7 + if-no-files-found: error + + publish: + name: Publish AUR recipe + needs: package + if: github.event_name == 'workflow_dispatch' || (github.ref == 'refs/heads/main' && github.event_name == 'push') + runs-on: ubuntu-latest + steps: + - name: Download validated recipe + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: aur-git-recipe + path: aur-files + + - name: Create or update AUR package base + env: + AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + AUR_KNOWN_HOSTS: ${{ vars.AUR_KNOWN_HOSTS }} + run: | + test -n "$AUR_SSH_PRIVATE_KEY" || { echo "Missing AUR_SSH_PRIVATE_KEY"; exit 1; } + test -n "$AUR_KNOWN_HOSTS" || { echo "Missing verified AUR_KNOWN_HOSTS"; exit 1; } + install -d -m 700 ~/.ssh + printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur + chmod 600 ~/.ssh/aur + printf '%s\n' "$AUR_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 600 ~/.ssh/known_hosts + export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/aur -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" + git clone ssh://aur@aur.archlinux.org/legio-launcher-git.git aur-repo + cp aur-files/{PKGBUILD,.SRCINFO,legio-launcher} aur-repo/ + cd aur-repo + git config user.name 'Legio release bot' + git config user.email 'actions@users.noreply.github.com' + git add PKGBUILD .SRCINFO legio-launcher + if ! git diff --cached --quiet; then + git commit -m "Update development package recipe" + git push origin HEAD:master + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ac37a68..ca2c81c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,12 @@ name: CI on: - workflow_call: + workflow_dispatch: + inputs: + test_packages: + description: Build downloadable Linux and Windows test packages + type: boolean + default: false pull_request: branches: - main @@ -16,9 +21,51 @@ on: permissions: contents: read +concurrency: + group: ci-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: + plan: + name: Select checks and release + runs-on: ubuntu-latest + outputs: + code: ${{ steps.plan.outputs.code }} + release: ${{ steps.plan.outputs.release }} + version: ${{ steps.plan.outputs.version }} + tag: ${{ steps.plan.outputs.tag }} + prerelease: ${{ steps.plan.outputs.prerelease }} + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + - name: Select checks + id: plan + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + run: python scripts/ci_plan.py + + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 + with: + node-version: 22 + + - name: Check local links and images + run: node --test scripts/test_documentation.mjs + frontend: name: Frontend + needs: plan + if: needs.plan.outputs.code == 'true' runs-on: ubuntu-latest steps: - name: Check out repository @@ -88,6 +135,8 @@ jobs: rust: name: Rust + needs: plan + if: needs.plan.outputs.code == 'true' runs-on: ubuntu-latest steps: - name: Check out repository @@ -128,7 +177,9 @@ jobs: run: cargo test --manifest-path src-tauri/Cargo.toml --all-features --locked tauri-smoke: - name: Tauri smoke (${{ matrix.name }}) + name: Native checks (${{ matrix.name }}) + needs: plan + if: needs.plan.outputs.code == 'true' runs-on: ${{ matrix.os }} env: VCPKGRS_TRIPLET: x64-windows-static-md @@ -321,31 +372,31 @@ jobs: run: cargo test --manifest-path src-tauri/Cargo.toml --locked --lib finalize_install::tests::recovers_after_database_commit_fails_and_database_reopens - name: Build Tauri application - if: github.event_name != 'pull_request' + if: needs.plan.outputs.release != 'true' && !(github.event_name == 'workflow_dispatch' && inputs.test_packages) shell: bash run: corepack pnpm tauri build --debug --no-bundle - name: Build Linux test AppImage - if: runner.os == 'Linux' && github.event_name == 'pull_request' + if: runner.os == 'Linux' && github.event_name == 'workflow_dispatch' && inputs.test_packages shell: bash run: 'corepack pnpm tauri build --bundles appimage --config ''{"bundle":{"createUpdaterArtifacts":false}}''' - name: Record Linux AppImage checksums and revision - if: runner.os == 'Linux' && github.event_name == 'pull_request' + if: runner.os == 'Linux' && github.event_name == 'workflow_dispatch' && inputs.test_packages shell: bash env: - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + SOURCE_SHA: ${{ github.sha }} run: | directory="src-tauri/target/release/bundle/appimage" (cd "$directory" && sha256sum -- *.AppImage) > "$directory/SHA256SUMS.txt" - printf 'Built revision: %s\nPR head: %s\nTest AppImage. Updater signing is disabled; this is not a published release.\n' \ - "$(git rev-parse HEAD)" "$PR_HEAD_SHA" > "$directory/build-info.txt" + printf 'Built revision: %s\nSource revision: %s\nTest AppImage. Updater signing is disabled; this is not a published release.\n' \ + "$(git rev-parse HEAD)" "$SOURCE_SHA" > "$directory/build-info.txt" - name: Upload Linux test AppImage - if: runner.os == 'Linux' && github.event_name == 'pull_request' + if: runner.os == 'Linux' && github.event_name == 'workflow_dispatch' && inputs.test_packages uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: - name: legio-linux-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} + name: legio-linux-test-${{ github.run_id }}-${{ github.sha }} path: | src-tauri/target/release/bundle/appimage/*.AppImage src-tauri/target/release/bundle/appimage/SHA256SUMS.txt @@ -354,17 +405,17 @@ jobs: retention-days: 14 - name: Build Windows test installer - if: runner.os == 'Windows' && github.event_name == 'pull_request' + if: runner.os == 'Windows' && github.event_name == 'workflow_dispatch' && inputs.test_packages shell: bash env: CARGO_PROFILE_RELEASE_DEBUG: "1" run: 'corepack pnpm tauri build --bundles nsis --config ''{"bundle":{"createUpdaterArtifacts":false}}''' - name: Record Windows installer checksums and revision - if: runner.os == 'Windows' && github.event_name == 'pull_request' + if: runner.os == 'Windows' && github.event_name == 'workflow_dispatch' && inputs.test_packages shell: pwsh env: - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + SOURCE_SHA: ${{ github.sha }} run: | $directory = "src-tauri/target/release/bundle/nsis" $installers = @(Get-ChildItem "$directory/*.exe") @@ -375,14 +426,14 @@ jobs: } | Set-Content "$directory/SHA256SUMS.txt" -Encoding utf8 $builtRevision = git rev-parse HEAD if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - @("Built revision: $builtRevision", "PR head: $env:PR_HEAD_SHA", "Test installer. Updater signing is disabled; this is not a published release.") | + @("Built revision: $builtRevision", "Source revision: $env:SOURCE_SHA", "Test installer. Updater signing is disabled; this is not a published release.") | Set-Content "$directory/build-info.txt" -Encoding utf8 - name: Upload Windows test installer - if: runner.os == 'Windows' && github.event_name == 'pull_request' + if: runner.os == 'Windows' && github.event_name == 'workflow_dispatch' && inputs.test_packages uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: - name: legio-windows-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} + name: legio-windows-test-${{ github.run_id }}-${{ github.sha }} path: | src-tauri/target/release/bundle/nsis/*.exe src-tauri/target/release/bundle/nsis/SHA256SUMS.txt @@ -391,10 +442,23 @@ jobs: retention-days: 14 - name: Upload matching Windows native symbols - if: runner.os == 'Windows' && github.event_name == 'pull_request' + if: runner.os == 'Windows' && github.event_name == 'workflow_dispatch' && inputs.test_packages uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: - name: legio-windows-symbols-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} + name: legio-windows-symbols-test-${{ github.run_id }}-${{ github.sha }} path: src-tauri/target/release/legio.pdb if-no-files-found: error retention-days: 14 + + release: + name: Release + needs: [plan, docs, frontend, rust, tauri-smoke] + if: needs.plan.outputs.release == 'true' + permissions: + contents: write + uses: ./.github/workflows/release.yml + with: + version: ${{ needs.plan.outputs.version }} + tag: ${{ needs.plan.outputs.tag }} + prerelease: ${{ needs.plan.outputs.prerelease == 'true' }} + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1706bfdd..87c4d10b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,22 @@ -name: Release +name: Release packages on: - push: - branches: - - main + workflow_call: + inputs: + version: + required: true + type: string + tag: + required: true + type: string + prerelease: + required: true + type: boolean + secrets: + TAURI_SIGNING_PRIVATE_KEY: + required: true + AUR_SSH_PRIVATE_KEY: + required: true permissions: contents: read @@ -13,149 +26,8 @@ concurrency: cancel-in-progress: false jobs: - detect: - name: Detect version change - runs-on: ubuntu-latest - outputs: - release: ${{ steps.version.outputs.release }} - version: ${{ steps.version.outputs.version }} - tag: ${{ steps.version.outputs.tag }} - prerelease: ${{ steps.version.outputs.prerelease }} - steps: - - name: Check out repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Detect Cargo version change - id: version - env: - BEFORE_SHA: ${{ github.event.before }} - shell: bash - run: | - python <<'PY' - import os - import re - import subprocess - import tomllib - - output = os.environ["GITHUB_OUTPUT"] - before = os.environ["BEFORE_SHA"] - - def write(name, value): - with open(output, "a", encoding="utf-8") as handle: - handle.write(f"{name}={value}\n") - - manifest = "src-tauri/Cargo.toml" - if not os.path.isfile(manifest): - write("release", "false") - raise SystemExit(0) - - with open(manifest, "rb") as handle: - current_data = tomllib.load(handle) - - try: - current = current_data["package"]["version"] - except KeyError as exc: - raise SystemExit("src-tauri/Cargo.toml must define package.version") from exc - - try: - previous_raw = subprocess.check_output( - ["git", "show", f"{before}:{manifest}"], - stderr=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError: - write("release", "false") - raise SystemExit(0) - - previous_data = tomllib.loads(previous_raw.decode("utf-8")) - try: - previous = previous_data["package"]["version"] - except KeyError as exc: - raise SystemExit("Previous src-tauri/Cargo.toml does not define package.version") from exc - - if current == previous: - write("release", "false") - raise SystemExit(0) - - semver = re.compile( - r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" - r"(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?" - r"(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$" - ) - - def parse(value): - match = semver.fullmatch(value) - if not match: - raise SystemExit(f"Invalid Semantic Version: {value}") - major, minor, patch, prerelease, _build = match.groups() - identifiers = prerelease.split(".") if prerelease else None - return int(major), int(minor), int(patch), identifiers - - def compare_prerelease(left, right): - if left is None and right is None: - return 0 - if left is None: - return 1 - if right is None: - return -1 - - for a, b in zip(left, right): - if a == b: - continue - a_numeric = a.isdigit() - b_numeric = b.isdigit() - if a_numeric and b_numeric: - return 1 if int(a) > int(b) else -1 - if a_numeric != b_numeric: - return -1 if a_numeric else 1 - return 1 if a > b else -1 - - if len(left) == len(right): - return 0 - return 1 if len(left) > len(right) else -1 - - def compare(left, right): - left_parsed = parse(left) - right_parsed = parse(right) - - if left_parsed[:3] != right_parsed[:3]: - return 1 if left_parsed[:3] > right_parsed[:3] else -1 - - return compare_prerelease(left_parsed[3], right_parsed[3]) - - if compare(current, previous) <= 0: - raise SystemExit( - f"Version must increase: {previous} -> {current}" - ) - - tag = f"v{current}" - tag_check = subprocess.run( - ["git", "rev-parse", "--quiet", "--verify", f"refs/tags/{tag}"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - if tag_check.returncode == 0: - raise SystemExit(f"Tag already exists: {tag}") - - prerelease = parse(current)[3] is not None - - write("release", "true") - write("version", current) - write("tag", tag) - write("prerelease", str(prerelease).lower()) - PY - - quality: - name: Verify release commit - needs: detect - if: needs.detect.outputs.release == 'true' - uses: ./.github/workflows/ci.yml - build: name: Build ${{ matrix.name }} - needs: [detect, quality] - if: needs.detect.outputs.release == 'true' strategy: fail-fast: false matrix: @@ -170,6 +42,7 @@ jobs: runs-on: ${{ matrix.os }} env: VCPKGRS_TRIPLET: x64-windows-static-md + VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache steps: - name: Check out repository @@ -198,6 +71,32 @@ jobs: rustup toolchain install 1.98.1 --profile minimal rustup default 1.98.1 + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + with: + workspaces: src-tauri -> target + key: ${{ matrix.os }} + + - name: Configure Windows archive cache + if: runner.os == 'Windows' + id: vcpkg-cache + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path $env:VCPKG_DEFAULT_BINARY_CACHE | Out-Null + $revision = git -C $env:VCPKG_INSTALLATION_ROOT rev-parse HEAD + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + "revision=$revision" >> $env:GITHUB_OUTPUT + "image=$env:ImageVersion" >> $env:GITHUB_OUTPUT + + - name: Cache Windows archive binaries + if: runner.os == 'Windows' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + key: vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ env.VCPKGRS_TRIPLET }}-libarchive-${{ steps.vcpkg-cache.outputs.revision }}-${{ steps.vcpkg-cache.outputs.image }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ env.VCPKGRS_TRIPLET }}-libarchive-${{ steps.vcpkg-cache.outputs.revision }}- + - name: Install Windows archive library if: runner.os == 'Windows' shell: pwsh @@ -251,9 +150,7 @@ jobs: permissions: contents: write needs: - - detect - build - if: needs.detect.outputs.release == 'true' runs-on: ubuntu-latest steps: @@ -268,9 +165,9 @@ jobs: - name: Publish GitHub release env: GH_TOKEN: ${{ github.token }} - TAG: ${{ needs.detect.outputs.tag }} - VERSION: ${{ needs.detect.outputs.version }} - PRERELEASE: ${{ needs.detect.outputs.prerelease }} + TAG: ${{ inputs.tag }} + VERSION: ${{ inputs.version }} + PRERELEASE: ${{ inputs.prerelease }} TARGET_SHA: ${{ github.sha }} REPOSITORY: ${{ github.repository }} shell: bash @@ -324,10 +221,8 @@ jobs: aur: name: Update AUR package - needs: - - detect - - publish - if: needs.detect.outputs.release == 'true' && needs.detect.outputs.prerelease == 'false' + needs: publish + if: ${{ !inputs.prerelease }} runs-on: ubuntu-latest steps: - name: Check out repository @@ -336,8 +231,8 @@ jobs: - name: Prepare AUR package env: GH_TOKEN: ${{ github.token }} - TAG: ${{ needs.detect.outputs.tag }} - VERSION: ${{ needs.detect.outputs.version }} + TAG: ${{ inputs.tag }} + VERSION: ${{ inputs.version }} shell: bash run: | mkdir aur-files @@ -351,7 +246,7 @@ jobs: env: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} AUR_KNOWN_HOSTS: ${{ vars.AUR_KNOWN_HOSTS }} - VERSION: ${{ needs.detect.outputs.version }} + VERSION: ${{ inputs.version }} shell: bash run: | test -n "$AUR_SSH_PRIVATE_KEY" || { echo "Missing AUR_SSH_PRIVATE_KEY"; exit 1; } diff --git a/README.md b/README.md index 7101ac3e..ec295006 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,13 @@ Legio does not support or promote piracy. Use this option only with a legitimate ## Download -Get the latest version from [Releases](https://github.com/fraa2a/Legio/releases). Packages are available for Windows and Linux. To run Windows games on Linux, install a compatible runner such as Proton or Wine first. +### [Download the latest release](https://github.com/fraa2a/Legio/releases/latest) -Steam must be installed for Steam integration and for games that require it. +- **Windows:** `.exe` installer. +- **Linux:** AppImage, `.deb` or `.rpm`. +- **Arch Linux / AUR:** `legio-launcher-bin` for stable binaries, or `legio-launcher-git` to compile the latest `main` sources. -Linux uses WebKitGTK's Skia compositor on runtime versions 2.54 and newer. -See [Linux rendering diagnostics](_meta/docs/LINUX-RENDERING.md) -for backend comparisons and runtime packaging requirements. - -See [Linux runners and performance controls](_meta/docs/LINUX.md) for umu setup, -Proton/Wine requirements, prefixes, GameMode and gamescope. +Steam is required for Steam integration. For Windows games on Linux, see [runner setup](_meta/docs/LINUX.md). ## Download sources @@ -43,6 +40,8 @@ Add an HTTPS catalog in **Settings > Sources**. You can install multiple sources refresh their cached releases, and remove them individually. No download source is configured by default. +**Hydra sources are also supported.** Some source URLs are protected by Cloudflare and cannot be fetched directly by Legio. Download the actual JSON file in your browser, then use **Choose JSON file** in Sources to select its local path. Use a JSON file, not a saved challenge page. Local refresh rereads that file; it does not download a newer copy. See [Hydra compatibility](_meta/docs/HYDRA.md) for supported downloads and limitations. + After starting Legio once, opening a link such as `legio://add-source?url=https%3A%2F%2Fcatalogo.example%2Fgames.json` opens the launcher and adds the catalog. The browser may ask to open Legio. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 6cc8cccf..228c9029 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,5 +1,21 @@ # Third-party notices +Third-party code and assets retain their own license terms. Legio's SA-NC-PD license does not replace those terms. MIT-licensed adaptations may be included in a differently licensed application, but their copyright and permission notices must be preserved. + +## Inter + +Legio bundles the unmodified Inter variable fonts, including italics. + +- Source: https://github.com/rsms/inter +- Copyright (c) 2016 The Inter Project Authors. +- License: SIL Open Font License 1.1. The full text is in `src/assets/fonts/OFL.txt`, distributed as `licenses/Inter-OFL.txt` in application bundles. + +## Glow icons + +- Source: https://github.com/glow-ui/glow-icons +- Copyright (c) 2025 glow-ui. +- License: MIT. The full notice is in `_meta/licenses/glow-icons.txt`, distributed as `licenses/glow-icons.txt` in application bundles. + ## Hydra Launcher The free-host resolution behavior in `src-tauri/src/download_resolver.rs`, `src-tauri/src/gofile.rs`, `src-tauri/src/gofile_probe.js` and `src-tauri/src/gofile_capture.js` is adapted from the Hydra Launcher hoster implementations at commit `a22ec02201e73db8ca2ee214c2b37cd2d6a27374`. @@ -93,5 +109,5 @@ Source title indexing embeds the upstream FlexSearch distribution from the `0.7. - Source: https://github.com/nextapps-de/flexsearch - Author and Copyright: Thomas Wilkerling, hosted by Nextapps GmbH. -- License: Apache-2.0. The complete license is in `src-tauri/vendor/flexsearch.LICENSE` and is included in application bundles. +- License: Apache-2.0. The complete license is in `src-tauri/vendor/flexsearch.LICENSE`, distributed as `licenses/flexsearch.LICENSE` in application bundles. - The upstream distribution banner says `v0.7.41`; the package metadata at the pinned commit says `0.7.43`. The distribution file is vendored unchanged. diff --git a/_meta/docs/TESTING.md b/_meta/docs/TESTING.md index bf09f6da..bbaf41e3 100644 --- a/_meta/docs/TESTING.md +++ b/_meta/docs/TESTING.md @@ -19,13 +19,14 @@ corepack pnpm tauri build --debug --no-bundle Also run the relevant `test:*` suites in [package.json](../../package.json), for example: ```sh +corepack pnpm test:docs corepack pnpm test:regressions corepack pnpm test:review corepack pnpm test:feedback corepack pnpm test:sources ``` -The [CI workflow](../../.github/workflows/ci.yml) lists required suites. [Scripts](../../scripts/README.md) describes fixtures and test support. JSDOM and mock graphics tests do not validate native rendering. +The [CI workflow](../../.github/workflows/ci.yml) checks documentation on every run. Documentation-only changes skip native builds and application tests; code changes run frontend, Rust and Linux/Windows native checks. New PR pushes cancel superseded runs. Release commits use the production build instead of an additional smoke build. [Scripts](../../scripts/README.md) describes fixtures and test support. JSDOM and mock graphics tests do not validate native rendering. Native tests need platform dependencies, localhost sockets and access to their child processes. Run relevant ignored or interactive tests explicitly; a passing subset does not replace required CI checks. @@ -45,7 +46,7 @@ Record reproduction details and results in the issue or PR, including build, OS, ## PR test packages -CI publishes Linux and Windows packages for pull requests. Check `build-info.txt` against the PR revision and verify `SHA256SUMS.txt`. Windows symbols are a separate artifact. +Run **CI** manually in Actions with **Build downloadable Linux and Windows test packages** enabled. Select the branch to test. Check `build-info.txt` against that revision and verify `SHA256SUMS.txt`. Windows symbols are a separate artifact. **These are unsigned-updater test packages, not releases. Installers use production data; back it up first.** Development runs use a separate profile. diff --git a/_meta/docs/UPDATING.md b/_meta/docs/UPDATING.md index db1b93b1..d24135af 100644 --- a/_meta/docs/UPDATING.md +++ b/_meta/docs/UPDATING.md @@ -1,6 +1,6 @@ # Releases and updates -Increasing the version in `src-tauri/Cargo.toml` on `main` triggers a release. The pipeline builds and signs Ubuntu (`.deb`), Fedora (`.rpm`), Linux (`.AppImage`), and Windows (`.exe`) packages, then publishes their `.sig` files, `SHA256SUMS.txt`, and `latest.json` in the GitHub Release. Preliminary versions are published as prereleases and do not replace the stable `releases/latest` channel. +Increasing the version in `src-tauri/Cargo.toml` on `main` triggers a release after CI passes. CI calls the release workflow once; no second full CI run or redundant smoke build is started. The pipeline builds and signs Ubuntu (`.deb`), Fedora (`.rpm`), Linux (`.AppImage`), and Windows (`.exe`) packages, then publishes their `.sig` files, `SHA256SUMS.txt`, and `latest.json` in the GitHub Release. Preliminary versions are published as prereleases and do not replace the stable `releases/latest` channel. Legio checks for updates at startup and from Settings. The manifest distinguishes the installed package format. Tauri verifies the signature before installation. On Ubuntu and Fedora, package installation may require administrator authorization. On Windows, the NSIS installer is launched. Legio restarts after installation. @@ -8,6 +8,12 @@ The Arch package `legio-launcher-bin` uses the AppImage published on GitHub. The The AUR package declares `umu-launcher` as a runtime dependency, so AUR helpers install it alongside Legio. Arch users need the `multilib` repository enabled. Standalone AppImage, `.deb` and `.rpm` downloads do not bundle umu; install it separately to launch manually imported games through Proton or GE-Proton. +## AUR development package + +`legio-launcher-git` compiles current `main` sources on the user's system. It is separate from the stable binary package and does not need a Nightly release or an AUR commit for each source change. The [AUR workflow](../../.github/workflows/aur-git.yml) builds and publishes recipe changes, generating `.SRCINFO` with `makepkg`. See [package maintenance](../../packaging/aur-git/README.md). + +The two packages conflict and share the installed profile. Back up data before switching from development code to an older stable version. Both must be updated through the package manager. + ## Credentials - `TAURI_SIGNING_PRIVATE_KEY` contains the private Tauri key used to sign installers. The public key is in `src-tauri/tauri.conf.json`. Keep a secure private backup: losing it prevents updates to existing installations. diff --git a/package.json b/package.json index d3cd5ee7..5b96627d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "test:sources": "node scripts/test_source_search.mjs && node scripts/test_gofile_token.mjs && node --conditions=browser --test scripts/test_sources.mjs scripts/test_hydra_sources.mjs scripts/test_download_mirrors.mjs", "test:catalog": "node --conditions=browser --test scripts/test_catalog.mjs scripts/test_store_filters.mjs", "test:acl": "node --test scripts/test_command_acl.mjs", + "test:docs": "node --test scripts/test_documentation.mjs", "test:review": "node --conditions=browser --test scripts/test_review_frontend.mjs scripts/test_updater_review.mjs scripts/test_maintenance_review.mjs", "test:select": "node --conditions=browser --test scripts/test_select.mjs", "test:artwork-cache": "node --conditions=browser --test scripts/test_steam_image_cache.mjs", diff --git a/packaging/aur-git/PKGBUILD b/packaging/aur-git/PKGBUILD new file mode 100644 index 00000000..f2279cb2 --- /dev/null +++ b/packaging/aur-git/PKGBUILD @@ -0,0 +1,41 @@ +pkgname=legio-launcher-git +pkgver=0.2.2.r0.g0000000 +pkgrel=1 +epoch=1 +pkgdesc='Legio desktop game launcher (development sources)' +arch=('x86_64') +url='https://github.com/fraa2a/Legio' +license=('LicenseRef-SA-NC-PD-1.0') +depends=('glibc' 'gcc-libs' 'gtk3' 'webkit2gtk-4.1' 'libayatana-appindicator' 'libarchive' 'librsvg' 'libxdo' 'umu-launcher' 'xdg-utils' 'desktop-file-utils') +makedepends=('git' 'rust>=1.98.1' 'nodejs>=22' 'pnpm' 'python' 'pkgconf') +provides=('legio-launcher') +conflicts=('legio-launcher') +source=('legio::git+https://github.com/fraa2a/Legio.git#branch=main' 'legio-launcher') +sha256sums=('SKIP' 'e4749475518da35cf046c273c7802802d263f3018760c565c262cc838b9361df') + +pkgver() { + cd "$srcdir/legio" + local version + version=$(python -c 'import tomllib; print(tomllib.load(open("src-tauri/Cargo.toml", "rb"))["package"]["version"].replace("-", "."))') + printf '%s.r%s.g%s' "$version" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" +} + +build() { + cd "$srcdir/legio" + export CARGO_TARGET_DIR="$srcdir/target" + pnpm install --frozen-lockfile + pnpm tauri build --no-bundle --config '{"build":{"beforeBuildCommand":"pnpm build"},"bundle":{"createUpdaterArtifacts":false}}' +} + +package() { + cd "$srcdir/legio" + install -Dm755 "$srcdir/target/release/legio" "$pkgdir/usr/lib/legio/legio" + install -Dm755 "$srcdir/legio-launcher" "$pkgdir/usr/bin/legio-launcher" + install -Dm644 packaging/aur/legio-launcher.desktop "$pkgdir/usr/share/applications/legio-launcher.desktop" + install -Dm644 src-tauri/icons/icon.png "$pkgdir/usr/share/pixmaps/legio-launcher.png" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 THIRD_PARTY_NOTICES.md "$pkgdir/usr/share/licenses/$pkgname/THIRD_PARTY_NOTICES.md" + install -Dm644 src/assets/fonts/OFL.txt "$pkgdir/usr/share/licenses/$pkgname/Inter-OFL.txt" + install -Dm644 _meta/licenses/glow-icons.txt "$pkgdir/usr/share/licenses/$pkgname/glow-icons.txt" + install -Dm644 src-tauri/vendor/flexsearch.LICENSE "$pkgdir/usr/share/licenses/$pkgname/flexsearch.LICENSE" +} diff --git a/packaging/aur-git/README.md b/packaging/aur-git/README.md new file mode 100644 index 00000000..37f01686 --- /dev/null +++ b/packaging/aur-git/README.md @@ -0,0 +1,15 @@ +# AUR development package + +`legio-launcher-git` builds the latest `main` sources locally. It conflicts with the stable `legio-launcher-bin`; both use the same application profile. Back up your data before switching to an older version. + +- Git sources use `SKIP` checksums because the branch moves. +- The local launcher wrapper has a fixed checksum. After editing it, run `updpkgsums` from `pacman-contrib`; the packaging test detects stale hashes. +- `pkgver()` reads Cargo's version and appends the Git revision count and commit. +- Licenses are installed in `/usr/share/licenses/legio-launcher-git`, the standard Arch location. `$pkgdir` is makepkg's staging directory, not the user's home. +- The wrapper marks the installation as package-managed, preventing the app updater from replacing pacman-owned files. + +## Publish + +The **AUR development package** workflow builds with `makepkg` on Arch, generates `.SRCINFO`, then pushes the recipe over SSH. The first push creates the AUR package base. It runs when the recipe changes on `main`, or manually through Actions; PR runs build but never publish. + +The workflow uses `AUR_SSH_PRIVATE_KEY` and verified `AUR_KNOWN_HOSTS`. Review the selected ref before manually publishing. Do not update AUR for each application commit: users rebuild the VCS package through their AUR helper's development-package update option. diff --git a/packaging/aur-git/legio-launcher b/packaging/aur-git/legio-launcher new file mode 100644 index 00000000..d36646c4 --- /dev/null +++ b/packaging/aur-git/legio-launcher @@ -0,0 +1,3 @@ +#!/bin/sh +export LEGIO_AUR_PACKAGE=1 +exec /usr/lib/legio/legio "$@" diff --git a/scripts/README.md b/scripts/README.md index ffabd0bf..9ee7ff53 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -7,15 +7,18 @@ Run from the repository root. [package.json](../package.json) defines `test:*` c | Files | Purpose | | --- | --- | | `test_*.mjs` | Regression suites; use package commands for the required Node flags | +| `test_documentation.mjs` | Local Markdown links and image paths (`test:docs`) | | `test_module_loader.mjs` | Svelte/TypeScript loading with mocks; not a standalone suite | | `fixtures/gofile-wt.js` | Protocol fixture shared with Rust via `include_str!` | Reuse the runner and name tests by behavior. Moving files requires updating package commands, fixture references and CI. -## Python release tools +## Python automation +- `ci_plan.py`: selects documentation/code checks and detects release version changes. +- `test_ci_plan.py` and `test_packaging.py`: check CI selection, versions, wrapper checksums and bundled licenses. - `release_manifest.py`: creates and validates updater metadata. - `prepare_aur.py`: generates AUR package files. -- `test_release_manifest.py`: run with `python -B -m unittest discover -s scripts`. +Run all Python tests with `python -B -m unittest discover -s scripts`. These use the standard library. Keep signing credentials private; see [Releases and updates](../_meta/docs/UPDATING.md). diff --git a/scripts/ci_plan.py b/scripts/ci_plan.py new file mode 100644 index 00000000..bfe1f16c --- /dev/null +++ b/scripts/ci_plan.py @@ -0,0 +1,90 @@ +"""Select checks and releases for one GitHub Actions event.""" + +import os +import re +import subprocess +import tomllib +from pathlib import Path + + +VERSION_PATTERN = re.compile( + r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" + r"(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?" + r"(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$" +) +MANIFEST = "src-tauri/Cargo.toml" +DOCUMENTS = { + "README.md", "CONTRIBUTING.md", "SECURITY.md", "AGENTS.md", + "scripts/README.md", "tools/linux-rendering/README.md", +} + + +def parse_version(value): + match = VERSION_PATTERN.fullmatch(value) + if not match: + raise ValueError(f"Invalid Semantic Version: {value}") + major, minor, patch, prerelease, _build = match.groups() + identifiers = prerelease.split(".") if prerelease else None + if identifiers and any(item.isdigit() and len(item) > 1 and item[0] == "0" for item in identifiers): + raise ValueError(f"Invalid numeric prerelease identifier: {value}") + return (int(major), int(minor), int(patch)), identifiers + + +def compare_versions(left, right): + left_core, left_pre = parse_version(left) + right_core, right_pre = parse_version(right) + if left_core != right_core: + return 1 if left_core > right_core else -1 + if left_pre is None or right_pre is None: + return (left_pre is None) - (right_pre is None) + for a, b in zip(left_pre, right_pre): + if a == b: + continue + if a.isdigit() and b.isdigit(): + return 1 if int(a) > int(b) else -1 + if a.isdigit() != b.isdigit(): + return -1 if a.isdigit() else 1 + return 1 if a > b else -1 + return (len(left_pre) > len(right_pre)) - (len(left_pre) < len(right_pre)) + + +def is_documentation(path): + return path in DOCUMENTS or path.endswith("/README.md") or path.startswith(("_meta/docs/", "_meta/screenshots/")) + + +def git(*args): + return subprocess.check_output(["git", *args], stderr=subprocess.DEVNULL).decode("utf-8") + + +def plan(base, event): + current = tomllib.loads(Path(MANIFEST).read_text(encoding="utf-8"))["package"]["version"] + parse_version(current) + try: + previous = tomllib.loads(git("show", f"{base}:{MANIFEST}"))["package"]["version"] + paths = git("diff", "--name-only", "--no-renames", base, "HEAD").splitlines() + except subprocess.CalledProcessError: + previous = None + paths = git("ls-tree", "-r", "--name-only", "HEAD").splitlines() + + changed = previous is not None and current != previous + if changed: + if compare_versions(current, previous) <= 0: + raise ValueError(f"Version must increase: {previous} -> {current}") + if git("tag", "--list", f"v{current}").strip(): + raise ValueError(f"Tag already exists: v{current}") + + release = changed and event == "push" and os.environ.get("GITHUB_REF") == "refs/heads/main" + return { + "code": event == "workflow_dispatch" or any(not is_documentation(path) for path in paths), + "release": release, + "version": current, + "tag": f"v{current}", + "prerelease": parse_version(current)[1] is not None, + } + + +if __name__ == "__main__": + values = plan(os.environ.get("BASE_SHA", ""), os.environ["GITHUB_EVENT_NAME"]) + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + for name, value in values.items(): + output.write(f"{name}={str(value).lower() if isinstance(value, bool) else value}\n") diff --git a/scripts/test_ci_plan.py b/scripts/test_ci_plan.py new file mode 100644 index 00000000..53d33a6d --- /dev/null +++ b/scripts/test_ci_plan.py @@ -0,0 +1,98 @@ +import os +import subprocess +import tempfile +import unittest +from contextlib import chdir +from pathlib import Path +from unittest.mock import patch + +from ci_plan import compare_versions, is_documentation, parse_version, plan + + +class VersionTests(unittest.TestCase): + def test_semver_order(self): + versions = ["1.0.0-alpha.1", "1.0.0-alpha.2", "1.0.0-beta.1", "1.0.0-rc.1", "1.0.0", "1.0.1"] + for earlier, later in zip(versions, versions[1:]): + self.assertLess(compare_versions(earlier, later), 0) + self.assertGreater(compare_versions(later, earlier), 0) + self.assertEqual(compare_versions("1.0.0+abc", "1.0.0+def"), 0) + + def test_invalid_versions(self): + for value in ["01.0.0", "1.2", "1.0.0-alpha.01", "1.0.0-", "1.0.0\n"]: + with self.subTest(value=value), self.assertRaises(ValueError): + parse_version(value) + + def test_only_documentation_paths_are_lightweight(self): + for path in ["README.md", "_meta/docs/LINUX.md", "_meta/screenshots/home.png", "packaging/aur-git/README.md"]: + self.assertTrue(is_documentation(path)) + for path in ["LICENSE", "THIRD_PARTY_NOTICES.md", ".github/workflows/ci.yml", "src/main.ts", "packaging/aur-git/PKGBUILD"]: + self.assertFalse(is_documentation(path)) + + +class PlanTests(unittest.TestCase): + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + self.addCleanup(self.directory.cleanup) + self.context = chdir(self.directory.name) + self.context.__enter__() + self.addCleanup(self.context.__exit__, None, None, None) + self.git("init", "-q") + Path("src-tauri").mkdir() + self.version("1.0.0") + self.commit() + self.base = self.git("rev-parse", "HEAD").strip() + + def git(self, *args): + return subprocess.check_output(["git", "-c", "user.name=Test", "-c", "user.email=test@example.invalid", *args], stderr=subprocess.DEVNULL).decode() + + def version(self, value): + Path("src-tauri/Cargo.toml").write_text(f'[package]\nversion = "{value}"\n', encoding="utf-8") + + def commit(self): + self.git("add", ".") + self.git("commit", "-qm", "test") + + def test_docs_skip_native_builds(self): + Path("README.md").write_text("docs", encoding="utf-8") + self.commit() + result = plan(self.base, "push") + self.assertFalse(result["code"]) + self.assertFalse(result["release"]) + + def test_code_and_manual_events_run_checks_without_release(self): + Path("source.rs").write_text("code", encoding="utf-8") + self.commit() + self.assertTrue(plan(self.base, "pull_request")["code"]) + self.assertTrue(plan(self.base, "workflow_dispatch")["code"]) + self.assertFalse(plan(self.base, "workflow_dispatch")["release"]) + + def test_release_only_for_version_change_on_main_push(self): + self.version("1.1.0-beta.1") + self.commit() + with patch.dict(os.environ, GITHUB_REF="refs/heads/main"): + result = plan(self.base, "push") + self.assertTrue(result["release"]) + self.assertTrue(result["code"]) + self.assertTrue(result["prerelease"]) + self.assertEqual(result["tag"], "v1.1.0-beta.1") + self.assertFalse(plan(self.base, "pull_request")["release"]) + with patch.dict(os.environ, GITHUB_REF="refs/heads/feature"): + self.assertFalse(plan(self.base, "push")["release"]) + + def test_rejects_decrease_and_existing_tag(self): + self.version("0.9.0") + with self.assertRaisesRegex(ValueError, "must increase"): + plan(self.base, "push") + self.version("1.1.0") + self.git("tag", "v1.1.0") + with self.assertRaisesRegex(ValueError, "Tag already exists"): + plan(self.base, "push") + + def test_unknown_base_is_conservative_without_releasing(self): + result = plan("0" * 40, "push") + self.assertTrue(result["code"]) + self.assertFalse(result["release"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_documentation.mjs b/scripts/test_documentation.mjs new file mode 100644 index 00000000..9d7780f6 --- /dev/null +++ b/scripts/test_documentation.mjs @@ -0,0 +1,42 @@ +import assert from "node:assert/strict"; +import { readdir, readFile, access } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "node:test"; + +const root = fileURLToPath(new URL("../", import.meta.url)); + +async function markdownFiles(directory) { + const files = []; + for (const entry of await readdir(join(root, directory), { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) files.push(...await markdownFiles(path)); + else if (entry.name.endsWith(".md")) files.push(path); + } + return files; +} + +test("documentation links and images reference existing local paths", async () => { + const files = [ + "README.md", "CONTRIBUTING.md", "SECURITY.md", "THIRD_PARTY_NOTICES.md", + "scripts/README.md", "tools/linux-rendering/README.md", + ...await markdownFiles("_meta"), + ...await markdownFiles("packaging"), + ]; + const missing = []; + for (const path of files) { + const markdown = await readFile(join(root, path), "utf8"); + for (const match of markdown.matchAll(/\]\(([^\s)]+)(?:\s+"[^"]*")?\)/g)) { + const target = match[1]; + if (/^(?:[a-z][a-z\d+.-]*:|#|\/\/)/i.test(target)) continue; + const local = decodeURIComponent(target.split(/[?#]/)[0]); + if (!local) continue; + try { + await access(resolve(root, dirname(path), local)); + } catch { + missing.push(`${path}: ${target}`); + } + } + } + assert.deepEqual(missing, [], `Missing documentation targets:\n${missing.join("\n")}`); +}); diff --git a/scripts/test_packaging.py b/scripts/test_packaging.py new file mode 100644 index 00000000..b8f8fba4 --- /dev/null +++ b/scripts/test_packaging.py @@ -0,0 +1,35 @@ +import hashlib +import json +import re +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +class PackagingTests(unittest.TestCase): + def test_git_package_verifies_wrapper_but_not_moving_git_source(self): + recipe = ROOT / "packaging/aur-git" + text = (recipe / "PKGBUILD").read_text(encoding="utf-8") + match = re.search(r"sha256sums=\('SKIP' '([a-f0-9]{64})'\)", text) + self.assertIsNotNone(match, "Keep the Git source unpinned and the local wrapper checksummed") + expected = hashlib.sha256((recipe / "legio-launcher").read_bytes()).hexdigest() + self.assertEqual(match[1], expected, "Update the wrapper checksum in PKGBUILD when changing the wrapper") + + def test_bundles_include_required_license_notices(self): + manifest = ROOT / "src-tauri/tauri.conf.json" + resources = json.loads(manifest.read_text(encoding="utf-8"))["bundle"]["resources"] + required = { + "../LICENSE": "LICENSE", + "../THIRD_PARTY_NOTICES.md": "THIRD_PARTY_NOTICES.md", + "../src/assets/fonts/OFL.txt": "licenses/Inter-OFL.txt", + "../_meta/licenses/glow-icons.txt": "licenses/glow-icons.txt", + "vendor/flexsearch.LICENSE": "licenses/flexsearch.LICENSE", + } + for source, destination in required.items(): + self.assertEqual(resources.get(source), destination) + self.assertTrue((manifest.parent / source).is_file(), source) + + +if __name__ == "__main__": + unittest.main() diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 47ab8555..b22e3c34 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -31,7 +31,13 @@ "active": true, "targets": "all", "createUpdaterArtifacts": true, - "resources": ["../src/assets/fonts/OFL.txt", "vendor/flexsearch.LICENSE"], + "resources": { + "../LICENSE": "LICENSE", + "../THIRD_PARTY_NOTICES.md": "THIRD_PARTY_NOTICES.md", + "../src/assets/fonts/OFL.txt": "licenses/Inter-OFL.txt", + "../_meta/licenses/glow-icons.txt": "licenses/glow-icons.txt", + "vendor/flexsearch.LICENSE": "licenses/flexsearch.LICENSE" + }, "icon": [ "icons/icon.png", "icons/icon.ico" From badfbe3f81c0cb070d90a7d17d4d70a1f3879f3a Mon Sep 17 00:00:00 2001 From: fraa2a Date: Wed, 21 Oct 2026 18:03:48 +0200 Subject: [PATCH 2/4] fix: use Arch package name for libxdo dependency --- packaging/aur-git/PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/aur-git/PKGBUILD b/packaging/aur-git/PKGBUILD index f2279cb2..c9edf98e 100644 --- a/packaging/aur-git/PKGBUILD +++ b/packaging/aur-git/PKGBUILD @@ -6,7 +6,7 @@ pkgdesc='Legio desktop game launcher (development sources)' arch=('x86_64') url='https://github.com/fraa2a/Legio' license=('LicenseRef-SA-NC-PD-1.0') -depends=('glibc' 'gcc-libs' 'gtk3' 'webkit2gtk-4.1' 'libayatana-appindicator' 'libarchive' 'librsvg' 'libxdo' 'umu-launcher' 'xdg-utils' 'desktop-file-utils') +depends=('glibc' 'gcc-libs' 'gtk3' 'webkit2gtk-4.1' 'libayatana-appindicator' 'libarchive' 'librsvg' 'xdotool' 'umu-launcher' 'xdg-utils' 'desktop-file-utils') makedepends=('git' 'rust>=1.98.1' 'nodejs>=22' 'pnpm' 'python' 'pkgconf') provides=('legio-launcher') conflicts=('legio-launcher') From ef71da7c9fb49560cfff134687df17017658e48a Mon Sep 17 00:00:00 2001 From: fraa2a Date: Wed, 21 Oct 2026 18:04:58 +0200 Subject: [PATCH 3/4] docs: preserve frontend runtime license notices --- THIRD_PARTY_NOTICES.md | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 228c9029..dffccddb 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -16,6 +16,49 @@ Legio bundles the unmodified Inter variable fonts, including italics. - Copyright (c) 2025 glow-ui. - License: MIT. The full notice is in `_meta/licenses/glow-icons.txt`, distributed as `licenses/glow-icons.txt` in application bundles. +## Svelte + +Frontend runtime: https://github.com/sveltejs/svelte + +MIT License + +Copyright (c) 2016-2025 [Svelte Contributors](https://github.com/sveltejs/svelte/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Tauri API and plugins + +Frontend bindings: `@tauri-apps/api`, `@tauri-apps/plugin-dialog`, `@tauri-apps/plugin-process` and `@tauri-apps/plugin-updater`. + +Source: https://github.com/tauri-apps/tauri and https://github.com/tauri-apps/plugins-workspace + +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors +Copyright (c) 2019-2022, The Tauri Programme in the Commons Conservancy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + ## Hydra Launcher The free-host resolution behavior in `src-tauri/src/download_resolver.rs`, `src-tauri/src/gofile.rs`, `src-tauri/src/gofile_probe.js` and `src-tauri/src/gofile_capture.js` is adapted from the Hydra Launcher hoster implementations at commit `a22ec02201e73db8ca2ee214c2b37cd2d6a27374`. From f06bf474e21d7d9babc748ccf13abece9ae8a759 Mon Sep 17 00:00:00 2001 From: fraa2a Date: Wed, 21 Oct 2026 18:16:34 +0200 Subject: [PATCH 4/4] fix: disable incompatible GCC LTO in Arch builds --- packaging/aur-git/PKGBUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/aur-git/PKGBUILD b/packaging/aur-git/PKGBUILD index c9edf98e..dcdcb625 100644 --- a/packaging/aur-git/PKGBUILD +++ b/packaging/aur-git/PKGBUILD @@ -10,6 +10,8 @@ depends=('glibc' 'gcc-libs' 'gtk3' 'webkit2gtk-4.1' 'libayatana-appindicator' 'l makedepends=('git' 'rust>=1.98.1' 'nodejs>=22' 'pnpm' 'python' 'pkgconf') provides=('legio-launcher') conflicts=('legio-launcher') +# GCC LTO objects from bundled C libraries cannot be linked by Rust's LLD. +options=('!lto') source=('legio::git+https://github.com/fraa2a/Legio.git#branch=main' 'legio-launcher') sha256sums=('SKIP' 'e4749475518da35cf046c273c7802802d263f3018760c565c262cc838b9361df')