From 8d7fdfd305170b492fead36aa056c1e8f9b4cc42 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 24 Oct 2026 18:48:25 +0200 Subject: [PATCH] ci: Build Frame Home in CI Build Frame Home payload in CI Build the ARM64 headset payload against the glibc 2.39 baseline, validate its ABI, and pass the generated artifact to Studio packaging. Add provenance attestations and document CI and local build modes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yml | 82 ++++++++++++++++++------ docs/building.md | 106 ++++++++++++++----------------- scripts/build-frame-home.sh | 122 ++++++++++++++++++++++++++++++++++++ scripts/build-studio.sh | 31 +++------ src/openvr_loader.cpp | 4 ++ studio/CMakeLists.txt | 11 +++- studio/build_windows.bat | 4 +- 7 files changed, 257 insertions(+), 103 deletions(-) create mode 100644 scripts/build-frame-home.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4aba03..49b92e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,14 +1,18 @@ -# Builds Frame Studio for every computer it runs on, and the Frame Home installer, on every push (any branch or tag), -# every pull request, and by hand (Actions > build > Run workflow). Each build is a downloadable artifact of the run. -# A push to main also replaces the "latest" pre-release with them (one place to download the newest of everything); -# a tag "v*" publishes them as that version's release. +# Builds Frame Studio for every computer it runs on, plus a same-commit Frame Home payload, on branch pushes, +# pull requests, and by hand (Actions > build > Run workflow). A push to main replaces the Studio "latest" pre-release +# and includes the Frame Home installer. A tag "v*" publishes a versioned release containing both products. name: build on: push: + branches: ['**'] + tags: ['v*'] pull_request: workflow_dispatch: +permissions: + contents: read + # A newer push to the same branch replaces a run still going (its builds would be out of date anyway). concurrency: group: build-${{ github.ref }} @@ -17,10 +21,12 @@ concurrency: # Every Studio build knows the commit it's from (studio/updater.h compares it with the latest release's). env: FRAME_BUILD: ${{ github.sha }} + FRAME_HOME_PAYLOAD_DIR: ${{ github.workspace }}/build/frame-home/payload jobs: linux: name: Linux ${{ matrix.arch }} + needs: [frame-home] strategy: fail-fast: false matrix: @@ -33,6 +39,11 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: FrameHome-payload + path: build/frame-home/payload + if-no-files-found: error - name: Tools and X11 headers run: | # x86 links against 32-bit X11, so apt needs the i386 packages too @@ -48,6 +59,7 @@ jobs: windows: name: Windows ${{ matrix.arch }} + needs: [frame-home] strategy: fail-fast: false matrix: @@ -56,6 +68,10 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: FrameHome-payload + path: build/frame-home/payload - name: Build (MSVC) shell: cmd run: studio\build_windows.bat ${{ matrix.arch }} "%GITHUB_WORKSPACE%\build\windows-${{ matrix.arch }}" @@ -77,6 +93,7 @@ jobs: macos: name: macOS ${{ matrix.arch }} + needs: [frame-home] strategy: fail-fast: false matrix: @@ -89,6 +106,10 @@ jobs: timeout-minutes: 45 steps: - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: FrameHome-payload + path: build/frame-home/payload - name: Tools and MoltenVK (Vulkan on Metal, bundled into the app) run: brew install ninja molten-vk - name: Build @@ -99,19 +120,30 @@ jobs: path: dist/FrameStudio-macos-${{ matrix.arch }}.zip frame-home: - name: Frame Home installer - runs-on: ubuntu-24.04 - timeout-minutes: 30 + name: Build Frame Home payload + runs-on: ubuntu-24.04-arm + timeout-minutes: 45 steps: - - uses: actions/checkout@v4 - - name: Headset programs match the source - run: sh scripts/frame-payload.sh check - - name: Package - run: sh scripts/build-studio.sh frame-home + - uses: actions/checkout@v4.2.2 + - name: Build payload and installer + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build gcc-13 g++-13 glslc zip + sh scripts/build-frame-home.sh --ci + - uses: actions/upload-artifact@v4 + with: + name: FrameHome-payload + path: build/frame-home/payload - uses: actions/upload-artifact@v4 with: name: FrameHome-installer - path: dist/FrameHome-installer.zip + path: build/frame-home/FrameHome-installer.zip + if-no-files-found: error + - uses: actions/upload-artifact@v4 + with: + name: FrameHome-build-info + path: build/frame-home/build-info.txt + if-no-files-found: error release: name: Publish @@ -125,30 +157,40 @@ jobs: timeout-minutes: 20 permissions: contents: write + id-token: write + attestations: write env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} steps: - uses: actions/download-artifact@v4 with: + pattern: FrameStudio-* path: dist merge-multiple: true - - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: - path: src - # Every download carries frame/payload: none is published beside headset programs older than the source - # (Studio would save environments the headset can't open). - - name: Headset programs match the source - run: sh src/scripts/frame-payload.sh check + name: FrameHome-payload + path: ${{ runner.temp }}/frame-home-payload + - uses: actions/download-artifact@v4 + with: + name: FrameHome-installer + path: dist # What Frame Studio (studio/updater.h) and the headset's Environments panel check for updates: this commit, # Frame Home's identity (a hash of the headset programs, the same one the panel works out for what's installed) # and the files in this release. - name: latest.json run: | - home=$(cd src/frame/payload && sha256sum bin/frame-home bin/frame-switcher bin/fenv-tool layer/libframe_home_layer.so layer/env_ps.spv | cut -c1-64 | sha256sum | cut -c1-64) + home=$(cd "$RUNNER_TEMP/frame-home-payload" && sha256sum bin/frame-home bin/frame-switcher bin/fenv-tool layer/libframe_home_layer.so layer/env_ps.spv | cut -c1-64 | sha256sum | cut -c1-64) files=$(ls dist | python3 -c 'import json, sys; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))') printf '{"build": "%s", "home": "%s", "date": "%s", "files": %s}\n' "$GITHUB_SHA" "$home" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$files" > dist/latest.json cat dist/latest.json + - uses: actions/attest-build-provenance@v2 + with: + subject-path: dist/FrameStudio-* + - uses: actions/attest-build-provenance@v2 + with: + subject-path: dist/FrameHome-installer.zip - name: Version release (tag v*) if: ${{ startsWith(github.ref, 'refs/tags/v') && !contains(needs.*.result, 'failure') }} run: gh release create "${{ github.ref_name }}" dist/* --repo "$REPO" --title "Frame Studio ${{ github.ref_name }}" --generate-notes diff --git a/docs/building.md b/docs/building.md index 3798b0a..425e1e8 100644 --- a/docs/building.md +++ b/docs/building.md @@ -3,12 +3,11 @@ You only need this page if you want to change the code or build a version that isn't on the Releases page. Ready builds for every computer are on GitHub (see [getting-started.md](getting-started.md#download)). -There are two parts: +There are two build surfaces: - **Frame Studio** builds on Windows, Linux and macOS (and cross-builds for Windows from Linux). -- **The headset side** (Frame Home, the Environments panel, `fenv-tool` and the compositor layer) builds on the - Steam Frame itself. A ready-built copy is in the repository, in `frame/payload/`, so you only rebuild it when you - change that code. +- **Frame Home** (the renderer, Environments panel, `fenv-tool` and compositor layer) is built by GitHub Actions on + ARM64 Linux, and can also be built locally on the Steam Frame. Everything Studio needs is in `third_party/`. Vulkan is loaded when Studio starts, so you don't need the Vulkan SDK to build it. All builds use CMake 3.16 or later and Ninja. @@ -25,6 +24,9 @@ CMake and Ninja. studio\build_windows.bat x64 ``` + Studio packages the CI-built headset payload. Download the `FrameHome-payload` artifact from an Actions run and set + `FRAME_HOME_PAYLOAD_DIR` to its extracted directory before running the build script. + Use `x86` for 32-bit Windows or `arm64` for Windows on ARM instead of `x64`. 3. The program is `%LOCALAPPDATA%\FrameStudio\build-x64\Frame Studio.exe` (the folder ends in your architecture). The headset files are copied beside it, in `frame\`. @@ -48,12 +50,10 @@ Each target is built and packed into `dist/`. `scripts/build-studio.sh --help` l | `linux-x86` | 64-bit Linux with `gcc-multilib g++-multilib` | `dist/FrameStudio-linux-x86.tar.gz` | | `windows-x64`, `windows-x86`, `windows-arm64` | Linux with [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) | `dist/FrameStudio-windows-.zip` | | `macos-arm64`, `macos-x64` | A Mac with Xcode's command line tools | `dist/FrameStudio-macos-.zip` | -| `frame-home` | Anywhere | `dist/FrameHome-installer.zip` (the headset files and install scripts, no Studio) | | `all` | Anywhere | Every target this computer can build | -`all` builds: on a Mac, both macOS targets and `frame-home`; on 64-bit x86 Linux, `linux-x64`, `linux-x86` and -`frame-home`; on ARM Linux, `linux-arm64` and `frame-home`. On Linux it adds the three Windows targets when it finds -llvm-mingw. +`all` builds the targets supported by this computer. On Linux it adds the three Windows targets when it finds +llvm-mingw. The standalone Frame Home installer is built separately by GitHub Actions. Build folders go in `build/` unless you pass `--build-dir`. @@ -108,18 +108,20 @@ The Apple Silicon build runs on macOS 11 and later, the Intel build on macOS 10. ## Continuous builds (GitHub Actions) -The workflow is `.github/workflows/build.yml`. +The `.github/workflows/build.yml` workflow builds both products and publishes their assets together. There is one +rolling `latest` release and one shared `v*` tag namespace; each release contains the Studio packages and +`FrameHome-installer.zip`. | When | What happens | |---|---| -| Every push, to any branch or tag, and every pull request | All targets build: Linux x64, x86, arm64; Windows x64, x86, arm64 (MSVC); macOS arm64 and x64; the Frame Home installer. Each is kept as an artifact of the run. | -| A push to `main` | The **Latest build** pre-release is replaced with the new builds. Builds that worked are published even if another platform failed. | -| A tag starting with `v` (for example `v1.0`) | A release with that name is published, with all builds. It is only made when every build succeeded. | +| Every push and pull request | Studio builds for Linux, Windows and macOS; Frame Home is built on ARM64 and bundled into Studio packages. Successful outputs are kept as run artifacts. | +| A push to `main` | The **Latest build** pre-release is replaced with Studio builds and the standalone Frame Home installer. | +| A tag starting with `v` (for example `v1.0`) | One version release is published with both Studio packages and the Frame Home installer. | | By hand | **Actions > build > Run workflow** | A newer push to the same branch cancels a run that is still going. -To publish a version: +To publish versions: ```sh git tag v1.0 @@ -132,58 +134,44 @@ To download a build from any run: 2. Click the run (each push is one). 3. Scroll down to **Artifacts** and download the one you need. You must be signed in. -## The headset side +## Frame Home payload -Frame Home (`frame-home`), the Environments panel (`frame-switcher`), `fenv-tool` and the compositor layer are built -on the Steam Frame itself, against SteamVR's own OpenVR library (`/opt/steamvr/bin/linuxarm64`, set by -`OPENVR_LIB_DIR`). You need a C++17 compiler, CMake, Ninja and the Vulkan headers on the headset. +The release payload is built from source by GitHub Actions on ARM64 Linux, currently pinned to glibc 2.39 for compatibility with SteamOS stable/beta. The same artifact is used by Studio packages and the standalone installer. -1. Copy the repository to the headset and log in: `ssh frame`. -2. Build the programs with the top-level `CMakeLists.txt`: - - ```sh - cd ~/frame-env - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - cmake --build build - ``` - - This makes `build/frame-home`, `build/frame-switcher` and `build/fenv-tool`. -3. Build the compositor layer (it is not part of CMake): - - ```sh - mkdir -p build/layer - g++ -O2 -shared -fPIC -std=c++17 -fvisibility=hidden layer/frame_home_layer.cpp -o build/layer/libframe_home_layer.so -lpthread - ``` - -4. Compile the layer's shader `layer/env_ps.frag` to SPIR-V, for example with glslc: - - ```sh - glslc layer/env_ps.frag -o build/layer/env_ps.spv - ``` - -### Refreshing frame/payload - -`frame/payload/` is the ready-built copy that every Studio build and the installer carry. After building on the -headset, copy the results into it: - -| From (on the headset) | To (in the repository) | -|---|---| -| `build/frame-home`, `build/frame-switcher`, `build/fenv-tool` | `frame/payload/bin/` | -| `build/layer/libframe_home_layer.so`, `build/layer/env_ps.spv` | `frame/payload/layer/` | - -`frame/payload/install.sh` is the installer itself; it lives in the repository and needs no build. - -Then record which source they were built from, and commit the new payload together with the code that changed: +To build and test on the Steam Frame, run: ```sh -sh scripts/frame-payload.sh stamp +sh scripts/build-frame-home.sh ``` -`frame/payload/SOURCE` holds a hash of everything the headset side is built from (`src/`, `third_party/`, `layer/`, -`CMakeLists.txt`). `sh scripts/frame-payload.sh check` tells you whether the payload is still current. The release -build runs it too and publishes nothing while the payload is older than the source: a Studio beside an older Frame -Home saves environments the headset can't open. Studio also asks the headset (`fenv-tool version`) before -**Send to Frame** and says when its Frame Home needs updating. +This uses the headset's native OpenVR library and Vulkan SDK, normally at `/opt/steamvr/bin/linuxarm64`, and requires +CMake, Ninja, a C/C++ compiler, `glslc`, `zip`, and Python 3. Set `OPENVR_LIB_DIR` if SteamVR is installed elsewhere. The +payload, installer ZIP, and build manifest are written under `build/frame-home/`. This mode accepts the headset's +installed glibc version, including Preview's 2.43; those local outputs are for development and are not the release +build for stable/beta. + +CI selects release mode explicitly with `sh scripts/build-frame-home.sh --ci` (or `FRAME_HOME_BUILD_MODE=ci`). It +requires ARM64 Linux with glibc 2.39 and GCC 13, then checks the ELF architecture and GLIBC symbol ceiling before +packaging. The build manifest records compiler/tool versions, dynamic dependencies, and +GLIBC/GLIBCXX/CXXABI requirements in `build/frame-home/build-info.txt`; CI retains it as a run artifact. Studio can +bundle a locally built or downloaded payload by setting `FRAME_HOME_PAYLOAD_DIR` to that payload directory. The +installer ZIP is checked against a Frame Home-only file allowlist before it is published. + +Studio checks the headset's `fenv-tool version` before **Send to Frame** and warns when the headset cannot open the +scene format Studio would save. + +### Attestations + +Frame Home installer and Studio release archives carry GitHub build-provenance attestations. With GitHub CLI, verify +an asset from the repository root: + +```sh +gh attestation verify FrameHome-installer.zip --repo Toastconcern/FrameStudio +gh attestation verify FrameStudio-linux-x64.tar.gz --repo Toastconcern/FrameStudio +``` + +The attestation identifies the repository, workflow, source ref and commit that produced the archive. It does not +replace runtime signature verification by the installer or updater. ## Repository layout diff --git a/scripts/build-frame-home.sh b/scripts/build-frame-home.sh new file mode 100644 index 0000000..cf8382c --- /dev/null +++ b/scripts/build-frame-home.sh @@ -0,0 +1,122 @@ +#!/bin/sh +# Build the Steam Frame payload and its standalone installer on an ARM64 glibc 2.39 runner. +set -eu +ROOT=$(cd "$(dirname "$0")/.." && pwd) +OUT=${FRAME_HOME_OUTPUT_DIR:-"$ROOT/build/frame-home"} +PAYLOAD="$OUT/payload" +MODE=${1:-${FRAME_HOME_BUILD_MODE:-local}} +case "$MODE" in + ci|--ci) + MODE=ci + BUILD=${FRAME_HOME_BUILD_DIR:-"$ROOT/build/frame-home-cmake-ci"} + CC=${CC:-gcc-13} + CXX=${CXX:-g++-13} + ;; + local|--local) + MODE=local + BUILD=${FRAME_HOME_BUILD_DIR:-"$ROOT/build/frame-home-cmake-local"} + CC=${CC:-gcc} + CXX=${CXX:-g++} + ;; + *) echo "Usage: $0 [--local|--ci]" >&2; exit 2 ;; +esac +export CC CXX + +[ "$(uname -m)" = aarch64 ] || { echo "Frame Home requires an ARM64 runner" >&2; exit 1; } +if [ "$MODE" = ci ] && [ "$(getconf GNU_LIBC_VERSION)" != "glibc 2.39" ]; then + echo "Release builds require glibc 2.39 (found: $(getconf GNU_LIBC_VERSION))" >&2 + exit 1 +fi +for tool in cmake ninja "$CC" "$CXX" glslc readelf zip python3; do + command -v "$tool" >/dev/null 2>&1 || { echo "Missing build tool: $tool" >&2; exit 1; } +done +if [ "$MODE" = ci ]; then + "$CXX" --version | grep -q '13\.' || { echo "Release builds require GCC 13" >&2; exit 1; } +fi + +if [ "$MODE" = local ]; then + cmake -S "$ROOT" -B "$BUILD" -G Ninja -DCMAKE_BUILD_TYPE=Release -DFRAME_HOME_PC=OFF \ + "-DOPENVR_LIB_DIR=${OPENVR_LIB_DIR:-/opt/steamvr/bin/linuxarm64}" +else + cmake -S "$ROOT" -B "$BUILD" -G Ninja -DCMAKE_BUILD_TYPE=Release -DFRAME_HOME_PC=ON +fi +cmake --build "$BUILD" --target frame-home frame-switcher fenv-tool --parallel + +rm -rf "$PAYLOAD" "$OUT/FrameHome-installer-stage" "$OUT/FrameHome-installer.zip" +mkdir -p "$PAYLOAD/bin" "$PAYLOAD/layer" +cp "$BUILD/frame-home" "$BUILD/frame-switcher" "$BUILD/fenv-tool" "$PAYLOAD/bin/" +"$CXX" -O2 -shared -fPIC -std=c++17 -fvisibility=hidden \ + -I"$ROOT/third_party/vulkan-headers/include" -I"$ROOT/src" \ + "$ROOT/layer/frame_home_layer.cpp" -o "$PAYLOAD/layer/libframe_home_layer.so" -lpthread +glslc "$ROOT/layer/env_ps.frag" -o "$PAYLOAD/layer/env_ps.spv" +cp "$ROOT/frame/payload/install.sh" "$PAYLOAD/install.sh" +chmod +x "$PAYLOAD/install.sh" "$PAYLOAD/bin/"* + +for binary in "$PAYLOAD/bin/"* "$PAYLOAD/layer/libframe_home_layer.so"; do + readelf -h "$binary" | grep -q 'Machine:.*AArch64' || { + echo "Not an AArch64 ELF: $binary" >&2; exit 1; + } + highest=$(readelf --version-info "$binary" | grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' | sort -Vu | tail -n 1 || true) + if [ "$MODE" = ci ] && [ -n "$highest" ] && [ "$(printf '%s\n' "$highest" GLIBC_2.39 | sort -V | tail -n 1)" != GLIBC_2.39 ]; then + echo "$binary requires $highest, newer than the glibc 2.39 baseline" >&2 + exit 1 + fi +done + +STAGE="$OUT/FrameHome-installer-stage/FrameHome-installer" +mkdir -p "$STAGE" +cp -R "$PAYLOAD" "$STAGE/payload" +cp "$ROOT/LICENSE" "$STAGE/LICENSE.txt" +cp "$ROOT/frame/install-frame-home.sh" "$ROOT/frame/install-frame-home.ps1" \ + "$ROOT/frame/Install Frame Home.bat" "$ROOT/frame/README-frame-home.txt" "$STAGE/" +chmod +x "$STAGE/install-frame-home.sh" +(cd "$OUT/FrameHome-installer-stage" && zip -qr "$OUT/FrameHome-installer.zip" FrameHome-installer) +rm -rf "$OUT/FrameHome-installer-stage" +python3 - "$OUT/FrameHome-installer.zip" <<'PY' +import sys +import zipfile + +required = { + "FrameHome-installer/payload/install.sh", + "FrameHome-installer/payload/bin/frame-home", + "FrameHome-installer/payload/bin/frame-switcher", + "FrameHome-installer/payload/bin/fenv-tool", + "FrameHome-installer/payload/layer/libframe_home_layer.so", + "FrameHome-installer/payload/layer/env_ps.spv", +} +allowed_files = { + "FrameHome-installer/LICENSE.txt", + "FrameHome-installer/Install Frame Home.bat", + "FrameHome-installer/install-frame-home.ps1", + "FrameHome-installer/install-frame-home.sh", + "FrameHome-installer/README-frame-home.txt", +} +with zipfile.ZipFile(sys.argv[1]) as archive: + names = {name.rstrip("/") for name in archive.namelist()} + missing = required - names + unexpected = { + name for name in names + if name != "FrameHome-installer" + and name not in allowed_files + and not name.startswith("FrameHome-installer/payload/") + } + if missing or unexpected: + raise SystemExit(f"Invalid Frame Home installer: missing={sorted(missing)}, unexpected={sorted(unexpected)}") +PY +{ + printf 'mode=%s\n' "$MODE" + printf 'architecture=%s\n' "$(uname -m)" + printf 'libc=%s\n' "$(getconf GNU_LIBC_VERSION)" + printf 'c_compiler=%s\n' "$("$CC" --version | head -n 1)" + printf 'cxx_compiler=%s\n' "$("$CXX" --version | head -n 1)" + printf 'cmake=%s\n' "$(cmake --version | head -n 1)" + printf 'ninja=%s\n' "$(ninja --version)" + printf 'glslc=%s\n' "$(glslc --version | head -n 1)" +} > "$OUT/build-info.txt" +for binary in "$PAYLOAD/bin/"* "$PAYLOAD/layer/libframe_home_layer.so"; do + printf '\n[%s]\n' "${binary#"$PAYLOAD/"}" >> "$OUT/build-info.txt" + readelf -d "$binary" | grep -E 'NEEDED|RPATH|RUNPATH' >> "$OUT/build-info.txt" || true + readelf --version-info "$binary" | grep -oE '(GLIBC|GLIBCXX|CXXABI)_[0-9]+(\.[0-9]+)+' | sort -Vu | tr '\n' ' ' >> "$OUT/build-info.txt" || true + printf '\n' >> "$OUT/build-info.txt" +done +echo "Built $OUT/payload, $OUT/FrameHome-installer.zip, and $OUT/build-info.txt" diff --git a/scripts/build-studio.sh b/scripts/build-studio.sh index c30a970..251f491 100755 --- a/scripts/build-studio.sh +++ b/scripts/build-studio.sh @@ -10,14 +10,13 @@ # (on Windows itself: studio\build_windows.bat x64|x86|arm64) # macos-arm64 macos-x64 on a Mac with Xcode's command line tools; MoltenVK is bundled into the # app from $MOLTENVK (a libMoltenVK.dylib) or Homebrew's molten-vk -# frame-home the Steam Frame installer on its own (no Studio needed): the headset -# files from frame/payload and the install scripts # all every target this computer can build -# Output: dist/FrameStudio-.zip (.tar.gz on Linux) and dist/FrameHome-installer.zip. +# Output: dist/FrameStudio-.zip (.tar.gz on Linux). set -eu ROOT=$(cd "$(dirname "$0")/.." && pwd) DIST="$ROOT/dist" BUILD="$ROOT/build" +FRAME_PAYLOAD_DIR=${FRAME_HOME_PAYLOAD_DIR:-"$ROOT/frame/payload"} CMAKE=${CMAKE:-cmake} TARGETS="" while [ $# -gt 0 ]; do @@ -33,10 +32,10 @@ say() { echo "build-studio: $*"; } if [ "$TARGETS" = " all" ]; then case "$(uname -s)" in - Darwin) TARGETS="macos-arm64 macos-x64 frame-home" ;; + Darwin) TARGETS="macos-arm64 macos-x64" ;; *) case "$(uname -m)" in - aarch64|arm64) TARGETS="linux-arm64 frame-home" ;; - *) TARGETS="linux-x64 linux-x86 frame-home" ;; + aarch64|arm64) TARGETS="linux-arm64" ;; + *) TARGETS="linux-x64 linux-x86" ;; esac [ -x "${LLVM_MINGW:-$HOME/.local/opt/llvm-mingw}/bin/x86_64-w64-mingw32-clang" ] && TARGETS="$TARGETS windows-x64 windows-x86 windows-arm64" ;; esac @@ -47,7 +46,8 @@ build() { target=$1; shift dir="$BUILD/$target" say "building $target" - "$CMAKE" -S "$ROOT/studio" -B "$dir" -DCMAKE_BUILD_TYPE=Release "$@" > "$dir.configure.log" 2>&1 || { + "$CMAKE" -S "$ROOT/studio" -B "$dir" -DCMAKE_BUILD_TYPE=Release \ + "-DFRAME_HOME_PAYLOAD_DIR=$FRAME_PAYLOAD_DIR" "$@" > "$dir.configure.log" 2>&1 || { tail -20 "$dir.configure.log"; say "$target: configure failed (full log: $dir.configure.log)"; exit 1; } "$CMAKE" --build "$dir" --target frame-studio --parallel } @@ -73,8 +73,8 @@ stage_pc() { cp "$home/frame-home" "$home/frame-switcher" "$stage/pc/bin/" ${CXX:-g++} -O2 -shared -fPIC -std=c++17 -fvisibility=hidden -I"$ROOT/third_party/vulkan-headers/include" -I"$ROOT/src" \ "$ROOT/layer/frame_home_layer.cpp" -o "$stage/pc/layer/libframe_home_layer.so" -lpthread - cp "$ROOT/frame/payload/layer/env_ps.spv" "$stage/pc/layer/" - cp "$ROOT/frame/payload/install.sh" "$stage/pc/" && chmod +x "$stage/pc/install.sh" "$stage"/pc/bin/* ;; + cp "$FRAME_PAYLOAD_DIR/layer/env_ps.spv" "$stage/pc/layer/" + cp "$FRAME_PAYLOAD_DIR/install.sh" "$stage/pc/" && chmod +x "$stage/pc/install.sh" "$stage"/pc/bin/* ;; esac } @@ -112,18 +112,6 @@ bundle_moltenvk() { fi } -# dist/FrameHome-installer.zip: Frame Home for the Steam Frame, installed without Frame Studio -package_frame_home() { - stage="$BUILD/stage/FrameHome-installer" - rm -rf "$stage" && mkdir -p "$stage" - cp -R "$ROOT/frame/payload" "$stage/payload" - cp "$ROOT/LICENSE" "$stage/LICENSE.txt" - cp "$ROOT/frame/install-frame-home.sh" "$ROOT/frame/install-frame-home.ps1" "$ROOT/frame/Install Frame Home.bat" "$ROOT/frame/README-frame-home.txt" "$stage/" - chmod +x "$stage/install-frame-home.sh" "$stage/payload/install.sh" "$stage"/payload/bin/* 2>/dev/null || true - (cd "$BUILD/stage" && rm -f "$DIST/FrameHome-installer.zip" && zip -qr "$DIST/FrameHome-installer.zip" FrameHome-installer) - say "frame-home -> dist/FrameHome-installer.zip" -} - for t in $TARGETS; do case "$t" in linux-x64) build "$t" -G Ninja && build_pc_home "$t" -G Ninja && package "$t" ;; @@ -135,7 +123,6 @@ for t in $TARGETS; do windows-arm64) build "$t" -G Ninja -DCMAKE_TOOLCHAIN_FILE="$ROOT/cmake/llvm-mingw.cmake" -DWINDOWS_ARCH=aarch64 && package "$t" ;; macos-arm64) build "$t" -G Ninja -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 && package "$t" ;; macos-x64) build "$t" -G Ninja -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 && package "$t" ;; - frame-home) package_frame_home ;; *) say "unknown target: $t (see --help)"; exit 1 ;; esac done diff --git a/src/openvr_loader.cpp b/src/openvr_loader.cpp index e084c65..4fb67eb 100644 --- a/src/openvr_loader.cpp +++ b/src/openvr_loader.cpp @@ -75,7 +75,11 @@ const char* kLibName = "openvr_api.dll"; using Lib = void*; Lib open(const fs::path& p) { return dlopen(p.c_str(), RTLD_NOW | RTLD_LOCAL); } void* symbol(Lib l, const char* name) { return dlsym(l, name); } +#if defined(__aarch64__) +const char* kLibInRuntime = "bin/linuxarm64/libopenvr_api.so"; +#else const char* kLibInRuntime = "bin/linux64/libopenvr_api.so"; +#endif const char* kLibName = "libopenvr_api.so"; #endif diff --git a/studio/CMakeLists.txt b/studio/CMakeLists.txt index 2086eb5..1647320 100644 --- a/studio/CMakeLists.txt +++ b/studio/CMakeLists.txt @@ -18,6 +18,15 @@ add_compile_definitions(FRAME_BUILD="${FRAME_BUILD}") set(ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..") set(TP "${ROOT}/third_party") +set(FRAME_HOME_PAYLOAD_DIR "$ENV{FRAME_HOME_PAYLOAD_DIR}" CACHE PATH "Built Frame Home payload to bundle with Studio") +if(NOT FRAME_HOME_PAYLOAD_DIR) + set(FRAME_HOME_PAYLOAD_DIR "${ROOT}/frame/payload") +endif() +foreach(FRAME_HOME_FILE install.sh bin/frame-home bin/frame-switcher bin/fenv-tool layer/libframe_home_layer.so layer/env_ps.spv) + if(NOT EXISTS "${FRAME_HOME_PAYLOAD_DIR}/${FRAME_HOME_FILE}") + message(FATAL_ERROR "Frame Home payload is incomplete; build or download it and set FRAME_HOME_PAYLOAD_DIR (${FRAME_HOME_FILE} missing)") + endif() +endforeach() if(WIN32) add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS VK_USE_PLATFORM_WIN32_KHR) @@ -158,7 +167,7 @@ if(APPLE) endif() # Frame Home's headset side (arm64 binaries + install.sh) ships beside the exe, in frame\: Frame > Install on Steam Frame. add_custom_command(TARGET frame-studio POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory "${ROOT}/frame/payload" "$/frame") + COMMAND ${CMAKE_COMMAND} -E copy_directory "${FRAME_HOME_PAYLOAD_DIR}" "$/frame") if(MINGW) # MinGW builds (x64, x86 and ARM64): wWinMain entry point, no runtime DLLs to ship target_link_options(frame-studio PRIVATE -municode -static) endif() diff --git a/studio/build_windows.bat b/studio/build_windows.bat index b97b984..4407019 100644 --- a/studio/build_windows.bat +++ b/studio/build_windows.bat @@ -35,6 +35,8 @@ call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" %VCARCH% >nul || ( echo ERROR: where cmake >nul 2>nul || set "PATH=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%PATH%" where ninja >nul 2>nul || set "PATH=%VSPATH%\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja;%PATH%" -cmake -S "%SRC%." -B "%BDIR%" -G Ninja -DCMAKE_BUILD_TYPE=Release %CROSS% || exit /b 1 +set "PAYLOAD_ARG=" +if defined FRAME_HOME_PAYLOAD_DIR set PAYLOAD_ARG="-DFRAME_HOME_PAYLOAD_DIR=%FRAME_HOME_PAYLOAD_DIR%" +cmake -S "%SRC%." -B "%BDIR%" -G Ninja -DCMAKE_BUILD_TYPE=Release %CROSS% %PAYLOAD_ARG% || exit /b 1 cmake --build "%BDIR%" --target frame-studio || exit /b 1 echo Built "%BDIR%\Frame Studio.exe" (%ARCH%)