From 338759e3e7843c2b761e07b89ced6a79e7313cf3 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 3 Oct 2026 20:53:20 -0400 Subject: [PATCH 1/4] CI builds name their grid agni, not "agni" with its quotes The configure step passed AL_GRID with quotes of its own, a leftover of when the grid was a C string define. Since the install rules became the package manifest, AL_GRID is a plain string checked against agni: the quoted name never matched, so every CI build shipped a settings_install.xml with CmdLineGridChoice set to "agni". The viewer took that for a grid given on the command line, could not find it, and in doing so skipped the user's last grid (CurrentGrid) and, at the login panel, their saved start location. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 33eb212bbc..22e7307887 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -407,7 +407,7 @@ jobs: -DAL_USE_SENTRY:BOOL="$AL_USE_SENTRY" \ -DAL_SENTRY_DSN:STRING="$AL_SENTRY_DSN" \ -DAL_CHANNEL:STRING="${AL_CHANNEL}" \ - -DAL_GRID:STRING="\"$VIEWER_GRID\"" \ + -DAL_GRID:STRING="$VIEWER_GRID" \ $template_verifier_master_url \ "${SIGNING[@]}" From 6d1169c4a687a5a9bc5b127752cd7b89c5f600d9 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 3 Oct 2026 20:53:31 -0400 Subject: [PATCH 2/4] Pull requests read the vcpkg cache in R2 and no longer write it Same-repository pull requests were given the writer keys and readwrite mode as a temporary test to populate the cache. That let code still under review put packages in the cache that release builds then use. They are back on the read-only keys in read mode, as the cache was set up; the read-only keys pass the cache's preflight, as the pull request builds of 2026-09-20 showed. Only trusted builds of protected refs write. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 22e7307887..1c81847ce4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -329,12 +329,13 @@ jobs: vars.VCPKG_R2_ENABLED == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository env: - # Temporary cache population test; restore read mode and reader secrets afterward. - R2_CACHE_MODE: readwrite + # Read only, with the read-only keys: code still under review must + # not put packages in front of a release build. + R2_CACHE_MODE: read R2_ENDPOINT_URL: ${{ vars.VCPKG_R2_ENDPOINT_URL }} R2_BUCKET: ${{ vars.VCPKG_R2_BUCKET }} - R2_ACCESS_KEY_ID: ${{ secrets.VCPKG_R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.VCPKG_R2_SECRET_ACCESS_KEY }} + R2_ACCESS_KEY_ID: ${{ secrets.VCPKG_R2_READ_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.VCPKG_R2_READ_SECRET_ACCESS_KEY }} run: python scripts/vcpkg/configure_r2_cache.py - name: Cache Rust Dependencies From a853d337bc7e208815293e914638d728afe7bbe6 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 3 Oct 2026 20:53:50 -0400 Subject: [PATCH 3/4] CodeQL analyses the viewer as built, and the workflows run with least privilege CodeQL's default setup read the C/C++ without building it: no vcpkg header resolved, no platform #if decided, two and three quarter hours a run. The CodeQL workflow traces a real build instead, on Linux and on Windows, with the security-extended queries, plus Python and the workflows themselves without a build. The configure runs before tracing starts, so the vcpkg ports stay out of the database, and the build tree sits outside the checkout, so their headers stay out of the alerts. Linux keeps default setup's category, so its alerts and dismissals carry over. Default setup has to be switched off for it to upload. - A composite action, setup-build, holds the host packages, Python tools, vcpkg bootstrap and R2 cache setup the build and CodeQL share. Its Rust cache is keyed on the vcpkg manifest and registry baseline; the Cargo.lock it was keyed on never existed, so the key never moved. - Every workflow names its token's permissions, so the repository's default can be read only: the release job writes contents, CodeQL writes security events, the labeler writes pull requests, the rest read or nothing. The build job no longer asks for packages: write. - Inputs and ref names reach scripts through the environment, never pasted into them; checkouts keep no credentials. - Build: a pull request's new push cancels its old run; one platform failing no longer cancels the others; pull requests that change only documentation or GitHub files no build reads do not build; the release carries the tag's relnotes; the packaging job checks out only dotnet-tools.json; the .tar.xz and .dmg packages upload without being compressed a second time; dead matrix excludes and step outputs are gone. - Dependabot watches the composite action and no longer proposes vcpkg baselines: it moves builtin-baseline without the submodule, and every such pull request failed to configure. - Lint workflows runs actionlint on changes under .github. - The labeler drops libraries that are gone and labels alscript, llwebrtc, llphysicsextensionsos, media_plugins and .github. Co-Authored-By: Claude Opus 5.5 --- .github/actionlint.yaml | 7 + .github/actions/setup-build/action.yaml | 114 ++++++++++++ .github/codeql/codeql-config.yaml | 18 ++ .github/dependabot.yaml | 12 +- .github/labeler.yaml | 28 ++- .github/workflows/build.yaml | 238 ++++++++++-------------- .github/workflows/check-pr.yaml | 5 +- .github/workflows/codeql.yaml | 142 ++++++++++++++ .github/workflows/label.yaml | 5 + .github/workflows/lint-workflows.yaml | 38 ++++ .github/workflows/tag-release.yaml | 24 ++- 11 files changed, 467 insertions(+), 164 deletions(-) create mode 100644 .github/actionlint.yaml create mode 100644 .github/actions/setup-build/action.yaml create mode 100644 .github/codeql/codeql-config.yaml create mode 100644 .github/workflows/codeql.yaml create mode 100644 .github/workflows/lint-workflows.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000000..baf5f4c625 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,7 @@ +self-hosted-runner: + # Runner images newer than actionlint's list of GitHub-hosted labels. + labels: + - ubuntu-26.04 + - ubuntu-26.04-arm + - windows-11-vs2026-arm + - xcode-27 diff --git a/.github/actions/setup-build/action.yaml b/.github/actions/setup-build/action.yaml new file mode 100644 index 0000000000..f277b4d21f --- /dev/null +++ b/.github/actions/setup-build/action.yaml @@ -0,0 +1,114 @@ +name: Set up the build +description: >- + Installs the host packages, Python tools and vcpkg that configuring the + viewer needs, and adds the R2 vcpkg binary cache when asked to. Expects the + repository checked out with its submodules and VCPKG_ROOT set. + +inputs: + r2-cache-mode: + description: "read or readwrite to use the R2 vcpkg binary cache; empty to go without it" + required: false + default: "" + r2-endpoint-url: + description: "The R2 account's S3 endpoint" + required: false + default: "" + r2-bucket: + description: "The R2 bucket holding the cache" + required: false + default: "" + r2-access-key-id: + description: "R2 access key id" + required: false + default: "" + r2-secret-access-key: + description: "R2 secret access key" + required: false + default: "" + +runs: + using: composite + steps: + - name: Linux dependency install and disk cleanup + if: runner.os == 'Linux' + shell: bash + run: | + sudo apt update + sudo apt install -y \ + autoconf autoconf-archive automake bison build-essential cmake curl flex gettext \ + libasound2-dev libaudio-dev libdbus-1-dev libdecor-0-dev libdrm-dev \ + libegl1-mesa-dev libfribidi-dev libgbm-dev libgl1-mesa-dev libgl1-mesa-dri libgles2-mesa-dev \ + libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libibus-1.0-dev libjack-dev libltdl-dev \ + libpipewire-0.3-dev libpulse-dev libsndio-dev libtext-unidecode-perl \ + libthai-dev libtool libudev-dev libunwind-dev liburing-dev libvlc-dev libwayland-dev \ + libx11-dev libxcursor-dev libxext-dev libxfixes-dev libxft-dev libxi-dev libxinerama-dev \ + libxkbcommon-dev libxrandr-dev libxss-dev libxtst-dev linux-libc-dev mold \ + nasm ninja-build pkgconf tar tex-common texinfo unzip zip + + sudo locale-gen en_US.UTF-8 + sudo locale-gen en_GB.UTF-8 + sudo locale-gen fr_FR.UTF-8 + + df -h + sudo docker container prune -f + sudo docker image prune -a -f + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/.ghcup + df -h + + - name: macOS Homebrew dependency install + if: runner.os == 'macOS' + shell: bash + run: brew install autoconf autoconf-archive automake libtool nasm unzip zip + + - name: Setup python + uses: actions/setup-python@v7 + with: + python-version: "3.14" + + # Linux builds with the CMake floor, so the floor is a floor. + - name: Install python dependencies + shell: bash + run: | + if [[ "$RUNNER_OS" == "Linux" ]] + then pip3 install llsd cmake==4.0.0 ninja + else pip3 install llsd cmake ninja + fi + + - name: Bootstrap vcpkg Windows + if: runner.os == 'Windows' + shell: pwsh + run: ./vcpkg/bootstrap-vcpkg.bat + + - name: Bootstrap vcpkg non-Windows + if: runner.os != 'Windows' + shell: bash + run: ./vcpkg/bootstrap-vcpkg.sh + + # Ports built with cargo (Velopack) fetch their crates here. Keyed on the + # manifest and the registry baseline, which decide the crate versions. + - name: Cache Rust dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('indra/vcpkg.json', 'indra/vcpkg-configuration.json') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Install AWS CLI on macOS + if: inputs.r2-cache-mode != '' && runner.os == 'macOS' + shell: bash + run: brew install awscli + + - name: Configure R2 binary cache + if: inputs.r2-cache-mode != '' + shell: bash + env: + R2_CACHE_MODE: ${{ inputs.r2-cache-mode }} + R2_ENDPOINT_URL: ${{ inputs.r2-endpoint-url }} + R2_BUCKET: ${{ inputs.r2-bucket }} + R2_ACCESS_KEY_ID: ${{ inputs.r2-access-key-id }} + R2_SECRET_ACCESS_KEY: ${{ inputs.r2-secret-access-key }} + run: python scripts/vcpkg/configure_r2_cache.py diff --git a/.github/codeql/codeql-config.yaml b/.github/codeql/codeql-config.yaml new file mode 100644 index 0000000000..4bbe718b82 --- /dev/null +++ b/.github/codeql/codeql-config.yaml @@ -0,0 +1,18 @@ +name: Alchemy CodeQL config + +# security-extended adds the lower-precision security queries to the default +# set, the taint tracking among them: data from the network followed into +# parsers, allocations and file paths. A rule that proves too noisy for this +# codebase goes in query-filters, e.g. +# +# query-filters: +# - exclude: +# id: cpp/some-rule +queries: + - uses: security-extended + +# Only the languages read without a build (Python, Actions) honour paths: +# C/C++ analyses exactly what the build compiles. +paths-ignore: + - indra/externals + - vcpkg diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index a522dcc477..4452870f69 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -1,12 +1,14 @@ version: 2 updates: + # The workflows, and the composite actions they share. - package-ecosystem: github-actions - directory: / + directories: + - / + - /.github/actions/* schedule: interval: weekly - - package-ecosystem: vcpkg - directory: /indra # The location of vcpkg.json - schedule: - interval: weekly + # No vcpkg entry: Dependabot moves builtin-baseline but not the vcpkg + # submodule, and a baseline the submodule does not contain fails every + # configure. The two move together, by hand. diff --git a/.github/labeler.yaml b/.github/labeler.yaml index 96b8043824..8c832d620b 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -1,3 +1,7 @@ +alscript: +- changed-files: + - any-glob-to-any-file: indra/alscript/** + llappearance: - changed-files: - any-glob-to-any-file: indra/llappearance/** @@ -18,10 +22,6 @@ llcorehttp: - changed-files: - any-glob-to-any-file: indra/llcorehttp/** -llcrashlogger: -- changed-files: - - any-glob-to-any-file: indra/llcrashlogger/** - llfilesystem: - changed-files: - any-glob-to-any-file: indra/llfilesystem/** @@ -46,14 +46,14 @@ llmath: - changed-files: - any-glob-to-any-file: indra/llmath/** -llmeshoptimizer: -- changed-files: - - any-glob-to-any-file: indra/llmeshoptimizer/** - llmessage: - changed-files: - any-glob-to-any-file: indra/llmessage/** +llphysicsextensionsos: +- changed-files: + - any-glob-to-any-file: indra/llphysicsextensionsos/** + llplugin: - changed-files: - any-glob-to-any-file: indra/llplugin/** @@ -70,6 +70,10 @@ llui: - changed-files: - any-glob-to-any-file: indra/llui/** +llwebrtc: +- changed-files: + - any-glob-to-any-file: indra/llwebrtc/** + llwindow: - changed-files: - any-glob-to-any-file: indra/llwindow/** @@ -78,6 +82,10 @@ llxml: - changed-files: - any-glob-to-any-file: indra/llxml/** +media_plugins: +- changed-files: + - any-glob-to-any-file: indra/media_plugins/** + viewer: - changed-files: - any-glob-to-any-file: indra/newview/** @@ -115,3 +123,7 @@ objc: - any-glob-to-any-file: - '**/*.m' - '**/*.mm' + +github_actions: +- changed-files: + - any-glob-to-any-file: .github/** diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1c81847ce4..3b47d277d7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,18 +22,40 @@ on: type: string default: "hippo" pull_request: + # Everything but documentation and the GitHub files no build reads; + # later patterns win. + paths: + - "**" + - "!**/*.md" + - "!doc/**" + - "!LICENSE" + - "!.github/**" + - ".github/workflows/build.yaml" + - ".github/actions/**" push: branches: ["main", "release/*", "beta/*", "project/*"] tags: ["Alchemy*"] +# Every job reads the repository and nothing more, unless it says otherwise. permissions: contents: read +# A new push to a pull request supersedes the run in flight. Everything else +# (branches, tags, dispatches) runs to completion. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + jobs: # The whole point of the setup job is that we want to set variables once # that will be consumed by multiple subsequent jobs. setup: runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + # which-branch looks up a tag's branch and that branch's pull request. + pull-requests: read outputs: viewer_branch: ${{ steps.which-branch.outputs.branch }} relnotes: ${{ steps.which-branch.outputs.relnotes }} @@ -43,20 +65,17 @@ jobs: sentry_dsn: ${{ steps.setvar.outputs.sentry_dsn }} viewer_channel: ${{ steps.setvar.outputs.viewer_channel }} is_private_build: ${{ steps.setvar.outputs.is_private_build }} + r2_cache_mode: ${{ steps.setvar.outputs.r2_cache_mode }} env: # Build with a tag like "Alchemy#abcdef0" to generate a release page # (used for builds we are planning to deploy). # When you want to use a string variable as a workflow YAML boolean, it's # important to ensure it's the empty string when false. If you omit || '', # its value when false is "false", which is interpreted as true. - RELEASE_RUN: ${{ (github.event.inputs.release_run || github.ref_type == 'tag' && startsWith(github.ref_name, 'Alchemy')) && 'Y' || '' }} + RELEASE_RUN: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'Alchemy') && 'Y' || '' }} FROM_FORK: ${{ github.event.pull_request.head.repo.fork }} steps: - - name: Checkout code - uses: actions/checkout@v7 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - + # which-branch installs PyGithub for tag builds. - name: Setup python uses: actions/setup-python@v7 with: @@ -77,6 +96,14 @@ jobs: # The Sentry project crash reports go to; unset, the viewer is # built without a crash reporter. SENTRY_DSN: ${{ vars.SENTRY_DSN }} + # Inputs and ref names reach the script through the environment, + # never pasted into it, so no input can become a command. + INPUT_CHANNEL: ${{ inputs.channel }} + INPUT_PROJECT: ${{ inputs.project }} + IS_EDU_TAG: ${{ github.ref_type == 'tag' && contains(github.ref_name, 'edu') }} + R2_ENABLED: ${{ vars.VCPKG_R2_ENABLED }} + REF_PROTECTED: ${{ github.ref_protected }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} run: | echo "release_run=$RELEASE_RUN" >> "$GITHUB_OUTPUT" @@ -108,25 +135,42 @@ jobs: echo "sentry_dsn=" >> "$GITHUB_OUTPUT" fi + # The vcpkg binary cache in R2. Only trusted builds of protected + # refs write it; same-repository pull requests read it with the + # read-only keys, so code still under review cannot put packages in + # front of a release build. Forks get no secrets and Dependabot gets + # its own, so neither uses it. + r2_cache_mode= + if [[ "$R2_ENABLED" == "true" && "$GITHUB_ACTOR" != "dependabot[bot]" ]]; then + if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then + if [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; then + r2_cache_mode="read" + fi + elif [[ "$REF_PROTECTED" == "true" ]]; then + r2_cache_mode="readwrite" + fi + fi + echo "r2_cache_mode=$r2_cache_mode" >> "$GITHUB_OUTPUT" + # determine the viewer channel from the branch or tag name # trigger an EDU build by including "edu" in the tag - edu=${{ github.ref_type == 'tag' && contains(github.ref_name, 'edu') }} - echo "ref_type=${{ github.ref_type }}, ref_name=${{ github.ref_name }}, edu='$edu'" + edu=$IS_EDU_TAG + echo "ref_type=$GITHUB_REF_TYPE, ref_name=$GITHUB_REF_NAME, edu='$edu'" branch=$BUILD_VCS_BRANCH - if [[ "${{inputs.channel}}" != "" && "${{inputs.channel}}" != "Auto" ]]; + if [[ "$INPUT_CHANNEL" != "" && "$INPUT_CHANNEL" != "Auto" ]]; then - if [[ "${{inputs.channel}}" == "Project" ]]; + if [[ "$INPUT_CHANNEL" == "Project" ]]; then - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} Project ${{inputs.project}}" + export viewer_channel="$VIEWER_CHANNEL_BASE Project $INPUT_PROJECT" else - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} ${{inputs.channel}}" + export viewer_channel="$VIEWER_CHANNEL_BASE $INPUT_CHANNEL" fi elif [[ "$edu" == "true" ]]; then - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} Release edu" + export viewer_channel="$VIEWER_CHANNEL_BASE Release edu" elif [[ "$branch" == "develop" ]]; then - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} Develop" + export viewer_channel="$VIEWER_CHANNEL_BASE Develop" else IFS='/' read -ra ba <<< "$branch" prefix=${ba[0]} @@ -135,21 +179,21 @@ jobs: prj_str="${prj[*]}" # uppercase first letter of each word capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}') - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} Project $capitalized" + export viewer_channel="$VIEWER_CHANNEL_BASE Project $capitalized" elif [[ "$prefix" == "release" || "$prefix" == "main" ]]; then - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} Release" + export viewer_channel="$VIEWER_CHANNEL_BASE Release" else - export viewer_channel="${{ env.VIEWER_CHANNEL_BASE }} Test" + export viewer_channel="$VIEWER_CHANNEL_BASE Test" fi fi echo "viewer_channel=$viewer_channel" >> "$GITHUB_OUTPUT" build: needs: setup - permissions: - packages: write strategy: + # One platform failing says nothing about the others: let them finish. + fail-fast: false matrix: runner: ["windows-2025-vs2026", "xcode-27", "ubuntu-26.04"] configuration: [release] @@ -162,16 +206,8 @@ jobs: arch: arm64 - runner: windows-2025-vs2026 arch: arm64 - - runner: xcode-27 - configuration: relwithdebinfo - - runner: xcode-27 - configuration: optdebug - runner: xcode-27 arch: x64 - - build_variant: Viewer - configuration: relwithdebinfo - - build_variant: Viewer - configuration: optdebug include: # Linux on arm64: NEON is a first-class target, and this is the # one row where GCC compiles the SIMD layer for it. @@ -194,8 +230,7 @@ jobs: build_variant: Tests runs-on: ${{ matrix.runner }} - # Windows arm64 is allowed to fail, without cancelling the other rows, - # until the vlc-bin port has an arm64 Windows build. + # A failing test row is reported without failing the run. continue-on-error: ${{ startsWith(matrix.build_variant, 'Tests') }} outputs: viewer_version: ${{ steps.build.outputs.viewer_version }} @@ -234,117 +269,35 @@ jobs: master_message_template_checkout: ${{ github.workspace }}/.master-message-template # vcpkg Setup VCPKG_ROOT: ${{ github.workspace }}/vcpkg - VCPKG_USERNAME: ${{ github.repository_owner }} - # VCPKG_FEED_URL: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json VCPKG_BINARY_SOURCES: "clear;default,readwrite" steps: - - name: Linux Dependency Install and Disk Cleanup - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install -y \ - autoconf autoconf-archive automake bison build-essential cmake curl flex gettext \ - libasound2-dev libaudio-dev libdbus-1-dev libdbus-1-dev libdecor-0-dev libdrm-dev \ - libegl1-mesa-dev libfribidi-dev libgbm-dev libgl1-mesa-dev libgl1-mesa-dri libgles2-mesa-dev \ - libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libibus-1.0-dev libjack-dev libltdl-dev \ - libpipewire-0.3-dev libpulse-dev libsndio-dev libtext-unidecode-perl \ - libthai-dev libtool libudev-dev libunwind-dev liburing-dev libvlc-dev libwayland-dev \ - libx11-dev libxcursor-dev libxext-dev libxfixes-dev libxft-dev libxi-dev libxinerama-dev \ - libxkbcommon-dev libxrandr-dev libxss-dev libxtst-dev linux-libc-dev mold \ - nasm ninja-build pkgconf tar tex-common texinfo unzip zip - - sudo locale-gen en_US.UTF-8 - sudo locale-gen en_GB.UTF-8 - sudo locale-gen fr_FR.UTF-8 - - df -h - sudo docker container prune -f - sudo docker image prune -a -f - sudo rm -rf /usr/local/lib/android - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/.ghcup - df -h - - - name: macOS Homebrew Dependency Install - if: runner.os == 'macOS' - run: | - brew install autoconf autoconf-archive automake libtool nasm unzip zip - - name: Checkout code uses: actions/checkout@v7 with: + # Full history: the vcpkg submodule needs it to resolve the + # versions the manifest pins. fetch-depth: 0 + persist-credentials: false submodules: recursive ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Checkout master-message-template uses: actions/checkout@v7 with: + persist-credentials: false repository: secondlife/master-message-template path: .master-message-template - - name: Setup python - uses: actions/setup-python@v7 + - name: Set up the build + uses: ./.github/actions/setup-build with: - python-version: "3.14" - - # Linux builds with the CMake floor, so the floor is a floor. - - name: Install python dependencies - shell: bash - run: | - if [[ "$RUNNER_OS" == "Linux" ]] - then pip3 install llsd cmake==4.0.0 ninja - else pip3 install llsd cmake ninja - fi - - - name: Bootstrap vcpkg Windows - if: runner.os == 'Windows' - run: | - ./vcpkg/bootstrap-vcpkg.bat - - - name: Bootstrap vcpkg non-Windows - if: runner.os == 'macOS' || runner.os == 'Linux' - run: | - ./vcpkg/bootstrap-vcpkg.sh - - - name: Install AWS CLI on macOS - if: >- - vars.VCPKG_R2_ENABLED == 'true' && runner.os == 'macOS' && - ((github.ref_protected && github.event_name != 'pull_request') || - (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) - run: brew install awscli - - - name: Configure R2 binary cache for trusted builds - if: vars.VCPKG_R2_ENABLED == 'true' && github.ref_protected && github.event_name != 'pull_request' - env: - R2_CACHE_MODE: readwrite - R2_ENDPOINT_URL: ${{ vars.VCPKG_R2_ENDPOINT_URL }} - R2_BUCKET: ${{ vars.VCPKG_R2_BUCKET }} - R2_ACCESS_KEY_ID: ${{ secrets.VCPKG_R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.VCPKG_R2_SECRET_ACCESS_KEY }} - run: python scripts/vcpkg/configure_r2_cache.py - - - name: Configure R2 binary cache for same-repository PRs - if: >- - vars.VCPKG_R2_ENABLED == 'true' && github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository - env: - # Read only, with the read-only keys: code still under review must - # not put packages in front of a release build. - R2_CACHE_MODE: read - R2_ENDPOINT_URL: ${{ vars.VCPKG_R2_ENDPOINT_URL }} - R2_BUCKET: ${{ vars.VCPKG_R2_BUCKET }} - R2_ACCESS_KEY_ID: ${{ secrets.VCPKG_R2_READ_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.VCPKG_R2_READ_SECRET_ACCESS_KEY }} - run: python scripts/vcpkg/configure_r2_cache.py - - - name: Cache Rust Dependencies - uses: actions/cache@v6 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + r2-cache-mode: ${{ needs.setup.outputs.r2_cache_mode }} + r2-endpoint-url: ${{ vars.VCPKG_R2_ENDPOINT_URL }} + r2-bucket: ${{ vars.VCPKG_R2_BUCKET }} + # The writer keys only where the cache is written; a reader's mode + # alone would not stop code that finds writer keys from using them. + r2-access-key-id: ${{ needs.setup.outputs.r2_cache_mode == 'readwrite' && secrets.VCPKG_R2_ACCESS_KEY_ID || secrets.VCPKG_R2_READ_ACCESS_KEY_ID }} + r2-secret-access-key: ${{ needs.setup.outputs.r2_cache_mode == 'readwrite' && secrets.VCPKG_R2_SECRET_ACCESS_KEY || secrets.VCPKG_R2_READ_SECRET_ACCESS_KEY }} - name: Configure id: configure @@ -431,15 +384,15 @@ jobs: if $BUILD_TESTS then # Now build the tests. We want to build them in a separate step before running them. - cmake --build "$BUILD_DIRECTORY" --config "$cmake_config" --parallel $(nproc) + cmake --build "$BUILD_DIRECTORY" --config "$cmake_config" --parallel "$(nproc)" # Now that we've built the tests, we can run them to validate the build. # If the tests fail, their output will be visible in the logs due to # CTEST_OUTPUT_ON_FAILURE above, and the build step will be marked as failed. - cmake --build "$BUILD_DIRECTORY" --config "$cmake_config" --target BUILD_AND_RUN_TESTS --parallel $(nproc) + cmake --build "$BUILD_DIRECTORY" --config "$cmake_config" --target BUILD_AND_RUN_TESTS --parallel "$(nproc)" elif $AL_BUILD_VIEWER then - cmake --build "$BUILD_DIRECTORY" --config "$cmake_config" --parallel $(nproc) + cmake --build "$BUILD_DIRECTORY" --config "$cmake_config" --parallel "$(nproc)" else echo "::notice::Skipping build due to both viewer and tests being disabled" fi @@ -466,17 +419,10 @@ jobs: fi # The package: the install tree on Windows, which the signing job - # packages with Velopack; the CPack archive elsewhere. The names - # the packaging steps need come from the configure. + # packages with Velopack from it and its package.env metadata; the + # CPack archive elsewhere. if $AL_BUILD_VIEWER then - # The job's outputs come from whichever row sets them last. - # Keep the shared Velopack outputs on x64; Windows packaging - # reads per-architecture metadata artifacts below. - if [[ "$RUNNER_OS" != "Windows" || "$BUILD_ARCH" == "x64" ]] - then - cat "$BUILD_DIRECTORY/newview/package.env" >> "$GITHUB_OUTPUT" - fi case "$RUNNER_OS" in Windows) cmake --install "$BUILD_DIRECTORY" --config "$cmake_config" --prefix "$RUNNER_TEMP/app" @@ -508,7 +454,9 @@ jobs: with: name: "${{ steps.build.outputs.artifact }}-app" if-no-files-found: error - compression-level: 9 + # The Windows install tree is uncompressed; the .tar.xz and .dmg + # already are. + compression-level: ${{ runner.os == 'Windows' && 9 || 0 }} path: | ${{ steps.build.outputs.viewer_app }} @@ -605,17 +553,21 @@ jobs: sign-and-package-windows: needs: [setup, build] + timeout-minutes: 30 strategy: matrix: arch: [x64, arm64] runs-on: windows-2025-vs2026 steps: + # The app and its metadata come from the build's artifacts; the tree + # contributes only the Velopack tool manifest. - name: Checkout code uses: actions/checkout@v7 with: persist-credentials: false - submodules: recursive ref: ${{ github.event.pull_request.head.sha || github.sha }} + sparse-checkout: /dotnet-tools.json + sparse-checkout-cone-mode: false - name: Setup .NET for Velopack uses: actions/setup-dotnet@v6 @@ -765,8 +717,10 @@ jobs: release: needs: [setup, build, sign-and-package-windows] runs-on: ubuntu-latest + timeout-minutes: 30 if: needs.setup.outputs.release_run permissions: + # Creates the release, generates its notes and uploads its assets. contents: write steps: - uses: actions/download-artifact@v8 @@ -792,7 +746,9 @@ jobs: with: # name the release page for the branch. We want channel and version. name: "${{ needs.setup.outputs.viewer_channel }} ${{ needs.build.outputs.viewer_version }}" - body: | + # The tag's pull request notes after "relnotes:", if any, ahead of + # the generated notes. + body: ${{ needs.setup.outputs.relnotes }} prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} @@ -804,5 +760,7 @@ jobs: Windows*-releases/* - name: post release URL + env: + RELEASE_URL: ${{ steps.release.outputs.url }} run: | - echo "::notice::Release ${{ steps.release.outputs.url }}" + echo "::notice::Release $RELEASE_URL" diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml index 68ca8244c0..bf0997b11b 100644 --- a/.github/workflows/check-pr.yaml +++ b/.github/workflows/check-pr.yaml @@ -4,12 +4,13 @@ on: pull_request: types: [opened, edited, reopened, synchronize] -permissions: - contents: read +# The check reads the event payload; it makes no API calls. +permissions: {} jobs: check-description: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check PR description uses: actions/github-script@v9 diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 0000000000..3b07dd15c0 --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,142 @@ +name: CodeQL + +on: + push: + branches: ["develop", "main"] + pull_request: + branches: ["develop", "main"] + # Nothing CodeQL reads: skins, settings, shaders, docs. + paths-ignore: + - "**/*.md" + - "doc/**" + - "indra/newview/app_settings/**" + - "indra/newview/character/**" + - "indra/newview/skins/**" + schedule: + # Weekly, so new queries reach code nobody has touched since. + - cron: "17 3 * * 1" + workflow_dispatch: + +permissions: + contents: read + +# A pull request analyses only its latest push. A branch keeps one analysis +# running and the newest waiting, so a busy develop still finishes some. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + analyze: + name: Analyze (${{ matrix.name }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: ${{ matrix.build-mode == 'manual' && 360 || 30 }} + permissions: + # Reads the run the results belong to. + actions: read + contents: read + # Uploads the results to code scanning. + security-events: write + strategy: + fail-fast: false + matrix: + include: + - name: actions + language: actions + build-mode: none + runner: ubuntu-latest + category: /language:actions + - name: python + language: python + build-mode: none + runner: ubuntu-latest + category: /language:python + # C/C++ is traced through a real build, so the analysis sees the + # code as compiled, every vcpkg header resolved and every platform + # #if decided. One row per platform layer. Linux keeps the category + # default setup used, so its alerts and their dismissals carry over. + # macOS adds little: CodeQL does not read Objective-C++. + - name: c-cpp, Linux + language: c-cpp + build-mode: manual + runner: ubuntu-26.04 + preset: ninja-os-mold + triplet: x64-linux-alchemy-avx2-release + category: /language:c-cpp + - name: c-cpp, Windows + language: c-cpp + build-mode: manual + runner: windows-2025-vs2026 + preset: vs2026-os-x64 + triplet: x64-windows-alchemy-avx2-release + category: /language:c-cpp/os:windows + env: + VCPKG_ROOT: ${{ github.workspace }}/vcpkg + VCPKG_BINARY_SOURCES: "clear;default,readwrite" + # MSBuild keeps its worker nodes alive between builds. One started by + # the configure, before tracing began, would compile untraced. + MSBUILDDISABLENODEREUSE: 1 + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + # The build needs the submodules, and vcpkg its full history to + # resolve the versions the manifest pins. + fetch-depth: ${{ matrix.build-mode == 'manual' && '0' || '1' }} + persist-credentials: false + submodules: ${{ matrix.build-mode == 'manual' && 'recursive' || 'false' }} + + - name: Set up the build + if: matrix.build-mode == 'manual' + uses: ./.github/actions/setup-build + with: + # Read only, with the read-only keys: an analysis has nothing to add + # to the cache. + r2-cache-mode: >- + ${{ vars.VCPKG_R2_ENABLED == 'true' && github.actor != 'dependabot[bot]' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && + 'read' || '' }} + r2-endpoint-url: ${{ vars.VCPKG_R2_ENDPOINT_URL }} + r2-bucket: ${{ vars.VCPKG_R2_BUCKET }} + r2-access-key-id: ${{ secrets.VCPKG_R2_READ_ACCESS_KEY_ID }} + r2-secret-access-key: ${{ secrets.VCPKG_R2_READ_SECRET_ACCESS_KEY }} + + # The configure installs the vcpkg ports, so it runs before tracing + # starts: the database holds the viewer, not its dependencies. The + # build tree sits outside the checkout, which keeps the vcpkg headers + # out of the source root and so out of the alerts. Precompiled headers + # are off because the extractor reads each source file whole. + - name: Configure + if: matrix.build-mode == 'manual' + shell: bash + env: + PRESET: ${{ matrix.preset }} + TRIPLET: ${{ matrix.triplet }} + run: | + set -x + cmake -S indra --preset "$PRESET" -B "$RUNNER_TEMP/codeql-build" \ + -DVCPKG_TARGET_TRIPLET="$TRIPLET" \ + -DAL_BUILD_TESTS:BOOL=OFF \ + -DAL_BUILD_PACKAGE:BOOL=OFF \ + -DAL_USE_VELOPACK:BOOL=ON \ + -DAL_USE_LTO:BOOL=OFF \ + -DAL_USE_TRACY:BOOL=OFF \ + -DAL_USE_PRECOMPILED_HEADERS:BOOL=OFF + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + config-file: ./.github/codeql/codeql-config.yaml + + # The runner's own shell on each platform: pwsh on Windows, where the + # tracer follows MSBuild's processes. + - name: Build + if: matrix.build-mode == 'manual' + run: cmake --build "${{ runner.temp }}/codeql-build" --config Release --parallel + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v4 + with: + category: ${{ matrix.category }} diff --git a/.github/workflows/label.yaml b/.github/workflows/label.yaml index bb3a9031e7..d08819da60 100644 --- a/.github/workflows/label.yaml +++ b/.github/workflows/label.yaml @@ -2,8 +2,13 @@ name: Pull Request Labeler on: - pull_request_target +permissions: {} + jobs: triage: + # Applying labels needs pull-requests: write. Creating one would need + # issues: write as well, so every label in labeler.yaml must already + # exist in the repository. permissions: contents: read pull-requests: write diff --git a/.github/workflows/lint-workflows.yaml b/.github/workflows/lint-workflows.yaml new file mode 100644 index 0000000000..ad99e227c1 --- /dev/null +++ b/.github/workflows/lint-workflows.yaml @@ -0,0 +1,38 @@ +name: Lint workflows + +on: + pull_request: + paths: [".github/**"] + push: + branches: ["develop"] + paths: [".github/**"] + +permissions: + contents: read + +jobs: + actionlint: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup python + uses: actions/setup-python@v7 + with: + python-version: "3.14" + + - name: Install actionlint + run: pip install actionlint-py==1.7.12.25 + + # Syntax, expressions, matrix and runner labels, the inputs of the + # actions called, and the run scripts through shellcheck. + - name: Run actionlint + env: + # shellcheck's errors and warnings fail the check; its info and + # style notes do not. + SHELLCHECK_OPTS: --severity=warning + run: actionlint -color diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml index 41a215033a..1e36d3a9c6 100644 --- a/.github/workflows/tag-release.yaml +++ b/.github/workflows/tag-release.yaml @@ -26,19 +26,22 @@ on: description: "Override the tag name (optional). If the tag already exists, a numeric suffix is appended." required: false +# The tag is created with a personal access token, not the GITHUB_TOKEN. +permissions: {} + jobs: tag-release: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - name: Setup Env Vars - run: | - CHANNEL="${{ inputs.channel }}" - echo VIEWER_CHANNEL="Alchemy_${CHANNEL:-Develop}" >> ${GITHUB_ENV} - NIGHTLY_DATE=$(date --rfc-3339=date) - echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV} - echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV} - name: Create Tag uses: actions/github-script@v9 + env: + # Inputs reach the script through the environment, never pasted + # into it, so no input can become code. + CHANNEL: ${{ inputs.channel }} + PROJECT: ${{ inputs.project }} + TAG_OVERRIDE: ${{ inputs.tag_override }} with: # use a real access token instead of GITHUB_TOKEN default. # required so that the results of this tag creation can trigger the build workflow @@ -47,8 +50,11 @@ jobs: # this token will need to be renewed anually in January github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }} script: | - const override = `${{ inputs.tag_override }}`.trim(); - const baseTag = override || `${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}`; + // A scheduled run has no inputs: a Develop build tagged with the date. + const channel = process.env.CHANNEL || 'Develop'; + const suffix = process.env.PROJECT || new Date().toISOString().slice(0, 10); + const override = (process.env.TAG_OVERRIDE || '').trim(); + const baseTag = override || `Alchemy_${channel}#${context.sha.slice(0, 8)}-${suffix}`; // Try the base tag first, then append -2, -3, etc. if it already exists let tag = baseTag; From 94625d7cf6bb203c996a387a781eddbbdce15f4a Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 3 Oct 2026 21:14:41 -0400 Subject: [PATCH 4/4] The CodeQL build keeps precompiled headers, as the build does Without them the tree does not build: llstring.h calls std::strlen with no of its own, which the precompiled header always supplied, and the Linux analysis stopped at httpstats.cpp. Turning them off was a precaution only; the extractor reads the forced-include header as text whether or not the compiler has a precompiled copy of it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/codeql.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 3b07dd15c0..71894f9940 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -105,7 +105,8 @@ jobs: # starts: the database holds the viewer, not its dependencies. The # build tree sits outside the checkout, which keeps the vcpkg headers # out of the source root and so out of the alerts. Precompiled headers - # are off because the extractor reads each source file whole. + # stay on, as in the build: the extractor reads the forced-include + # header as text, and the tree does not build without them. - name: Configure if: matrix.build-mode == 'manual' shell: bash @@ -120,8 +121,7 @@ jobs: -DAL_BUILD_PACKAGE:BOOL=OFF \ -DAL_USE_VELOPACK:BOOL=ON \ -DAL_USE_LTO:BOOL=OFF \ - -DAL_USE_TRACY:BOOL=OFF \ - -DAL_USE_PRECOMPILED_HEADERS:BOOL=OFF + -DAL_USE_TRACY:BOOL=OFF - name: Initialize CodeQL uses: github/codeql-action/init@v4