mirror of
https://github.com/Toastconcern/FrameStudio.git
synced 2026-10-28 23:03:21 +01:00
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:
commit
fdf765f90a
23 files changed
+528
-177
No files matched your search
+196
-37
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
File mode changed.
Executable
+38
@@ -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
File mode changed.
@@ -1 +0,0 @@
|
||||
ed589d9db53c920be66e9334cc86e88e2893c2d410437d538477109cbf2fc02d
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Regular → Executable
File mode changed.
Binary file not shown.
Binary file not shown.
Executable
+137
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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%)
|
||||
Reference in new issue
Block a user