diff --git a/.github/Dockerfile b/.github/Dockerfile index bd23fe970..2a03d86d7 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -2,7 +2,10 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive -# System deps for Tauri + mold linker + clang + E2E testing (xvfb, dbus, webkit2gtk-driver) +# System deps for Tauri + mold linker + clang + E2E testing (xvfb, dbus, webkit2gtk-driver). +# CEF (bundled Chromium) runtime libs: libnss3, libnspr4, libgbm1, libxshmfence1, +# libxkbcommon0, libatk-bridge2.0-0 — required to link/run the CEF shared lib that +# cef-dll-sys downloads at build time (CEF is the default runtime now). RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ cmake \ @@ -23,6 +26,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ at-spi2-core \ dbus-x11 \ webkit2gtk-driver \ + libnss3 \ + libnspr4 \ + libgbm1 \ + libxshmfence1 \ + libxkbcommon0 \ + libatk-bridge2.0-0 \ + libxcomposite1 \ + libxdamage1 \ + libxrandr2 \ + libcups2 \ + libpangocairo-1.0-0 \ && rm -rf /var/lib/apt/lists/* # Rust 1.93.0 with minimal profile + fmt/clippy @@ -56,9 +70,22 @@ RUN curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.10.0/scca # tauri-driver (WebDriver server for Tauri E2E tests) RUN cargo install tauri-driver --version 2.0.5 +# Vendored CEF-aware tauri-cli. The upstream @tauri-apps/cli binary does not +# bundle CEF framework files into installers — only the fork at +# app/src-tauri/vendor/tauri-cef (a submodule) does. We compile it here so +# release/build workflows can invoke `cargo tauri build` directly without +# paying the ~5 min compile cost every run. +# +# Submodule must be checked out before `docker build` (see docker-ci-image.yml +# `submodules: recursive`). Source is discarded after install — only the +# `cargo-tauri` binary in $CARGO_HOME/bin is kept. +COPY app/src-tauri/vendor/tauri-cef /opt/tauri-cef +RUN cargo install --locked --path /opt/tauri-cef/crates/tauri-cli \ + && rm -rf /opt/tauri-cef /usr/local/cargo/registry/cache /usr/local/cargo/registry/src + # E2E entrypoint (starts Xvfb + dbus for headless webkit2gtk testing) COPY e2e/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh # Verify installs -RUN rustc --version && cargo --version && node --version && yarn --version && mold --version && sccache --version && which tauri-driver +RUN rustc --version && cargo --version && node --version && yarn --version && mold --version && sccache --version && which tauri-driver && cargo tauri --version diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index cef0db1f8..c09b17aca 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 1 - submodules: true + submodules: recursive - name: Setup Node.js 24.x uses: actions/setup-node@v4 @@ -53,6 +53,28 @@ jobs: restore-keys: | Windows-cargo-registry- + # CEF runtime auto-downloads via cef-dll-sys / vendored tauri-cli. Cache + # it so we don't re-fetch ~400MB every run. + - name: Cache CEF binary distribution + uses: actions/cache@v4 + with: + path: ~/AppData/Local/tauri-cef + key: cef-windows-${{ hashFiles('app/src-tauri/Cargo.toml') }} + restore-keys: | + cef-windows- + + - name: Cache vendored tauri-cli binary + id: tauri-cli-cache + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-tauri.exe + key: vendored-tauri-cli-windows-${{ hashFiles('app/src-tauri/vendor/tauri-cef/crates/tauri-cli/Cargo.toml') }} + + - name: Install vendored tauri-cli (cef-aware bundler) + if: steps.tauri-cli-cache.outputs.cache-hit != 'true' + shell: bash + run: cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli + - name: Install dependencies run: yarn install --frozen-lockfile @@ -126,18 +148,17 @@ jobs: const config = prepareTauriConfig(); core.setOutput('json', JSON.stringify(config)); - - name: Build Tauri app - uses: tauri-apps/tauri-action@v0.6.2 + - name: Build Tauri app (CEF default, vendored CLI) id: tauri-build + shell: bash + working-directory: app env: BASE_URL: ${{ vars.BASE_URL }} VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }} VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }} - with: - projectPath: app - args: -c ${{ steps.config-overrides.outputs.json }} --target x86_64-pc-windows-msvc - includeDebug: false - includeRelease: true + TAURI_CONFIG_OVERRIDE: ${{ steps.config-overrides.outputs.json }} + run: | + cargo tauri build -c "$TAURI_CONFIG_OVERRIDE" --target x86_64-pc-windows-msvc - name: Upload MSI artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ebc7bb5b..98f7fb494 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 1 - submodules: true + submodules: recursive - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 @@ -34,6 +34,22 @@ jobs: app/src-tauri -> target cache-on-failure: true + # CEF (Chromium Embedded Framework) runtime is downloaded on-demand by + # cef-dll-sys + the vendored tauri-cli. Cache it across builds — the + # payload is ~400MB per platform and fetching every run is painful. + - name: Cache CEF binary distribution + uses: actions/cache@v4 + with: + path: ~/.cache/tauri-cef + key: cef-ubuntu-22.04-${{ hashFiles('app/src-tauri/Cargo.toml') }} + restore-keys: | + cef-ubuntu-22.04- + + # Note: the vendored CEF-aware tauri-cli is pre-installed in the + # ghcr.io/tinyhumansai/openhuman_ci image (see .github/Dockerfile), + # so `cargo tauri build` below resolves to the fork without any + # per-run compile step. + - name: Cache node modules id: yarn-cache uses: actions/cache@v4 @@ -59,11 +75,11 @@ jobs: cp target/x86_64-unknown-linux-gnu/ci/openhuman-core app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu chmod +x app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu - - name: Build Tauri app + - name: Build Tauri app (CEF default) working-directory: app run: | TAURI_CONFIG_OVERRIDE='{"plugins":{"updater":{"active":false}}}' - yarn tauri build -c "$TAURI_CONFIG_OVERRIDE" --bundles deb + cargo tauri build -c "$TAURI_CONFIG_OVERRIDE" --bundles deb env: NODE_ENV: production CARGO_PROFILE_RELEASE_OPT_LEVEL: "1" diff --git a/.github/workflows/docker-ci-image.yml b/.github/workflows/docker-ci-image.yml index 27eaeaf64..95d64e987 100644 --- a/.github/workflows/docker-ci-image.yml +++ b/.github/workflows/docker-ci-image.yml @@ -7,6 +7,7 @@ on: - ".github/Dockerfile" - ".github/workflows/docker-ci-image.yml" - "e2e/docker-entrypoint.sh" + - ".gitmodules" workflow_dispatch: permissions: @@ -21,6 +22,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + # vendored tauri-cef fork is compiled into the image + submodules: recursive - name: Log in to GHCR uses: docker/login-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d8c5f151..914f287c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -272,7 +272,7 @@ jobs: with: ref: ${{ needs.prepare-build.outputs.build_ref }} fetch-depth: 1 - submodules: true + submodules: recursive - name: Configure staging app environment if: inputs.build_target == 'staging' @@ -323,6 +323,55 @@ jobs: restore-keys: | ${{ runner.os }}-cargo-registry- + # CEF is now the default runtime — cef-dll-sys + vendored tauri-cli + # auto-download the ~400MB Chromium distribution on first build. Cache + # it per-OS across runs. Default path is `dirs::cache_dir()/tauri-cef` + # (macOS: ~/Library/Caches; Linux: ~/.cache; Windows: %LOCALAPPDATA%). + - name: Cache CEF binary distribution (unix) + if: matrix.settings.platform != 'windows-latest' + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/tauri-cef + ~/.cache/tauri-cef + key: cef-${{ matrix.settings.target }}-${{ hashFiles('app/src-tauri/Cargo.toml') }} + restore-keys: | + cef-${{ matrix.settings.target }}- + + - name: Cache CEF binary distribution (windows) + if: matrix.settings.platform == 'windows-latest' + uses: actions/cache@v4 + with: + path: ~/AppData/Local/tauri-cef + key: cef-${{ matrix.settings.target }}-${{ hashFiles('app/src-tauri/Cargo.toml') }} + restore-keys: | + cef-${{ matrix.settings.target }}- + + # Upstream @tauri-apps/cli (used by tauri-action) doesn't bundle CEF + # framework files into the produced installer. Only the fork at + # vendor/tauri-cef does. Build and install that CLI so `cargo tauri + # build` invokes the cef-aware bundler. + - name: Cache vendored tauri-cli binary (unix) + if: matrix.settings.platform != 'windows-latest' + id: tauri-cli-cache-unix + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-tauri + key: vendored-tauri-cli-${{ runner.os }}-${{ hashFiles('app/src-tauri/vendor/tauri-cef/crates/tauri-cli/Cargo.toml') }} + + - name: Cache vendored tauri-cli binary (windows) + if: matrix.settings.platform == 'windows-latest' + id: tauri-cli-cache-windows + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-tauri.exe + key: vendored-tauri-cli-${{ runner.os }}-${{ hashFiles('app/src-tauri/vendor/tauri-cef/crates/tauri-cli/Cargo.toml') }} + + - name: Install vendored tauri-cli (cef-aware bundler) + if: (matrix.settings.platform == 'windows-latest' && steps.tauri-cli-cache-windows.outputs.cache-hit != 'true') || (matrix.settings.platform != 'windows-latest' && steps.tauri-cli-cache-unix.outputs.cache-hit != 'true') + shell: bash + run: cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli + - name: Install dependencies run: yarn install --frozen-lockfile @@ -455,16 +504,16 @@ jobs: CORE_TARGET_DIR: ${{ steps.core-paths.outputs.core_target_dir }} CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} - - name: Build, package and upload to release - if: needs.prepare-build.outputs.release_enabled == 'true' - uses: tauri-apps/tauri-action@v0.6.2 + # CEF is the default runtime — we build via the vendored tauri-cli so + # the bundler copies Chromium Embedded Framework files into the .app / + # .deb / .AppImage / .msi / NSIS setup. macOS signing is intentionally + # skipped here and handled by the dedicated re-sign + notarize step + # below (hardened runtime + entitlements are required for notarization). + - name: Build and package Tauri app (CEF, vendored CLI) id: tauri-build + shell: bash + working-directory: app env: - # NOTE: APPLE_CERTIFICATE, APPLE_SIGNING_IDENTITY, APPLE_ID, etc. are - # intentionally omitted so tauri-action builds WITHOUT signing. - # Signing + notarization are handled in a dedicated step afterwards - # where we sign everything with hardened runtime + entitlements - # (required by Apple notarization). BASE_URL: ${{ needs.prepare-build.outputs.base_url }} OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} VITE_OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} @@ -473,36 +522,51 @@ jobs: TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }} WITH_UPDATER: "true" - # Must match standalone "Build frontend" so tauri-action's beforeBuildCommand embeds the gate. VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }} VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }} - with: - projectPath: app - args: -c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }} - includeDebug: false - includeRelease: true - releaseId: ${{ needs.create-release.outputs.release_id }} - owner: tinyhumansai - repo: openhuman + TAURI_CONFIG_OVERRIDE: ${{ steps.config-overrides.outputs.json }} + MATRIX_ARGS: ${{ matrix.settings.args }} + run: | + cargo tauri build -c "$TAURI_CONFIG_OVERRIDE" $MATRIX_ARGS - - name: Build and package staging artifacts - if: needs.prepare-build.outputs.release_enabled != 'true' + # tauri-action previously uploaded non-macOS installer assets directly + # to the release. Replicate that for Linux + Windows here (macOS goes + # through the re-sign + notarize path below and uploads separately). + - name: Upload non-macOS installers to release + if: needs.prepare-build.outputs.release_enabled == 'true' && matrix.settings.platform != 'macos-latest' shell: bash env: - BASE_URL: ${{ needs.prepare-build.outputs.base_url }} - OPENHUMAN_APP_ENV: staging - VITE_OPENHUMAN_APP_ENV: staging - VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }} - MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }} - WITH_UPDATER: "true" - VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }} - VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.prepare-build.outputs.tag }} + MATRIX_TARGET: ${{ matrix.settings.target }} run: | - yarn --cwd app tauri build \ - -c '${{ steps.config-overrides.outputs.json }}' \ - ${{ matrix.settings.args }} + set -euo pipefail + shopt -s nullglob + BUNDLE_ROOTS=( + "app/src-tauri/target/${MATRIX_TARGET}/release/bundle" + "target/${MATRIX_TARGET}/release/bundle" + ) + UPLOAD=() + for root in "${BUNDLE_ROOTS[@]}"; do + [ -d "$root" ] || continue + for f in \ + "$root"/deb/*.deb \ + "$root"/appimage/*.AppImage \ + "$root"/appimage/*.AppImage.sig \ + "$root"/msi/*.msi \ + "$root"/msi/*.msi.sig \ + "$root"/nsis/*-setup.exe \ + "$root"/nsis/*-setup.exe.sig; do + [ -e "$f" ] && UPLOAD+=("$f") + done + done + if [ ${#UPLOAD[@]} -eq 0 ]; then + echo "No installer artifacts found for ${MATRIX_TARGET}" + exit 1 + fi + echo "Uploading:" + printf ' %s\n' "${UPLOAD[@]}" + gh release upload "$TAG" "${UPLOAD[@]}" --repo tinyhumansai/openhuman --clobber - name: Locate macOS .app bundle if: matrix.settings.platform == 'macos-latest' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8527406a1..8bb758ab9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,9 +76,9 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 1 - # Required for app/src-tauri/vendor/tauri-cef, which the - # [patch.crates-io] block in app/src-tauri/Cargo.toml resolves - # every tauri-* crate through (even without --features cef). + # Required for app/src-tauri/vendor/tauri-cef — the fork supplies + # tauri-runtime-cef (compiled into the default build) and the + # cef-aware tauri-cli + tauri-bundler used by release workflows. submodules: recursive - name: Cache Rust build artifacts @@ -89,6 +89,17 @@ jobs: app/src-tauri -> target cache-on-failure: true + # CEF is the default runtime, so `cargo test --manifest-path app/src-tauri/Cargo.toml` + # links the Chromium dylib via cef-dll-sys. Cache the download to avoid + # re-fetching ~400MB every PR run. + - name: Cache CEF binary distribution + uses: actions/cache@v4 + with: + path: ~/.cache/tauri-cef + key: cef-ubuntu-22.04-${{ hashFiles('app/src-tauri/Cargo.toml') }} + restore-keys: | + cef-ubuntu-22.04- + - name: Install system dependencies (cmake, ALSA, X11) run: apt-get update && apt-get install -y --no-install-recommends cmake libasound2-dev libxdo-dev libxtst-dev libx11-dev libevdev-dev && rm -rf /var/lib/apt/lists/* diff --git a/app/package.json b/app/package.json index 10fad79df..849682011 100644 --- a/app/package.json +++ b/app/package.json @@ -5,20 +5,21 @@ "scripts": { "dev": "vite", "dev:web": "vite", - "dev:app": "yarn core:stage && source ../scripts/load-dotenv.sh && tauri dev", - "dev:cef": "yarn core:stage && bash ../scripts/setup-chromium-safe-storage.sh && source ../scripts/load-dotenv.sh && CEF_PATH=$HOME/.local/share/cef/cef_macos_aarch64 APPLE_SIGNING_IDENTITY='OpenHuman Dev Signer' cargo tauri dev --features cef -- --no-default-features", + "dev:app": "yarn core:stage && bash ../scripts/setup-chromium-safe-storage.sh && source ../scripts/load-dotenv.sh && APPLE_SIGNING_IDENTITY='OpenHuman Dev Signer' cargo tauri dev", + "dev:cef": "yarn dev:app", + "dev:wry": "yarn core:stage && source ../scripts/load-dotenv.sh && cargo tauri dev --no-default-features --features wry", "core:stage": "node ../scripts/stage-core-sidecar.mjs", "build": "tsc && vite build", "build:app": "tsc && vite build", "compile": "tsc --noEmit", "preview": "vite preview", "tauri": "tauri", - "tauri:build:ui": "tauri build -- --bin OpenHuman", - "macos:build:intel": "source ../scripts/load-dotenv.sh && tauri build --bundles app dmg --target x86_64-apple-darwin -- --bin OpenHuman", - "macos:build:intel:debug": "source ../scripts/load-dotenv.sh && tauri build --debug --bundles app dmg --target x86_64-apple-darwin -- --bin OpenHuman", - "macos:build:debug": "source ../scripts/load-dotenv.sh && tauri build --debug --bundles app dmg -- --bin OpenHuman", - "macos:build:release": "source ../scripts/load-dotenv.sh && tauri build --bundles app dmg -- --bin OpenHuman", - "macos:build:release:signed": "source ../scripts/load-env.sh && tauri build --bundles app dmg -- --bin OpenHuman", + "tauri:build:ui": "cargo tauri build -- --bin OpenHuman", + "macos:build:intel": "source ../scripts/load-dotenv.sh && cargo tauri build --bundles app dmg --target x86_64-apple-darwin -- --bin OpenHuman", + "macos:build:intel:debug": "source ../scripts/load-dotenv.sh && cargo tauri build --debug --bundles app dmg --target x86_64-apple-darwin -- --bin OpenHuman", + "macos:build:debug": "source ../scripts/load-dotenv.sh && cargo tauri build --debug --bundles app dmg -- --bin OpenHuman", + "macos:build:release": "source ../scripts/load-dotenv.sh && cargo tauri build --bundles app dmg -- --bin OpenHuman", + "macos:build:release:signed": "source ../scripts/load-env.sh && cargo tauri build --bundles app dmg -- --bin OpenHuman", "macos:build:sign:release": "yarn macos:build:release:signed", "macos:run": "open '../target/debug/bundle/macos/OpenHuman.app'", "macos:dev": "yarn macos:build:debug && open '../target/debug/bundle/macos/OpenHuman.app'", diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index ab21e7ae0..974f2844f 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -27,13 +27,14 @@ serde_json = "1" [dependencies] # Tauri core and plugins. # -# Default build uses the stock `wry` backend (WKWebView on macOS, WebView2 on -# Windows, WebKitGTK on Linux). Passing `--features cef` swaps the runtime to -# CEF (Chromium Embedded Framework) via `tauri-runtime-cef` — the `[patch. -# crates-io]` block at the bottom of this file pins every tauri crate and -# plugin to the `feat/cef` branch on github so CEF symbols are even in scope. -# Keeping `cef` optional lets contributors without the CEF binaries still run -# `yarn tauri dev` on the wry backend. +# Default build uses the CEF (Chromium Embedded Framework) backend via +# `tauri-runtime-cef` — CI builds, release installers, and local `cargo tauri +# dev` all run against CEF. The `[patch.crates-io]` block at the bottom of this +# file pins every tauri crate and plugin to the `feat/cef` branch on github so +# CEF symbols are in scope, and `cef-dll-sys`'s build script auto-downloads the +# Chromium runtime for the current target on first build. Contributors who +# need the stock `wry` backend (WKWebView on macOS, WebView2 on Windows, +# WebKitGTK on Linux) can opt out with `--no-default-features --features wry`. tauri = { version = "2.10", default-features = false, features = [ "common-controls-v6", "macos-private-api", @@ -76,12 +77,12 @@ objc2 = "0.6" objc2-app-kit = "0.3.2" [features] -# Default runtime is wry (OS-native webview). The `cef` feature swaps in CEF -# (bundled Chromium). The two are mutually exclusive — the `feat/cef` branch -# re-exports `webview_version` from both runtime crates without cfg gates, so -# enabling both at once fails to compile. Users opt into CEF with -# `--no-default-features --features cef`. -default = ["wry"] +# Default runtime is CEF (bundled Chromium). The `wry` feature swaps in the +# OS-native webview backend. The two are mutually exclusive — the `feat/cef` +# branch re-exports `webview_version` from both runtime crates without cfg +# gates, so enabling both at once fails to compile. Users opt out of CEF with +# `--no-default-features --features wry`. +default = ["cef"] wry = ["tauri/wry"] cef = ["tauri/cef", "tauri/devtools", "dep:tauri-runtime-cef", "dep:cef"] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!