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%)