# 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 }} cancel-in-progress: true # 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: include: - { arch: x64, runner: ubuntu-24.04, extra: "" } - { arch: x86, runner: ubuntu-24.04, i386: true, extra: "gcc-multilib g++-multilib libx11-dev:i386 libxrandr-dev:i386 libxinerama-dev:i386 libxcursor-dev:i386 libxi-dev:i386" } - { arch: arm64, runner: ubuntu-24.04-arm, extra: "" } runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: FrameHome-payload path: build/frame-home/payload if-no-files-found: error - name: Tools and X11 headers run: | # x86 links against 32-bit X11, so apt needs the i386 packages too if [ "${{ matrix.i386 }}" = "true" ]; then sudo dpkg --add-architecture i386; fi 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 }} - name: Build run: sh scripts/build-studio.sh linux-${{ matrix.arch }} - uses: actions/upload-artifact@v4 with: name: FrameStudio-linux-${{ matrix.arch }} path: dist/FrameStudio-linux-${{ matrix.arch }}.tar.gz windows: name: Windows ${{ matrix.arch }} needs: [frame-home] strategy: fail-fast: false matrix: arch: [x64, x86, arm64] runs-on: windows-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: FrameHome-payload path: build/frame-home/payload - name: Build (MSVC) shell: cmd run: studio\build_windows.bat ${{ matrix.arch }} "%GITHUB_WORKSPACE%\build\windows-${{ matrix.arch }}" - name: Package shell: pwsh run: | $name = "FrameStudio-windows-${{ matrix.arch }}" $stage = "build/stage/$name" New-Item -ItemType Directory -Force $stage | Out-Null 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 with: name: FrameStudio-windows-${{ matrix.arch }} path: dist/FrameStudio-windows-${{ matrix.arch }}.zip macos: name: macOS ${{ matrix.arch }} needs: [frame-home] strategy: fail-fast: false matrix: include: - { arch: arm64, runner: macos-14 } # Intel: macos-13 was retired (Dec 2025); macos-15-intel is GitHub's last Intel Mac runner (until Aug 2027). # Not cross-built on an ARM Mac because Homebrew's MoltenVK, bundled into the app, is the runner's own arch. - { arch: x64, runner: macos-15-intel } runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: FrameHome-payload path: build/frame-home/payload - name: Tools and MoltenVK (Vulkan on Metal, bundled into the app) run: brew install ninja molten-vk - name: Build run: sh scripts/build-studio.sh macos-${{ matrix.arch }} - uses: actions/upload-artifact@v4 with: name: FrameStudio-macos-${{ matrix.arch }} path: dist/FrameStudio-macos-${{ matrix.arch }}.zip frame-home: name: Build Frame Home payload runs-on: ubuntu-24.04-arm timeout-minutes: 45 steps: - uses: actions/checkout@v4.2.2 - name: Build payload and installer run: | sudo apt-get update sudo apt-get install -y cmake ninja-build gcc-13 g++-13 glslc zip sh scripts/build-frame-home.sh --ci - uses: actions/upload-artifact@v4 with: name: FrameHome-payload path: build/frame-home/payload - uses: actions/upload-artifact@v4 with: name: FrameHome-installer path: build/frame-home/FrameHome-installer.zip if-no-files-found: error - uses: actions/upload-artifact@v4 with: name: FrameHome-build-info path: build/frame-home/build-info.txt if-no-files-found: error release: name: Publish # "latest" gets every build that finished (a failed or still-slow platform just isn't in it); a version release # needs them all. We don't wait on macOS: its runners can sit in GitHub's queue for hours, and since a push # cancels the previous run, waiting meant no run ever reached this job. Both Mac builds still upload and are # attached whenever they finish in time. if: ${{ !cancelled() && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') }} needs: [linux, windows, frame-home] runs-on: ubuntu-24.04 timeout-minutes: 20 permissions: contents: write id-token: write attestations: write env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} steps: - uses: actions/download-artifact@v4 with: pattern: FrameStudio-* path: dist merge-multiple: true - uses: actions/download-artifact@v4 with: name: FrameHome-payload path: ${{ runner.temp }}/frame-home-payload - uses: actions/download-artifact@v4 with: name: FrameHome-installer path: dist # What Frame Studio (studio/updater.h) and the headset's Environments panel check for updates: this commit, # Frame Home's identity (a hash of the headset programs, the same one the panel works out for what's installed) # and the files in this release. - name: latest.json run: | home=$(cd "$RUNNER_TEMP/frame-home-payload" && sha256sum bin/frame-home bin/frame-switcher bin/fenv-tool layer/libframe_home_layer.so layer/env_ps.spv | cut -c1-64 | sha256sum | cut -c1-64) files=$(ls dist | python3 -c 'import json, sys; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))') printf '{"build": "%s", "home": "%s", "date": "%s", "files": %s}\n' "$GITHUB_SHA" "$home" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$files" > dist/latest.json cat dist/latest.json - uses: actions/attest-build-provenance@v2 with: subject-path: dist/FrameStudio-* - uses: actions/attest-build-provenance@v2 with: subject-path: dist/FrameHome-installer.zip - name: Version release (tag v*) if: ${{ startsWith(github.ref, 'refs/tags/v') && !contains(needs.*.result, 'failure') }} run: gh release create "${{ github.ref_name }}" dist/* --repo "$REPO" --title "Frame Studio ${{ github.ref_name }}" --generate-notes - name: Latest build (push to main) if: github.ref == 'refs/heads/main' run: | gh release delete latest --repo "$REPO" --yes --cleanup-tag 2>/dev/null || true gh release create latest dist/* --repo "$REPO" --prerelease --target "${{ github.sha }}" \ --title "Latest build (${GITHUB_SHA::7})" \ --notes "Built automatically from ${{ github.sha }} on $(date -u '+%Y-%m-%d %H:%M UTC'). Replaced by every push to main."