Skip to content

fix: resolve inheritance hiding and covariant array warnings #23224

fix: resolve inheritance hiding and covariant array warnings

fix: resolve inheritance hiding and covariant array warnings #23224

name: Unity Cloud Build
on:
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
- labeled
- unlabeled
merge_group: {}
push:
branches:
- dev
workflow_dispatch:
inputs:
profile:
description: 'Select level of profiling'
required: true
default: 'none'
type: choice
options:
- none
- profile
- deep
compression:
description: 'Level of compression'
required: true
default: 'LZ4HC'
type: choice
options:
- None
- LZ4
- LZ4HC
clean_build:
description: 'Clean Build'
required: false
default: false
type: boolean
cache_strategy:
description: 'Select cache strategy'
required: true
default: 'library'
type: choice
options:
- none
- library
- workspace
- inherit
sentry_enabled:
description: 'Enable Sentry'
required: false
type: boolean
default: false
script_debugging:
description: 'Enable Script Debugging (forces Development build)'
required: false
type: boolean
default: false
delta_threshold_macos_mb:
description: 'Warn if macOS growth > this MB (0 = off)'
required: false
default: '0'
type: string
delta_threshold_windows_mb:
description: 'Warn if Windows growth > this MB (0 = off)'
required: false
default: '0'
type: string
enforce_delta:
description: 'Build fails when Δ MB exceeds threshold'
required: false
default: false
type: boolean
enforce_cap:
description: 'Build fails when size MB exceeds cap'
required: false
default: false
type: boolean
force_sign:
description: 'Run Windows code-signing steps for testing (non-blocking)'
required: false
default: false
type: boolean
platforms:
description: 'Which platforms to build'
required: true
default: 'both'
type: choice
options:
- both
- windows-only
- macos-only
workflow_call:
inputs:
profile:
required: true
type: string
default: 'none'
compression:
required: false
type: string
default: 'LZ4HC'
clean_build:
required: true
default: true
type: boolean
cache_strategy:
required: true
default: 'library'
type: string
version:
required: true
type: string
sentry_enabled:
required: false
type: boolean
default: false
script_debugging:
required: false
type: boolean
default: false
is_release_build:
type: boolean
required: false
default: false
install_source:
required: false
default: 'launcher'
type: string
tag_version:
type: string
required: false
delta_threshold_macos_mb:
description: 'Warn if macOS growth > this MB (0 = off)'
required: false
default: '0'
type: string
delta_threshold_windows_mb:
description: 'Warn if Windows growth > this MB (0 = off)'
required: false
default: '0'
type: string
enforce_delta:
description: 'Build fails when Δ MB exceeds threshold'
required: false
default: false
type: boolean
enforce_cap:
description: 'Build fails when size MB exceeds cap'
required: false
default: false
type: boolean
secrets:
ES_USERNAME:
description: 'SSL.com username for Windows code signing'
required: false
ES_PASSWORD:
description: 'SSL.com password for Windows code signing'
required: false
WINDOWS_CREDENTIAL_ID_SIGNER:
description: 'SSL.com credential ID for Windows code signing'
required: false
ES_TOTP_SECRET:
description: 'SSL.com TOTP secret for Windows code signing'
required: false
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ inputs.install_source }}
# Only cancel an in-flight build when the new event will actually produce a new useful build.
# Otherwise an incidental pull_request event (e.g. converted_to_draft race, non-override label change)
# would cancel a real in-progress build and let the new run skip prebuild/build, leaving the
# Build Gate falsely green.
cancel-in-progress: >-
${{
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
github.event_name == 'merge_group' ||
(
github.event_name == 'pull_request' &&
(
github.event.action == 'opened' ||
github.event.action == 'reopened' ||
github.event.action == 'synchronize' ||
github.event.action == 'ready_for_review' ||
(
(github.event.action == 'labeled' || github.event.action == 'unlabeled') &&
(
github.event.label.name == 'force-build' ||
github.event.label.name == 'clean-build' ||
github.event.label.name == 'windows-only' ||
github.event.label.name == 'macos-only'
)
)
)
)
}}
jobs:
prebuild:
name: Prebuild
runs-on: ubuntu-latest
timeout-minutes: 10
# Skip when PR has 'perf_test' label (only performance tests should run)
if: |
!contains(github.event.pull_request.labels.*.name, 'perf_test') && (
(github.ref == 'refs/heads/dev') ||
(github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch') ||
(
github.event_name == 'pull_request' &&
(
(
(github.event.action == 'opened' ||
github.event.action == 'reopened' ||
github.event.action == 'synchronize') &&
(
!github.event.pull_request.draft ||
contains(github.event.pull_request.labels.*.name, 'force-build') ||
contains(github.event.pull_request.labels.*.name, 'clean-build')
)
) ||
github.event.action == 'ready_for_review' ||
(
(github.event.action == 'labeled' || github.event.action == 'unlabeled') &&
(
github.event.label.name == 'force-build' ||
github.event.label.name == 'clean-build' ||
github.event.label.name == 'windows-only' ||
github.event.label.name == 'macos-only'
)
)
)
)
)
outputs:
should_build: ${{ steps.decide.outputs.should_build }}
commit_sha: ${{ steps.get_commit_sha.outputs.commit_sha }}
options: ${{ steps.get_options.outputs.options }}
version: ${{ github.event.inputs.version || inputs.version || steps.get_version.outputs.full_version }}
sentry_environment: ${{ steps.get_sentry.outputs.environment }}
sentry_upload_symbols: ${{ steps.get_sentry.outputs.upload_symbols }}
sentry_enabled: ${{ steps.get_sentry.outputs.sentry_enabled }}
clean_build: ${{ steps.set_defaults.outputs.clean_build }}
cache_strategy: ${{ steps.set_defaults.outputs.cache_strategy }}
install_source: ${{ steps.set_defaults.outputs.install_source }}
targets: ${{ steps.get_targets.outputs.targets }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Detect changes under Explorer/**
id: changed_explorer
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
Explorer/**
- name: Decide whether to build
id: decide
shell: bash
run: |
set -euo pipefail
should_build=false
# Non-PR triggers that reached prebuild => build
echo "[check non-PR] event_name='${{ github.event_name }}' != 'pull_request'"
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "[non-PR] TRUE -> should_build=true"
should_build=true
fi
if [ "$should_build" = "false" ]; then
labels="${{ join(github.event.pull_request.labels.*.name, ' ') }}"
echo "labels='$labels'"
has_override=false
echo "[check override] searching for 'force-build|clean-build' in labels"
echo "$labels" | grep -qwE 'force-build|clean-build' && has_override=true
echo "has_override='$has_override'"
if [ "$has_override" = "true" ]; then
echo "[override] TRUE -> should_build=true"
should_build=true
fi
fi
if [ "$should_build" = "false" ]; then
# If still draft and no override => do not build
is_draft="${{ github.event.pull_request.draft }}"
echo "[check draft] is_draft='$is_draft'"
if [ "$is_draft" = "true" ]; then
echo "[draft] TRUE -> should_build=false"
else
explorer_changed="${{ steps.changed_explorer.outputs.any_changed }}"
echo "[check explorer_changed] explorer_changed='$explorer_changed'"
if [ "$explorer_changed" = "true" ]; then
echo "[explorer_changed] TRUE -> should_build=true"
should_build=true
else
echo "[explorer_changed] FALSE -> should_build=false"
fi
fi
fi
echo "should_build=$should_build" >> "$GITHUB_OUTPUT"
echo "Final decision: should_build=$should_build"
- name: Skip build and test checks
if: steps.decide.outputs.should_build == 'false' && github.event_name == 'pull_request'
uses: actions/github-script@v8
with:
script: |
const sha = context.payload.pull_request.head.sha;
const checks = [
'Build (macos)',
'Build (windows64)',
'Test (editmode)',
'Test (playmode)'
];
for (const check of checks) {
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha,
state: 'success',
context: check,
description: 'Skipped — no Explorer/ changes detected'
});
}
console.log(`Posted success statuses for: ${checks.join(', ')}`);
- name: Get commit SHA
if: steps.decide.outputs.should_build == 'true'
id: get_commit_sha
run: |
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "commit_sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
else
echo "commit_sha=$GITHUB_SHA" >> $GITHUB_OUTPUT
fi
- name: Debug Commit SHA
if: steps.decide.outputs.should_build == 'true'
run: |
echo "Ref from Pull Request: ${{ github.event.pull_request.head.sha }}"
echo "Full Commit from git rev-parse: $(git rev-parse $GITHUB_SHA)"
echo "HEAD Commit: $(git rev-parse HEAD)"
- name: Get version
id: get_version
if: steps.decide.outputs.should_build == 'true' && github.event.inputs.version == '' && inputs.version == ''
uses: ./.github/actions/version
with:
commit_sha: ${{ steps.get_commit_sha.outputs.commit_sha }}
- name: Get Sentry parameters
if: steps.decide.outputs.should_build == 'true'
id: get_sentry
run: |
#!/bin/bash
sentry_enabled="${{ github.event.inputs.sentry_enabled || inputs.sentry_enabled }}"
if [[ "$sentry_enabled" != "true" && "${{ github.event_name }}" == "pull_request" ]]; then
echo "Checking PR labels: ${{ join(github.event.pull_request.labels.*.name, ', ') }}"
sentry_enabled=$(echo "${{ join(github.event.pull_request.labels.*.name, ' ') }}" | grep -qw 'enable-sentry' && echo true || echo false)
fi
if [[ "$sentry_enabled" == "true" ]]; then
if [[ "${{ inputs.is_release_build }}" == "true" ]]; then
echo "environment=production" >> "$GITHUB_OUTPUT"
else
echo "environment=development" >> "$GITHUB_OUTPUT"
fi
echo "upload_symbols=true" >> "$GITHUB_OUTPUT"
echo "sentry_enabled=true" >> "$GITHUB_OUTPUT"
else
echo "environment=" >> "$GITHUB_OUTPUT"
echo "upload_symbols=false" >> "$GITHUB_OUTPUT"
echo "sentry_enabled=false" >> "$GITHUB_OUTPUT"
fi
- name: Set default values
if: steps.decide.outputs.should_build == 'true'
id: set_defaults
run: |
# Clean build logic
if [ "${{ github.event.inputs.clean_build }}" ]; then
clean_build=${{ github.event.inputs.clean_build }}
elif [ "${{ inputs.clean_build }}" ]; then
clean_build=${{ inputs.clean_build }}
else
clean_build=false
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "Checking PR labels: ${{ join(github.event.pull_request.labels.*.name, ', ') }}"
# Proper label check with spaces
clean_build=$(echo "${{ join(github.event.pull_request.labels.*.name, ' ') }}" | grep -qw 'clean-build' && echo true || echo false)
fi
fi
echo "Set clean_build to: $clean_build"
echo "clean_build=${clean_build}" >> $GITHUB_OUTPUT
if [ "${{ github.event.inputs.cache_strategy }}" ]; then
cache_strategy=${{ github.event.inputs.cache_strategy }}
elif [ "${{ inputs.cache_strategy }}" ]; then
cache_strategy=${{ inputs.cache_strategy }}
else
cache_strategy='library'
fi
echo "Set cache_strategy to: $cache_strategy"
echo "cache_strategy=${cache_strategy}" >> $GITHUB_OUTPUT
if [ "${{ github.event.inputs.install_source }}" ]; then
install_source=${{ github.event.inputs.install_source }}
elif [ "${{ inputs.install_source }}" ]; then
install_source=${{ inputs.install_source }}
else
install_source='launcher'
fi
echo "Set install_source to: $install_source"
echo "install_source=${install_source}" >> $GITHUB_OUTPUT
- name: Get BuildOptions
if: steps.decide.outputs.should_build == 'true'
id: get_options
run: |
#!/bin/bash
#options=("DetailedBuildReport")
options=()
# compression - default is LZ4HC
compression="${{ github.event.inputs.compression || inputs.compression || 'LZ4HC' }}"
if [[ "$compression" == "LZ4" ]]; then
options+=("CompressWithLz4")
elif [[ "$compression" == "LZ4HC" ]]; then
options+=("CompressWithLz4HC")
fi
# input.profile
profile="${{ github.event.inputs.profile || inputs.profile }}"
if [[ "$profile" == "profile" || "$profile" == "deep" ]]; then
options+=("Development")
options+=("ConnectWithProfiler")
fi
if [[ "$profile" == "deep" ]]; then
options+=("EnableDeepProfilingSupport")
fi
# Script Debugging: input toggle (workflow_dispatch / workflow_call) or 'script-debugging' PR label.
# AllowDebugging requires a Development build to be honored by Unity.
script_debugging="${{ github.event.inputs.script_debugging || inputs.script_debugging }}"
if [[ "$script_debugging" != "true" && "${{ github.event_name }}" == "pull_request" ]]; then
script_debugging=$(echo "${{ join(github.event.pull_request.labels.*.name, ' ') }}" | grep -qw 'script-debugging' && echo true || echo false)
fi
if [[ "$script_debugging" == "true" ]]; then
# Add Development only if not already added by profile mode
if [[ ! " ${options[*]} " =~ " Development " ]]; then
options+=("Development")
fi
options+=("AllowDebugging")
fi
# Write the array as a comma-separated string
# Set the Internal Field Separator to comma
IFS=,
echo "options=${options[*]}" | tee -a "$GITHUB_OUTPUT"
- name: Determine build targets
if: steps.decide.outputs.should_build == 'true'
id: get_targets
shell: bash
run: |
set -euo pipefail
# platform_build_mode: windows | macos | both
platform_build_mode=both
dispatch_platforms="${{ github.event.inputs.platforms }}"
echo "Dispatch platforms: '$dispatch_platforms'"
if [ "$dispatch_platforms" = "windows-only" ]; then
platform_build_mode=windows
elif [ "$dispatch_platforms" = "macos-only" ]; then
platform_build_mode=macos
else
labels="${{ join(github.event.pull_request.labels.*.name, ' ') }}"
echo "Labels: '$labels'"
label_match_count=0
if echo "$labels" | grep -qw 'windows-only'; then
platform_build_mode=windows
label_match_count=$((label_match_count + 1))
fi
if echo "$labels" | grep -qw 'macos-only'; then
platform_build_mode=macos
label_match_count=$((label_match_count + 1))
fi
if [ "$label_match_count" -gt 1 ]; then
echo "::error::Both 'windows-only' and 'macos-only' labels are applied. Remove one — they are mutually exclusive."
exit 1
fi
fi
echo "Platform build mode: $platform_build_mode"
case "$platform_build_mode" in
windows) targets='["windows64"]' ;;
macos) targets='["macos"]' ;;
both) targets='["windows64","macos"]' ;;
*) echo "::error::Unknown platform_build_mode '$platform_build_mode'"; exit 1 ;;
esac
echo "Targets: $targets"
echo "targets=$targets" >> "$GITHUB_OUTPUT"
build:
name: Build
runs-on: ubuntu-latest
needs: prebuild
if: needs.prebuild.outputs.should_build == 'true'
# Safety ceiling around the 450m retry budget + surrounding steps.
timeout-minutes: 510
strategy:
fail-fast: false
matrix:
target: ${{ fromJSON(needs.prebuild.outputs.targets) }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}
- name: Setup python
uses: actions/setup-python@v6
with:
python-version: 3.12.3
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r scripts/cloudbuild/requirements.txt
- name: Execute Unity Cloud build
uses: nick-fields/retry@v3
with:
# Wraps QUEUE_TIMEOUT (240m) + BUILD_TIMEOUT (180m) + buffer.
timeout_minutes: 450
max_attempts: 2
retry_on_exit_code: 99
retry_wait_seconds: 30
on_retry_command: |
echo "::warning::🔁 Unity Cloud Build retry triggered at $(date '+%Y-%m-%d %H:%M:%S'). The next attempt will reattach to the persisted build if it is still in flight."
command: |
echo "🔧 Starting Unity Cloud Build attempt at $(date '+%Y-%m-%d %H:%M:%S')"
python -u scripts/cloudbuild/build.py
env:
API_KEY: ${{ secrets.UNITY_CLOUD_API_KEY }}
ORG_ID: ${{ secrets.UNITY_CLOUD_ORG_ID }}
PROJECT_ID: ${{ secrets.UNITY_CLOUD_PROJECT_ID }}
POLL_TIME: 60
QUEUE_POLL_TIME: 120
STALE_POLL_THRESHOLD: 600
QUEUE_TIMEOUT: 14400
BUILD_TIMEOUT: 10800
TARGET: t_${{ matrix.target }}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
COMMIT_SHA: ${{ needs.prebuild.outputs.commit_sha }}
BUILD_OPTIONS: ${{ needs.prebuild.outputs.options }}
CLEAN_BUILD: ${{ needs.prebuild.outputs.clean_build }}
CACHE_STRATEGY: ${{ needs.prebuild.outputs.cache_strategy }}
IS_RELEASE_BUILD: ${{ inputs.is_release_build }}
TAG_VERSION: ${{ inputs.tag_version }}
#Possible values: { none, library, workspace, inherit }
# Any ENV variables starting with "PARAM_" will be passed to Unity without the prefix
# (The "PARAM_" prefix exists to allow any future values config-free)
# e.g.: PARAM_ALLOW_DEBUG -> In Unity will be available as "ALLOW_DEBUG"
# e.g.: Editor.CloudBuild.Parameters["ALLOW_DEBUG"]
PARAM_BUILD_VERSION: ${{ needs.prebuild.outputs.version }}
# solves https://github.com/decentraland/unity-explorer/issues/6789
# use pre-defined sentry dsn, but in case it is not defined, use the development sentry project
PARAM_SENTRY_DSN: ${{ inputs.is_release_build && secrets.SENTRY_DSN || secrets.SENTRY_DSN_DEV }}
PARAM_SENTRY_ENVIRONMENT: ${{ needs.prebuild.outputs.sentry_environment }}
PARAM_SENTRY_ENABLED: ${{ needs.prebuild.outputs.sentry_enabled }}
PARAM_SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY }}
PARAM_INSTALL_SOURCE: ${{ needs.prebuild.outputs.install_source }}
PARAM_IS_RELEASE_BUILD: ${{ inputs.is_release_build }}
PARAM_UNITY_EXTRA_PARAMS: '-disable-assembly-updater'
- name: Locate Windows main executable
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
run: |
set -euo pipefail
MAIN_EXE=$(find build -maxdepth 1 -name '*.exe' ! -name 'UnityCrashHandler64.exe' | head -n1)
if [[ -z "$MAIN_EXE" ]]; then
echo "::error::No main .exe found in build/"
exit 1
fi
MAIN_EXE_ABS=$(readlink -f "$MAIN_EXE")
echo "MAIN_EXE=$MAIN_EXE_ABS" >> "$GITHUB_ENV"
echo "Located: $MAIN_EXE_ABS"
- name: Sign Windows executable
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
continue-on-error: true
uses: sslcom/esigner-codesign@b7f8ff36fc0de8690fbbab8e5b4421d29802f747 # develop @ 2025-06-23, pinned for supply-chain safety
with:
command: sign
username: ${{ secrets.ES_USERNAME }}
password: ${{ secrets.ES_PASSWORD }}
credential_id: ${{ secrets.WINDOWS_CREDENTIAL_ID_SIGNER }}
totp_secret: ${{ secrets.ES_TOTP_SECRET }}
file_path: ${{ env.MAIN_EXE }}
override: 'true'
malware_block: 'false'
environment_name: PROD
- name: Verify Windows signature
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
continue-on-error: true
run: |
set -euo pipefail
sudo apt-get install -y -qq osslsigncode > /dev/null 2>&1
echo "Verifying signature on $MAIN_EXE ..."
osslsigncode verify -in "$MAIN_EXE"
echo "::notice::Signature verified for $MAIN_EXE"
- name: 'Tar artifact to maintain original permissions'
if: matrix.target == 'macos'
run: tar --exclude='build/Decentraland_BackUpThisFolder_ButDontShipItWithYourGame' -cvf build.tar build
- name: Set artifact name
id: set_artifact_name
run: |
if [ "${{ needs.prebuild.outputs.install_source }}" == "launcher" ]; then
echo "artifact_name=Decentraland_${{ matrix.target }}" >> $GITHUB_ENV
else
echo "artifact_name=Decentraland_${{ matrix.target }}_${{ needs.prebuild.outputs.install_source }}" >> $GITHUB_ENV
fi
- name: Upload artifact for macOS
id: upload-macos-artifact
if: matrix.target == 'macos'
uses: actions/upload-artifact@v6
with:
name: ${{ env.artifact_name }}
path: build.tar
if-no-files-found: error
- name: Upload artifact for Windows
id: upload-windows-artifact
if: matrix.target == 'windows64'
uses: actions/upload-artifact@v6
with:
name: ${{ env.artifact_name }}
path: |
build
!build/**/*_BackUpThisFolder_ButDontShipItWithYourGame
!build/**/*_BurstDebugInformation_DoNotShip
!build/**/*.pdb
if-no-files-found: error
- name: Compress the build folder to upload it to S3
run: |
mkdir upload_to_s3/ && \
if [ "${{ matrix.target }}" == "macos" ]; then
zip -r upload_to_s3/${{ env.artifact_name }}.zip build.tar
elif [ "${{ matrix.target }}" == "windows64" ]; then
cd build
zip -r ../upload_to_s3/${{ env.artifact_name }}.zip . -x "*_BackUpThisFolder_ButDontShipItWithYourGame**" -x "*_BurstDebugInformation_DoNotShip**" -x "*.pdb"
fi
- name: Install jq
run: sudo apt-get update -y && sudo apt-get install -y jq
- name: Enforce artifact size budget
id: size_check
env:
# Per-target absolute caps (MB)
MAX_MACOS_MB: 450
MAX_WINDOWS64_MB: 360
# Per-platform delta thresholds (MB); 0 = disabled
DELTA_MACOS_MB: ${{ github.event.inputs.delta_threshold_macos_mb || inputs.delta_threshold_macos_mb || '0' }}
DELTA_WINDOWS_MB: ${{ github.event.inputs.delta_threshold_windows_mb || inputs.delta_threshold_windows_mb || '0' }}
# Enforcement toggles (when true => FAIL build)
ENFORCE_DELTA: ${{ github.event.inputs.enforce_delta || inputs.enforce_delta || false }}
ENFORCE_CAP: ${{ github.event.inputs.enforce_cap || inputs.enforce_cap || false }}
REPO_FULL: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
FILE="upload_to_s3/${{ env.artifact_name }}.zip"
if [ ! -f "$FILE" ]; then
echo "::error::Artifact zip not found: $FILE"
exit 1
fi
# Current size in bytes
BYTES=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE")
# Convert to MB (decimal, 1 MB = 1,000,000) and MiB (binary, 1 MiB = 1,048,576)
MB_DEC=$(awk -v b="$BYTES" 'BEGIN { printf("%.1f", b/1000000) }')
MIB_BIN=$(awk -v b="$BYTES" 'BEGIN { printf("%.1f", b/1048576) }')
# Per-platform caps and delta thresholds
case "${{ matrix.target }}" in
macos) MAX=$MAX_MACOS_MB ; MAX_DELTA_MB=$DELTA_MACOS_MB ;;
windows64) MAX=$MAX_WINDOWS64_MB; MAX_DELTA_MB=$DELTA_WINDOWS_MB ;;
*) MAX=0; MAX_DELTA_MB=0 ;;
esac
echo "Checking ${{ matrix.target }} → ${MB_DEC} MB / ${MIB_BIN} MiB (cap ${MAX} MB, delta threshold ${MAX_DELTA_MB} MB)"
# Lookup latest release tag from GitHub API
API_LATEST="https://api.github.com/repos/${REPO_FULL}/releases/latest"
RELEASE_JSON=$(curl -sSL -H "Authorization: Bearer $GITHUB_TOKEN" "$API_LATEST")
RELEASE_TAG=$(echo "$RELEASE_JSON" | jq -r '.tag_name // empty')
LATEST_FOUND=false
if [ -n "$RELEASE_TAG" ]; then
REPO_NAME="${REPO_FULL#*/}"
LATEST_NAME="${{ env.artifact_name }}.zip"
S3_URL="https://explorer-artifacts.decentraland.org/@dcl/${REPO_NAME}/releases/${RELEASE_TAG}/${LATEST_NAME}"
CL=$(curl -sI "$S3_URL" | awk '/[Cc]ontent-[Ll]ength:/ {print $2}' | tr -d '\r')
if [ -n "$CL" ]; then
MB_LATEST=$(( (CL + 999999) / 1000000 ))
MIB_LATEST=$(awk -v b="$CL" 'BEGIN { printf("%.1f", b/1048576) }')
LATEST_FOUND=true
echo "Latest release: ${RELEASE_TAG} → ${MB_LATEST} MB / ${MIB_LATEST} MiB"
fi
fi
# Delta
DELTA_MB="N/A"; DELTA_PCT="N/A"
if $LATEST_FOUND; then
DELTA_MB=$(awk -v cur="$MB_DEC" -v base="$MB_LATEST" 'BEGIN { printf("%d", cur - base) }')
DELTA_PCT=$(awk -v cur="$MB_DEC" -v base="$MB_LATEST" 'BEGIN { if (base==0){print "N/A"} else {printf("%.2f%%",(cur-base)*100.0/base)} }')
fi
# Flags
CAP_EXCEEDED=false
DELTA_EXCEEDED=false
if [ "${MAX:-0}" -gt 0 ] && (( $(echo "$MB_DEC > $MAX" | bc -l) )); then
CAP_EXCEEDED=true
[ "${ENFORCE_CAP}" = "true" ] && echo "::error::Artifact exceeds cap" || echo "::warning::Artifact exceeds cap"
fi
if $LATEST_FOUND && [ "${MAX_DELTA_MB:-0}" -gt 0 ]; then
ABS_DELTA_MB=${DELTA_MB#-}
if [ "$ABS_DELTA_MB" -gt "$MAX_DELTA_MB" ]; then
DELTA_EXCEEDED=true
[ "${ENFORCE_DELTA}" = "true" ] && echo "::error::Δ ${DELTA_MB} MB vs latest release exceeds threshold" || echo "::warning::Δ ${DELTA_MB} MB vs latest release exceeds threshold"
fi
fi
# Summary
{
echo "### Artifact size check (vs latest release)"
echo "_All sizes shown in both decimal MB (1 MB = 1,000,000 bytes) and binary MiB (1 MiB = 1,048,576 bytes)._"
echo
echo "| Target | File | Size (MB / MiB) | Cap (MB) | Δ MB vs latest release | Δ % vs latest release | Δ threshold (MB) | Result |"
echo "|---|---|---:|---:|---:|---:|---:|---|"
RESULT="✅ OK"
if [ "$CAP_EXCEEDED" = true ] || [ "$DELTA_EXCEEDED" = true ]; then
if [ "$CAP_EXCEEDED" = true ] && [ "$DELTA_EXCEEDED" = true ]; then
RESULT="❌ CAP & DELTA EXCEEDED"
elif [ "$CAP_EXCEEDED" = true ]; then
RESULT="❌ CAP EXCEEDED"
else
RESULT="❌ DELTA EXCEEDED"
fi
fi
if $LATEST_FOUND; then
echo "| ${{ matrix.target }} | \`$FILE\` | ${MB_DEC} / ${MIB_BIN} | ${MAX:-0} | $DELTA_MB | $DELTA_PCT | ${MAX_DELTA_MB:-0} | ${RESULT} |"
echo
echo "- Latest release: **${RELEASE_TAG}** (${MB_LATEST} MB / ${MIB_LATEST} MiB)"
else
echo "| ${{ matrix.target }} | \`$FILE\` | ${MB_DEC} / ${MIB_BIN} | ${MAX:-0} | N/A | N/A | ${MAX_DELTA_MB:-0} | ${RESULT} |"
fi
} >> "$GITHUB_STEP_SUMMARY"
# Export key values so the PR comment workflow can include them
echo "size_mb=$MB_DEC" >> "$GITHUB_OUTPUT"
echo "size_mib=$MIB_BIN" >> "$GITHUB_OUTPUT"
echo "cap_mb=${MAX:-0}" >> "$GITHUB_OUTPUT"
echo "delta_mb=$DELTA_MB" >> "$GITHUB_OUTPUT"
echo "delta_pct=$DELTA_PCT" >> "$GITHUB_OUTPUT"
echo "release_tag=${RELEASE_TAG:-}" >> "$GITHUB_OUTPUT"
echo "result=$RESULT" >> "$GITHUB_OUTPUT"
# Enforce (fail the job) only if toggles are true
if [ "$CAP_EXCEEDED" = true ] && [ "${ENFORCE_CAP}" = "true" ]; then exit 1; fi
if [ "$DELTA_EXCEEDED" = true ] && [ "${ENFORCE_DELTA}" = "true" ]; then exit 1; fi
- name: Save size report
if: always() && steps.size_check.outputs.result != ''
run: |
mkdir -p size_report
{
echo "release_tag=${{ steps.size_check.outputs.release_tag }}"
echo "| ${{ matrix.target }} | ${{ steps.size_check.outputs.size_mb }} / ${{ steps.size_check.outputs.size_mib }} | ${{ steps.size_check.outputs.cap_mb }} | ${{ steps.size_check.outputs.delta_mb }} | ${{ steps.size_check.outputs.delta_pct }} | ${{ steps.size_check.outputs.result }} |"
} > size_report/size_report_${{ matrix.target }}.md
- name: Upload size report
if: always() && steps.size_check.outputs.result != ''
uses: actions/upload-artifact@v6
with:
name: size_report_${{ matrix.target }}_${{ needs.prebuild.outputs.install_source }}
path: size_report/
if-no-files-found: warn
- name: Set SHA, branch, and build prefix
run: |
echo "SHA_SHORT=$(echo ${{ needs.prebuild.outputs.commit_sha }} | cut -c1-7)" >> $GITHUB_ENV
echo "SAFE_BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" >> $GITHUB_ENV
EVENT_NAME="${{ github.event_name }}"
echo "Detected event: $EVENT_NAME"
case "$EVENT_NAME" in
pull_request) echo "BUILD_PREFIX=pr" >> $GITHUB_ENV ;;
push) echo "BUILD_PREFIX=pu" >> $GITHUB_ENV ;;
merge_group) echo "BUILD_PREFIX=mg" >> $GITHUB_ENV ;;
workflow_dispatch) echo "BUILD_PREFIX=wd" >> $GITHUB_ENV ;;
workflow_call) echo "BUILD_PREFIX=wc" >> $GITHUB_ENV ;;
schedule) echo "BUILD_PREFIX=sc" >> $GITHUB_ENV ;;
*) echo "BUILD_PREFIX=gn" >> $GITHUB_ENV ;;
esac
- name: Compute S3 destination path
env:
RESOLVED_DESTINATION_PATH: "${{
inputs.is_release_build && format('@dcl/{0}/releases/{1}', github.event.repository.name, inputs.tag_version)
|| format('@dcl/{0}/branch/{1}/{2}-{3}-{4}', github.event.repository.name, env.SAFE_BRANCH_NAME, env.BUILD_PREFIX, github.run_number, env.SHA_SHORT)
}}"
run: |
echo "DESTINATION_PATH=${RESOLVED_DESTINATION_PATH}" >> $GITHUB_ENV
- name: Upload artifact to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.EXPLORER_TEAM_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.EXPLORER_TEAM_AWS_SECRET_ACCESS_KEY }}
EXPLORER_TEAM_S3_BUCKET: ${{ secrets.EXPLORER_TEAM_S3_BUCKET }}
run: |
npx @dcl/cdn-uploader@next \
--bucket $EXPLORER_TEAM_S3_BUCKET \
--local-folder upload_to_s3 \
--bucket-folder $DESTINATION_PATH
- name: Print S3 upload URL
run: |
ARTIFACT_URL="https://explorer-artifacts.decentraland.org/${DESTINATION_PATH}/${{ env.artifact_name }}.zip"
echo "::notice::Artifact uploaded to: ${ARTIFACT_URL}"
{
echo "### Artifact upload URL (${{ matrix.target }})"
echo
echo "[${ARTIFACT_URL}](${ARTIFACT_URL})"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload debug symbols
uses: actions/upload-artifact@v6
with:
name: ${{ env.artifact_name }}_debug_symbols
path: |
build/**/*_BackUpThisFolder_ButDontShipItWithYourGame
build/**/*_BurstDebugInformation_DoNotShip
if-no-files-found: error
- name: Upload debug symbols to Sentry
if: ${{ needs.prebuild.outputs.sentry_enabled == 'true' }}
shell: bash
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_CLI_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ inputs.is_release_build && vars.SENTRY_PROJECT || vars.SENTRY_PROJECT_DEV }}
run: |
npx --yes @sentry/cli debug-files upload \
--org "$SENTRY_ORG" \
--project "$SENTRY_PROJECT" \
"build"
# Will run always (even if failing)
- name: Upload cloud logs
if: always()
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}_${{ needs.prebuild.outputs.install_source }}_unity_log
path: unity_cloud_log.log
if-no-files-found: error
# Will run always (even if failing)
- name: Print cloud logs
if: always()
run: cat unity_cloud_log.log
- name: Extract and display errors
if: always()
run: |
echo "=== Extracted Errors for ${{ matrix.target }} ${{ needs.prebuild.outputs.install_source }} ==="
grep -iE "error c|fatal" unity_cloud_log.log | sed 's/^/\x1b[31m/' | sed 's/$/\x1b[0m/' || echo "No 'error c' or 'fatal' errors found in ${{ matrix.target }} log."
- name: Generate Shader Compilation Report
shell: pwsh
run: |
./scripts/Generate-ShaderReport.ps1 -InputLog "unity_cloud_log.log" -OutputReport "shader_compilation_report.log"
- name: Upload Shader Compilation Report
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}_${{ needs.prebuild.outputs.install_source }}_shader_compilation_report
path: shader_compilation_report.log
if-no-files-found: warn
# Also runs on failure() so an outer-step timeout doesn't leave a Unity-side build holding a slot.
- name: Cancel Unity Cloud build
if: ${{ cancelled() || failure() }}
env:
API_KEY: ${{ secrets.UNITY_CLOUD_API_KEY }}
ORG_ID: ${{ secrets.UNITY_CLOUD_ORG_ID }}
PROJECT_ID: ${{ secrets.UNITY_CLOUD_PROJECT_ID }}
run: python -u scripts/cloudbuild/build.py --cancel || true
build-gate:
name: Build Gate (Windows + macOS)
runs-on: ubuntu-latest
needs: [prebuild, build]
if: always() && github.event_name == 'pull_request'
steps:
- name: Verify both targets built and passed
shell: bash
run: |
set -euo pipefail
should_build='${{ needs.prebuild.outputs.should_build }}'
prebuild_result='${{ needs.prebuild.result }}'
# Legit no-op: prebuild ran and decided no build is needed (no Explorer/ changes).
# The Prebuild job's "Skip build and test checks" step already posts per-target success
# statuses for this case, so the gate can safely pass.
if [ "$prebuild_result" = "success" ] && [ "$should_build" != "true" ]; then
echo "Gate not applicable (prebuild ran, no Explorer/ changes). Passing."
exit 0
fi
# Any other prebuild outcome (skipped/cancelled/failure) means no real build happened.
# Fail closed so an incidental PR event can't silently produce a green Build Gate.
if [ "$prebuild_result" != "success" ]; then
echo "::error::Prebuild did not succeed (result: $prebuild_result). No build produced for this commit; re-trigger the workflow (e.g. push a new commit or add the 'force-build' label)."
exit 1
fi
targets='${{ needs.prebuild.outputs.targets }}'
echo "Targets that ran: $targets"
has_windows=$(echo "$targets" | jq 'any(. == "windows64")')
has_macos=$(echo "$targets" | jq 'any(. == "macos")')
if [ "$has_windows" != "true" ] || [ "$has_macos" != "true" ]; then
echo "::error::Build Gate requires both Windows and macOS builds. This run was filtered to: $targets"
echo "::error::Remove the 'windows-only' or 'macos-only' label so both targets build, then push or re-trigger."
exit 1
fi
build_result='${{ needs.build.result }}'
if [ "$build_result" != "success" ]; then
echo "::error::Build matrix did not succeed (result: $build_result)"
exit 1
fi
echo "Both Windows and macOS builds passed."
# Test change