Merge pull request #1 from brugr/build-fixes

Frame Home is built in CI from the same commit as Studio (glibc 2.39, so it runs on
SteamOS stable and beta, not just Preview), binaries leave the repository, curl
installer, build caching and provenance attestations. With fixes on top: the release
step's line continuation, Linux packages keep Frame Studio executable, libdeflate
linked statically, a dependency check on the headset programs, and Studio builds
without a payload outside CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ToastconcernandClaude Opus 5.5 committed 2026-10-24 16:42:22 -05:00
commit fdf765f90a
23 files changed
+528 -177

No files matched your search

+196 -37
View File
@@ -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:
@@ -31,23 +37,54 @@ jobs:
- { arch: arm64, runner: ubuntu-24.04-arm, extra: "" }
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
env:
FRAME_USE_CCACHE: "1"
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 500M
CCACHE_COMPILERCHECK: content
APT_CACHE_DIR: ${{ github.workspace }}/.apt-cache
steps:
- uses: actions/checkout@v4
- name: Check out source
uses: actions/checkout@v4
- name: Restore compiler cache
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/.ccache
key: ccache-studio-linux-${{ matrix.arch }}-${{ github.sha }}
restore-keys: ccache-studio-linux-${{ matrix.arch }}-
- name: Restore apt package cache
uses: actions/cache@v5
with:
path: .apt-cache/archives
key: apt-ubuntu-24.04-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build.yml') }}
restore-keys: apt-ubuntu-24.04-${{ matrix.arch }}-
- name: Download Frame Home payload
uses: actions/download-artifact@v4
with:
name: FrameHome-payload
path: build/frame-home/payload
- name: Tools and X11 headers
run: |
# x86 links against 32-bit X11, so apt needs the i386 packages too
if [ "${{ matrix.i386 }}" = "true" ]; then sudo dpkg --add-architecture i386; fi
sudo mkdir -p "$APT_CACHE_DIR/archives/partial"
sudo chown _apt:root "$APT_CACHE_DIR/archives/partial"
sudo apt-get update
sudo apt-get install -y ninja-build cmake libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxkbcommon-dev ${{ matrix.extra }}
sudo apt-get -o Dir::Cache::archives="$APT_CACHE_DIR/archives" install -y ccache ninja-build cmake libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxkbcommon-dev ${{ matrix.extra }}
sudo chown -R "$(id -u):$(id -g)" "$APT_CACHE_DIR/archives"
- name: Build
run: sh scripts/build-studio.sh linux-${{ matrix.arch }}
- uses: actions/upload-artifact@v4
- name: Show compiler cache statistics
run: ccache --show-stats
- name: Upload Linux build
uses: actions/upload-artifact@v4
with:
name: FrameStudio-linux-${{ matrix.arch }}
path: dist/FrameStudio-linux-${{ matrix.arch }}.tar.gz
path: build/stage/FrameStudio-linux-${{ matrix.arch }}
windows:
name: Windows ${{ matrix.arch }}
needs: [frame-home]
strategy:
fail-fast: false
matrix:
@@ -55,7 +92,13 @@ jobs:
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Check out source
uses: actions/checkout@v4
- name: Download Frame Home payload
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 }}"
@@ -68,15 +111,15 @@ jobs:
Copy-Item "build/windows-${{ matrix.arch }}/Frame Studio.exe" $stage
Copy-Item -Recurse "build/windows-${{ matrix.arch }}/frame" "$stage/frame"
Copy-Item LICENSE "$stage/LICENSE.txt"
New-Item -ItemType Directory -Force dist | Out-Null
Compress-Archive -Path $stage -DestinationPath "dist/$name.zip" -Force
- uses: actions/upload-artifact@v4
- name: Upload Windows build
uses: actions/upload-artifact@v4
with:
name: FrameStudio-windows-${{ matrix.arch }}
path: dist/FrameStudio-windows-${{ matrix.arch }}.zip
path: build/stage/FrameStudio-windows-${{ matrix.arch }}
macos:
name: macOS ${{ matrix.arch }}
needs: [frame-home]
strategy:
fail-fast: false
matrix:
@@ -87,32 +130,90 @@ jobs:
- { arch: x64, runner: macos-15-intel }
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
env:
FRAME_USE_CCACHE: "1"
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 500M
CCACHE_COMPILERCHECK: content
steps:
- uses: actions/checkout@v4
- name: Check out source
uses: actions/checkout@v4
- name: Restore compiler cache
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/.ccache
key: ccache-studio-macos-${{ matrix.arch }}-${{ github.sha }}
restore-keys: ccache-studio-macos-${{ matrix.arch }}-
- name: Restore Homebrew package cache
uses: actions/cache@v5
with:
path: ~/Library/Caches/Homebrew/downloads
key: brew-macos-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build.yml') }}
restore-keys: brew-macos-${{ matrix.arch }}-
- name: Download Frame Home payload
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
run: brew install ccache ninja molten-vk
- name: Build
run: sh scripts/build-studio.sh macos-${{ matrix.arch }}
- uses: actions/upload-artifact@v4
- name: Show compiler cache statistics
run: ccache --show-stats
- name: Upload macOS build
uses: actions/upload-artifact@v4
with:
name: FrameStudio-macos-${{ matrix.arch }}
path: dist/FrameStudio-macos-${{ matrix.arch }}.zip
path: build/stage/FrameStudio-macos-${{ matrix.arch }}
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
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 500M
CCACHE_COMPILERCHECK: content
APT_CACHE_DIR: ${{ github.workspace }}/.apt-cache
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/upload-artifact@v4
- name: Check out source
uses: actions/checkout@v4
- name: Restore compiler cache
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/.ccache
key: ccache-frame-home-arm64-${{ github.sha }}
restore-keys: ccache-frame-home-arm64-
- name: Restore apt package cache
uses: actions/cache@v5
with:
path: .apt-cache/archives
key: apt-ubuntu-24.04-arm64-${{ hashFiles('.github/workflows/build.yml') }}
restore-keys: apt-ubuntu-24.04-arm64-
- name: Install Frame Home build dependencies
run: |
set -euo pipefail
sudo mkdir -p "$APT_CACHE_DIR/archives/partial"
sudo chown _apt:root "$APT_CACHE_DIR/archives/partial"
sudo apt-get update
sudo apt-get -o Dir::Cache::archives="$APT_CACHE_DIR/archives" install -y ccache cmake ninja-build gcc-13 g++-13 glslc zip libdeflate-dev
sudo chown -R "$(id -u):$(id -g)" "$APT_CACHE_DIR/archives"
- name: Build payload and installer
run: sh scripts/build-frame-home.sh --ci
- name: Show compiler cache statistics
run: ccache --show-stats
- name: Upload Frame Home payload
uses: actions/upload-artifact@v4
with:
name: FrameHome-payload
path: build/frame-home/payload
- name: Upload Frame Home installer
uses: actions/upload-artifact@v4
with:
name: FrameHome-installer
path: dist/FrameHome-installer.zip
path: build/frame-home/FrameHome-installer-stage/FrameHome-installer
if-no-files-found: error
release:
name: Publish
# "latest" gets every build that finished (a failed or still-slow platform just isn't in it); a version release
@@ -125,30 +226,88 @@ 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
- name: Download Studio build artifacts
uses: actions/download-artifact@v4
with:
pattern: FrameStudio-*
path: dist
merge-multiple: true
- uses: actions/checkout@v4
merge-multiple: false
- name: Download Frame Home payload
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
- name: Download Frame Home installer
uses: actions/download-artifact@v4
with:
name: FrameHome-installer
path: dist/FrameHome-installer
- name: Rebuild release archives
run: |
for package in dist/FrameStudio-linux-*; do
[ -d "$package" ] || continue
name=${package##*/}
# (artifacts don't keep file modes: the program itself has to be executable again)
chmod +x "$package/Frame Studio"
if [ -d "$package/frame/bin" ]; then
chmod +x "$package/frame/install.sh" "$package/frame/bin/"*
fi
if [ -d "$package/pc/bin" ]; then
chmod +x "$package/pc/install.sh" "$package/pc/bin/"*
fi
tar -czf "dist/$name.tar.gz" -C dist "$name"
rm -rf "$package"
done
for package in dist/FrameStudio-windows-* dist/FrameStudio-macos-*; do
[ -d "$package" ] || continue
name=${package##*/}
if [ -d "$package/frame/bin" ]; then
chmod +x "$package/frame/install.sh" "$package/frame/bin/"*
fi
if [[ "$name" == FrameStudio-macos-* ]]; then
chmod +x "$package/Frame Studio.app/Contents/MacOS/Frame Studio"
fi
(cd dist && zip -qr "$name.zip" "$name")
rm -rf "$package"
done
chmod +x dist/FrameHome-installer/install-frame-home.sh \
dist/FrameHome-installer/payload/install.sh \
dist/FrameHome-installer/payload/bin/*
(cd dist && zip -qr FrameHome-installer.zip FrameHome-installer)
rm -rf dist/FrameHome-installer
python3 - <<'PY'
from pathlib import Path
from zipfile import ZipFile
for path in Path("dist").glob("*.zip"):
with ZipFile(path) as archive:
nested = [name for name in archive.namelist() if name.lower().endswith(".zip")]
if nested:
raise SystemExit(f"{path} contains nested ZIP files: {nested}")
PY
# 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
- name: Attest Studio release archives
uses: actions/attest-build-provenance@v2
with:
subject-path: dist/FrameStudio-*
- name: Attest Frame Home installer
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
+4
View File
@@ -1,6 +1,10 @@
# build output
/build*/
/dist/
/frame/payload/bin/
/frame/payload/layer/libframe_home_layer.so
/frame/payload/layer/env_ps.spv
/frame/payload/SOURCE
studio/build*/
tools/*/build*/
# editor / OS clutter
+3 -2
View File
@@ -94,8 +94,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set_source_files_properties("${TP}/qhe/cook/hzanim_acl.cpp" PROPERTIES COMPILE_OPTIONS "-flax-vector-conversions")
endif()
target_link_libraries(frame-scene PUBLIC miniz lzma smolv ${ASTCENC_LIB} pthread)
# libdeflate when the system has it (the Frame): APK scene.zips inflate several times faster than with miniz.
find_library(LIBDEFLATE_LIB deflate)
# libdeflate when the system has it (the Frame, CI): APK scene.zips inflate several times faster than with miniz. Its
# static library first, so a release build doesn't need it installed on the headset.
find_library(LIBDEFLATE_LIB NAMES libdeflate.a deflate)
find_path(LIBDEFLATE_INC libdeflate.h)
if(LIBDEFLATE_LIB AND LIBDEFLATE_INC)
target_compile_definitions(frame-scene PUBLIC QHE_HAVE_LIBDEFLATE)
+9 -8
View File
@@ -18,10 +18,9 @@ headset runs, and lets you edit, save and send them to the headset.
## Downloads
Every push builds everything. The newest build of `main` is always on the Releases page as **Latest build**; tagged
versions (`v1.0`, ...) get their own release. Builds of other branches are under Actions (open a run, then
Artifacts). Once installed, Frame Studio and Frame Home both update themselves from **Latest build**: Studio from its
top bar, Frame Home from the Environments panel on the headset.
Every push builds Frame Studio and its matching Frame Home payload. The **Latest build** release contains both Studio
packages and `FrameHome-installer.zip`; each `v*` version release contains both products too. Studio updates from its
top bar, and Frame Home updates from the Environments panel on the headset. Both use the same release channel.
| Computer | Frame Studio |
|---|---|
@@ -39,6 +38,8 @@ Studio needs a GPU with Vulkan 1.2: any recent NVIDIA, AMD or Intel driver on Wi
runs on Metal through MoltenVK, which comes inside the app. The 32-bit builds have at most 4 GB to work in, so the
very largest homes may not fit.
See [Installing Frame Home](docs/install-frame-home.md) for how to install the runtime on a Steam Frame.
## Highlights
**Frame Home (on the headset)**
@@ -75,7 +76,7 @@ very largest homes may not fit.
| [Performance](docs/performance.md) | Reaching 120 fps, checking it, fixing stutter |
| [Frame Studio guide](docs/studio-guide.md) | The Studio window, tools and panels |
| [Bundles](docs/bundles.md) | Several environments in one `.fbundle` |
| [Building from source](docs/building.md) | Building on each system, CI, the headset side |
| [Building from source](docs/building.md) | Building on each system, CI, and Frame Home payload builds |
| [Troubleshooting](docs/troubleshooting.md) | Symptoms and fixes, logs, the self-test |
## Building from source
@@ -87,8 +88,8 @@ Everything Studio needs is in `third_party/`; you need CMake 3.16+ and Ninja. In
| Windows (Visual Studio 2022 with C++) | `studio\build_windows.bat x64` (or `x86`, `arm64`) |
| Linux, macOS | `scripts/build-studio.sh <target>...` or `scripts/build-studio.sh all` |
The headset side is built on the Steam Frame; a ready-built copy is in `frame/payload/`. Full details, prerequisites
and the CI: [docs/building.md](docs/building.md).
Frame Home binaries are built in GitHub Actions and are not checked into `frame/payload/`. Full details, prerequisites
and CI: [docs/building.md](docs/building.md).
## Repository layout
@@ -97,7 +98,7 @@ and the CI: [docs/building.md](docs/building.md).
| `studio/` | Frame Studio: `studio.cpp` (the editor), `platform*.{h,cpp}` (Windows, Linux, macOS), `frame_pacer.h` |
| `src/` | Shared engine: renderer (`hsl_renderer`), scene loading and baking, `.fenv`, walkable area, Frame Home (`main.cpp`), the Environments panel (`switcher.cpp`), `fenv-tool` |
| `layer/` | The Vulkan layer inside SteamVR's compositor that shows Frame Home in place of its environment |
| `frame/` | The headset payload and the stand-alone installers |
| `frame/` | Frame Home installer source and stand-alone installer scripts |
| `scripts/`, `cmake/` | Build script and the llvm-mingw toolchain file |
| `tools/`, `tests_loco/` | Test and measuring tools, movement tests |
| `third_party/` | Vendored libraries (each with its own license) |
+55 -60
View File
@@ -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,9 +24,14 @@ CMake and Ninja.
studio\build_windows.bat x64
```
To bundle Frame Home with it, download the `FrameHome-payload` artifact from an Actions run, extract it, and set
`FRAME_HOME_PAYLOAD_DIR` to its payload directory before running the batch file, or
[build it manually](#frame-home-payload). Without one, Studio still builds (CMake warns), but its
**Install on Steam Frame** can't install Frame Home. Release builds (CI) require it.
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\`.
The headset files, when bundled, are copied beside it, in `frame\`.
To build into another folder, give it as the second argument: `studio\build_windows.bat x64 C:\builds\studio`.
@@ -41,6 +45,8 @@ scripts/build-studio.sh TARGET... [--build-dir DIR]
```
Each target is built and packed into `dist/`. `scripts/build-studio.sh --help` lists them.
The script uses `build/frame-home/payload` when present. Set `FRAME_HOME_PAYLOAD_DIR` to use a payload from another
location.
| Target | Build on | Output |
|---|---|---|
@@ -48,12 +54,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-<arch>.zip` |
| `macos-arm64`, `macos-x64` | A Mac with Xcode's command line tools | `dist/FrameStudio-macos-<arch>.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/<target>` unless you pass `--build-dir`.
@@ -108,18 +112,22 @@ 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.
Linux, macOS, and Frame Home jobs cache compiler outputs between runs; Windows/MSVC builds are not cached.
Linux jobs also cache downloaded apt packages, and macOS caches Homebrew downloads; apt repository metadata is still refreshed each run.
To publish a version:
To publish versions:
```sh
git tag v1.0
@@ -132,58 +140,45 @@ 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/`; the build manifest is included in the
payload artifact as `build/frame-home/payload/build-info.txt`. 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/payload/build-info.txt`; CI keeps it with the payload 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
+25 -7
View File
@@ -1,10 +1,10 @@
# Installing Frame Home
Frame Home is the part that runs on the Steam Frame. There are three ways to install it. All three do the same
Frame Home is the part that runs on the Steam Frame. There are four ways to install it. All four do the same
thing on the headset: they copy the files and run the same installer (`install.sh`).
Before you start, the headset needs developer mode and SSH: see [headset-setup.md](headset-setup.md). The third way
(on the headset itself) needs no SSH.
Before you start, the headset needs developer mode and SSH for the computer-based methods: see
[headset-setup.md](headset-setup.md). The two methods on the headset itself need no SSH.
## Way 1: from Frame Studio
@@ -23,10 +23,28 @@ Environments already on the headset with the same name and size are not copied a
Studio connects without asking for passwords, so it needs an SSH key (see [headset-setup.md](headset-setup.md)).
## Way 2: with FrameHome-installer.zip, from any computer
## Way 2: Install script from GitHub on the Steam Frame
Download `FrameHome-installer.zip` from the Releases page (see [getting-started.md](getting-started.md#download))
and unpack it. Inside are:
In Desktop Mode, open Konsole and run:
```sh
curl -fsSL https://raw.githubusercontent.com/Toastconcern/FrameStudio/main/frame/install-frame-home-online.sh | bash
```
This downloads the installer from the shared **Latest build** release. To install a versioned release instead, set
`FRAME_HOME_TAG` to its tag, for example:
```sh
curl -fsSL https://raw.githubusercontent.com/Toastconcern/FrameStudio/main/frame/install-frame-home-online.sh | FRAME_HOME_TAG=v1.0 bash
```
The bootstrap requires `curl` and `unzip`, verifies the downloaded ZIP, and runs the same `payload/install.sh` used
by the other install methods. Put `.fenv` files in `~/frame-home/envs`; the Environments panel lists them.
## Way 3: with FrameHome-installer.zip, from any computer
Download `FrameHome-installer.zip` from **Latest build** or a `v*` version release, then unpack it. Frame Home and
Studio share the same releases and tags, but this ZIP contains only Frame Home files. Inside are:
| File | For |
|---|---|
@@ -78,7 +96,7 @@ sh install-frame-home.sh --host steamos@192.168.1.50 my-home.fenv another.fenv
Copied environments get a headset-friendly name: lower case, spaces become dashes, and only letters, digits, `.`,
`_` and `-` are kept. `My Home.fenv` becomes `my-home.fenv`.
## Way 3: directly on the headset
## Way 4: from a downloaded folder on the headset
1. Switch the headset to Desktop Mode.
2. Copy the unpacked `FrameHome-installer` folder to the headset (USB stick, network share, or a download in the
Regular → Executable
View File
File mode changed.
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(uname -m)" != aarch64 ]]; then
echo "Frame Home must be installed on the Steam Frame (ARM64)." >&2
exit 1
fi
for tool in curl unzip; do
command -v "$tool" >/dev/null 2>&1 || {
echo "Missing required command: $tool" >&2
exit 1
}
done
tag=${FRAME_HOME_TAG:-latest}
if [[ -z "$tag" || "$tag" == *[^a-zA-Z0-9._-]* ]]; then
echo "Invalid release tag: $tag" >&2
exit 1
fi
url="https://github.com/Toastconcern/FrameStudio/releases/download/$tag/FrameHome-installer.zip"
tmp=$(mktemp -d "${TMPDIR:-/tmp}/frame-home-install.XXXXXXXX")
trap 'rm -rf "$tmp"' EXIT
archive="$tmp/FrameHome-installer.zip"
echo "Downloading Frame Home release $tag..."
curl --fail --location --silent --show-error --retry 2 --output "$archive" "$url"
unzip -tq "$archive"
unzip -q "$archive" -d "$tmp"
installer="$tmp/FrameHome-installer/payload"
if [[ ! -f "$installer/install.sh" || ! -x "$installer/bin/frame-home" ]]; then
echo "The downloaded release does not contain a valid Frame Home installer." >&2
exit 1
fi
sh "$installer/install.sh" "$@"
Regular → Executable
View File
File mode changed.
-1
View File
@@ -1 +0,0 @@
ed589d9db53c920be66e9334cc86e88e2893c2d410437d538477109cbf2fc02d
Binary file not shown.
Binary file not shown.
Binary file not shown.
Regular → Executable
View File
File mode changed.
Binary file not shown.
Binary file not shown.
+137
View File
@@ -0,0 +1,137 @@
#!/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
command -v ccache >/dev/null 2>&1 || { echo "Release builds require ccache" >&2; exit 1; }
"$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 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
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/"
compile_layer() {
if [ "$MODE" = ci ]; then ccache "$CXX" "$@"; else "$CXX" "$@"; fi
}
compile_layer -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
# Release builds load only what every SteamOS has (OpenVR and Vulkan are found at run time, libdeflate is linked in).
if [ "$MODE" = ci ]; then
extra=$(readelf -d "$binary" | sed -n 's/.*(NEEDED).*\[\(.*\)\]/\1/p' |
grep -vxE 'libc\.so\.6|libm\.so\.6|libdl\.so\.2|libpthread\.so\.0|libstdc\+\+\.so\.6|libgcc_s\.so\.1|ld-linux-aarch64\.so\.1' || true)
if [ -n "$extra" ]; then
echo "$binary needs libraries a headset may not have: $(echo $extra)" >&2
exit 1
fi
fi
done
{
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
cp "$OUT/build-info.txt" "$PAYLOAD/build-info.txt"
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)
python3 - "$OUT/FrameHome-installer.zip" <<'PY'
import sys
import zipfile
required = {
"FrameHome-installer/payload/install.sh",
"FrameHome-installer/payload/build-info.txt",
"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 = {
info.filename.rstrip("/")
for info in archive.infolist()
if not info.is_dir()
}
missing = required - names
unexpected = names - required - allowed_files
if missing or unexpected:
raise SystemExit(f"Invalid Frame Home installer: missing={sorted(missing)}, unexpected={sorted(unexpected)}")
PY
echo "Built $OUT/payload (including build-info.txt), $OUT/FrameHome-installer.zip"
+27 -23
View File
@@ -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-<target>.zip (.tar.gz on Linux) and dist/FrameHome-installer.zip.
# Output: dist/FrameStudio-<target>.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:-}
CMAKE=${CMAKE:-cmake}
TARGETS=""
while [ $# -gt 0 ]; do
@@ -28,15 +27,33 @@ while [ $# -gt 0 ]; do
esac
done
[ -n "$TARGETS" ] || { sed -n '2,19p' "$0"; exit 1; }
if [ -z "$FRAME_PAYLOAD_DIR" ]; then
for candidate in "$BUILD/frame-home/payload" "$ROOT/build/frame-home/payload"; do
if [ -d "$candidate" ]; then FRAME_PAYLOAD_DIR=$candidate; break; fi
done
[ -n "$FRAME_PAYLOAD_DIR" ] || FRAME_PAYLOAD_DIR="$ROOT/frame/payload"
fi
mkdir -p "$DIST" "$BUILD"
say() { echo "build-studio: $*"; }
say "using Frame Home payload: $FRAME_PAYLOAD_DIR"
configure() {
source=$1; dir=$2; shift 2
if [ "${FRAME_USE_CCACHE:-0}" = 1 ]; then
command -v ccache >/dev/null 2>&1 || { say "FRAME_USE_CCACHE is set but ccache is unavailable"; exit 1; }
"$CMAKE" -S "$source" -B "$dir" -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache "$@"
else
"$CMAKE" -S "$source" -B "$dir" -DCMAKE_BUILD_TYPE=Release "$@"
fi
}
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 +64,7 @@ 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 || {
configure "$ROOT/studio" "$dir" "-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
}
@@ -58,7 +75,7 @@ build_pc_home() {
target=$1; shift
dir="$BUILD/$target-home"
say "building Frame Home for $target's SteamVR"
"$CMAKE" -S "$ROOT" -B "$dir" -DCMAKE_BUILD_TYPE=Release -DFRAME_HOME_PC=ON "$@" > "$dir.configure.log" 2>&1 || {
configure "$ROOT" "$dir" -DFRAME_HOME_PC=ON "$@" > "$dir.configure.log" 2>&1 || {
tail -20 "$dir.configure.log"; say "$target: Frame Home configure failed (full log: $dir.configure.log)"; exit 1; }
"$CMAKE" --build "$dir" --target frame-home frame-switcher --parallel
}
@@ -73,8 +90,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 +129,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 +140,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
-34
View File
@@ -1,34 +0,0 @@
#!/bin/sh
# The Steam Frame programs in frame/payload are built on the headset (arm64 SteamOS, its compositor's Vulkan driver)
# and committed; every Frame Studio download and the Frame Home installer ship them. frame/payload/SOURCE records the
# source they were built from, so Studio is never released beside an older headset build (Studio would save
# environments the headset can't open).
#
# scripts/frame-payload.sh stamp after copying freshly built programs into frame/payload
# scripts/frame-payload.sh check fails when the headset's source changed since (CI runs it before releasing)
set -eu
ROOT=$(cd "$(dirname "$0")/.." && pwd)
STAMP="$ROOT/frame/payload/SOURCE"
# What the headset programs are built from (studio/ is Studio's alone), as the files are now: committed or not.
source_hash() {
list=$(mktemp)
(cd "$ROOT" && git -c core.quotepath=off ls-files -- src third_party layer CMakeLists.txt > "$list" &&
git hash-object --stdin-paths < "$list" | paste -d ' ' - "$list" | sha256sum | cut -c1-64)
rm -f "$list"
}
case "${1:-}" in
stamp) source_hash > "$STAMP"
echo "frame/payload/SOURCE: $(cat "$STAMP")" ;;
check) want=$(source_hash)
have=$(cat "$STAMP" 2>/dev/null || true)
if [ "$want" != "$have" ]; then
echo "frame/payload is out of date: the headset's source changed since its programs were built." >&2
echo "Rebuild frame-home, frame-switcher and fenv-tool on the Frame, copy them into frame/payload/bin," >&2
echo "then run scripts/frame-payload.sh stamp (docs/building.md)." >&2
exit 1
fi
echo "frame/payload matches the source ($want)" ;;
*) sed -n '2,8p' "$0"; exit 2 ;;
esac
+4
View File
@@ -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
+2 -2
View File
@@ -708,8 +708,8 @@ DebugInfo readDebugInfo() {
}
// ── Updates (the Steam Frame only) ──
// Frame Home updates from GitHub. The release "latest" carries latest.json, whose "home" is a hash of the headset
// programs (frame-home, frame-switcher, fenv-tool and the compositor layer, worked out by CI from frame/payload). When
// Frame Home updates from GitHub. The shared "latest" release carries latest.json, whose "home" is a hash of the
// headset programs (frame-home, frame-switcher, fenv-tool and the compositor layer, worked out by CI). When
// it isn't the hash of what's installed here, the header offers Update Frame Home: that downloads
// FrameHome-installer.zip and runs its install.sh, which puts the new programs in place and restarts Frame Home and
// this panel. Checked a minute after the panel starts, then every 6 hours (30 minutes after a failed check).
+25 -2
View File
@@ -18,6 +18,27 @@ 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()
# A release must carry it (CI: GitHub sets CI); a developer's build without one still builds, and its Install on Steam
# Frame says the headset files are missing.
option(FRAME_HOME_REQUIRE_PAYLOAD "Fail when the Frame Home payload is incomplete" "$ENV{CI}")
set(FRAME_HOME_PAYLOAD_COMPLETE ON)
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}")
set(FRAME_HOME_PAYLOAD_COMPLETE OFF)
set(FRAME_HOME_MISSING "${FRAME_HOME_FILE}")
endif()
endforeach()
if(NOT FRAME_HOME_PAYLOAD_COMPLETE)
set(FRAME_HOME_PAYLOAD_HELP "build or download it and set FRAME_HOME_PAYLOAD_DIR (${FRAME_HOME_MISSING} missing)")
if(FRAME_HOME_REQUIRE_PAYLOAD)
message(FATAL_ERROR "Frame Home payload is incomplete; ${FRAME_HOME_PAYLOAD_HELP}")
endif()
message(WARNING "Frame Home payload is incomplete, so this Studio can't install Frame Home; ${FRAME_HOME_PAYLOAD_HELP}")
endif()
if(WIN32)
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS VK_USE_PLATFORM_WIN32_KHR)
@@ -157,8 +178,10 @@ if(APPLE)
set_target_properties(frame-studio PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Frame Studio" MACOSX_BUNDLE_GUI_IDENTIFIER "com.framestudio.app")
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" "$<TARGET_FILE_DIR:frame-studio>/frame")
if(FRAME_HOME_PAYLOAD_COMPLETE)
add_custom_command(TARGET frame-studio POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${FRAME_HOME_PAYLOAD_DIR}" "$<TARGET_FILE_DIR:frame-studio>/frame")
endif()
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()
+3 -1
View File
@@ -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%)