Skip to content

Commit 00c5d07

Browse files
mikhail-dclclaudeeordanodecentraland-bot
authored
chore: build-time quick wins — rsp DAG rebuild fix, CI queue hygiene, symbols retention, LF scripts (#9616)
* chore: commit compiler rsp files, preBuildScript writes only on drift Every cloud build wrote Assets/csc.rsp (+4 siblings) fresh onto a clean checkout; Bee logged 'Rebuilding DAG because FileSignature timestamp changed: Assets/csc.rsp' and re-ran IL2CPP+Usym (~267s) on no-change builds. Committed rsp files keep a stable checkout state; the script now rewrites only on content drift. Their .meta files are committed too, and Explorer/Assets/*.rsp is pinned to eol=lf so CRLF-converting Windows builders cannot defeat the byte-compare. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: CI queue hygiene — cancel-first ordering, orphan-build cleanup UBA allows one pending build per target and the org concurrency cap counts queued builds, so superseded or orphaned builds inflate everyone's queue. - workflow: the Unity-side cancel now runs first-after-build with a 2-min timeout — GitHub force-kills a cancelled job 5 minutes after the request, and a large-log upload ahead of the DELETE could eat the window. Log steps gate on the log existing (in-queue cancels never write one). - build.py: persist the target before the build POST and let --cancel fall back to the target's latest non-terminal build, so a runner death between POST and id write cannot leave a queued build holding a slot; the cancel DELETE gets a 30s timeout. A 90s push debounce was considered and rejected: flat latency tax on every push build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: upload debug-symbol artifacts only on release builds, 7-day retention The *_debug_symbols artifacts (546-935 MB per platform) uploaded on every build at default retention with zero consumers: Sentry symbolication runs against the on-disk build/ folder in the same job, and release drafting only downloaded them because its Decentraland_.* regex accidentally matched before attaching just the four player zips. Gate the upload to release builds, cap retention at 7 days, and anchor the drafting regex to the player zips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: force LF checkout for shell scripts Windows cloud builders check out with CRLF conversion, so the UBA preBuildScript (scripts/generate-ignore-warnings.sh) failed with "$'\r': command not found" on every Windows build — preBuildScriptFailsBuild=false hid it. All committed *.sh blobs are already LF; this only pins the checkout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: extend stale watchdog to active build phase via log growth detection The existing STALE_POLL_THRESHOLD watchdog (600 s) only monitors queue statuses (created / queued / sentToBuilder). Once a build transitions to `started` the watchdog goes blind — a deadlocked builder keeps reporting `started` indefinitely, leaving BUILD_TIMEOUT (3 h) as the only backstop. This change adds a log-growth liveness signal for the active phase: • New `get_log_byte_count(id)` probes `/builds/{id}/log` via HEAD (then a single-byte Range request as fallback) to return the current log size without downloading the full log. • `run_poll_loop` tracks `last_log_byte_count` / `last_log_growth`. On every poll tick while `status in ACTIVE_STATUSES` the log size is checked; if it has not grown for `LOG_STALL_THRESHOLD` seconds (default 900 s / 15 min) the build is cancelled and `log_stall` is returned. • `log_stall` is handled identically to `build_timeout`: build_info is deleted, the log downloaded for debugging, and exit code 99 triggers nick-fields/retry on a fresh builder VM. • `LOG_STALL_THRESHOLD: 900` is exposed in build-unitycloud.yml alongside the existing `STALE_POLL_THRESHOLD`. Closes #9628 * chore: address review — harden rsp generator, guard cancel fallback, add drift CI check - generate-ignore-warnings.sh: temp file via mktemp outside Assets/ (an interrupted run no longer leaves a stray file for Unity to import), explicit write/copy error handling replacing the stale $? idiom, cp keeps the destination inode, distinct unchanged/updated messages. - build.py: the --cancel fallback only cancels a build still in an explicit queue status — targets are shared (release pool, consecutive runs per branch), so an already-started build may belong to a concurrent run; a missing status is treated as not-cancellable. get_latest_build gains timeout=30: it now sits on the 2-minute cancellation critical path. - test.yml: new rsp-drift job regenerates the rsp files and fails on diff, so generator drift can't silently reintroduce the per-build Bee DAG rebuild. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: address watchdog re-review — growth-armed stall cancel, probe hardening, drift-guard gaps - build.py watchdog: the stall cancel now arms only after one observed log-size increase, so a probe reporting a constant value (proxy answering HEAD with Content-Length: 0, endpoint dead during builds) reads as inactive instead of cancelling every build at 15 min. A size decrease (restarted builds can truncate the log) resets the clock. First probe result is printed once; non-2xx probe responses are surfaced once instead of silently returning None. Threshold evidence: longest log silence across 18 preserved warm builds is 99 s (IL2CPP), giving the 900 s threshold a ~9x margin. - get_log_byte_count: allow_redirects=True on HEAD (requests defaults HEAD to not following redirects; the endpoint may 302 to signed storage), timeouts lowered 30 -> 10 s on the poll critical path. - test.yml: generator added to the pull_request paths filter (an edit to warnings_to_ignore alone never triggered the workflow); drift check uses git status --porcelain so a new untracked rsp file counts as drift; rsp-drift job gets timeout-minutes and contents: read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: retry externally-canceled builds instead of hard-failing Observed on this branch's own CI (run 31087915798): the Windows build sat sentToBuilder for 9 minutes, UBA canceled it platform-side (builder provisioning failure; the same window produced a transient HTTP 500 from the API), and build.py mapped the external 'canceled' to exit 1 - which nick-fields/retry does not retry, so an infra flake became a hard red job with the second attempt unused. This run's own cancellations exit through the watchdog/timeout branches, so a 'canceled' final outcome is always external: exit 99 after deleting build_info, letting the retry create a fresh build. Bounded by max_attempts: 2, so a supersede race cannot loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: don't retry a superseded build on shared targets The unconditional canceled->retry turned a supersede on the shared release pool into a ping-pong: the superseded run's retry re-POSTs, hits 'already a build pending', cancels the superseding run's build, and hands it the same exit 99 - both runs burn a queue+build cycle and one still ends red (and set_parameters clobbers the winner's env vars). Build numbers are monotonic per target: if the target's newest build is newer than ours we were superseded and exit 1 without retrying; if ours is still the newest, the cancel was platform-side (builder provisioning) and the retry stands. Also download the log best-effort on this path - with the log steps now gated on the log existing, an externally-canceled run would otherwise finish with no log artifact at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: close the supersede-probe gap, fail-open on probe errors Two residuals in the canceled-outcome guard: - run_build cancels the pending build and only re-POSTs ~30 s later, so a supersede is invisible to a probe landing inside that gap (~coin flip with POLL_TIME=60). When the first probe does not show a newer build, sleep 35 s and re-probe once before deciding to retry. - get_latest_build was the one bare call site on this path: a transient RequestException would traceback to exit 1 - skipping build_info cleanup and the log download, and converting the provisioning-flake retry this branch exists for into a red job. The probe now fails open (latest = None -> retry), matching the non-200 behaviour. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Esteban Ordano <42750+eordano@users.noreply.github.com> Co-authored-by: decentraland-bot <44584806+decentraland-bot@users.noreply.github.com>
1 parent 162f73f commit 00c5d07

16 files changed

Lines changed: 353 additions & 31 deletions

.gitattributes

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,13 @@ Explorer/Assets/StreamingAssets/AssetBundles/**/*.meta -filter=lfs -diff=lfs -me
2929
*.asset binary
3030
*.controller binary
3131
*.asset text diff=yaml merge=yaml
32+
33+
# Root-level compiler rsp files must check out byte-identical to what
34+
# scripts/generate-ignore-warnings.sh writes (LF), or its drift check rewrites
35+
# them on CRLF-converting builders and re-triggers a Bee DAG rebuild.
36+
Explorer/Assets/*.rsp text eol=lf
37+
38+
# Shell scripts must check out LF everywhere: CRLF-converting Windows builders
39+
# fail them with "$'\r': command not found" (seen on every Windows cloud build
40+
# running the UBA preBuildScript).
41+
*.sh text eol=lf

.github/workflows/build-release-main-page.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ jobs:
5656
uses: dawidd6/action-download-artifact@v6
5757
with:
5858
run_id: ${{ github.event.workflow_run.id }}
59-
name: Decentraland_.*
59+
# Anchored to the four player zips: Decentraland_.* also matched the multi-GB
60+
# *_debug_symbols artifacts, downloading them only to discard them.
61+
name: Decentraland_(windows64|macos)(_epic)?$
6062
name_is_regexp: true
6163
skip_unpack: true
6264

.github/workflows/build-unitycloud.yml

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ jobs:
573573
POLL_TIME: 60
574574
QUEUE_POLL_TIME: 120
575575
STALE_POLL_THRESHOLD: 600
576+
LOG_STALL_THRESHOLD: 900
576577
QUEUE_TIMEOUT: 14400
577578
BUILD_TIMEOUT: 10800
578579
TARGET: t_${{ matrix.target }}
@@ -599,6 +600,19 @@ jobs:
599600
PARAM_IS_RELEASE_BUILD: ${{ inputs.is_release_build }}
600601
PARAM_UNITY_EXTRA_PARAMS: '-disable-assembly-updater'
601602

603+
# Must run before any always() log/report steps: on cancellation GitHub grants a 5-minute
604+
# grace window, and the DELETE that frees the Unity-side queue slot has to win that race
605+
# (a large-log upload ahead of it can eat the whole window). Also runs on failure() so an
606+
# outer-step timeout doesn't leave a Unity-side build holding a slot.
607+
- name: Cancel Unity Cloud build
608+
if: ${{ cancelled() || failure() }}
609+
timeout-minutes: 2
610+
env:
611+
API_KEY: ${{ secrets.UNITY_CLOUD_API_KEY }}
612+
ORG_ID: ${{ secrets.UNITY_CLOUD_ORG_ID }}
613+
PROJECT_ID: ${{ secrets.UNITY_CLOUD_PROJECT_ID }}
614+
run: python -u scripts/cloudbuild/build.py --cancel || true
615+
602616
- name: Locate Windows main executable
603617
if: matrix.target == 'windows64' && (inputs.is_release_build == true || github.event.inputs.force_sign == 'true')
604618
run: |
@@ -877,14 +891,19 @@ jobs:
877891
echo "[${ARTIFACT_URL}](${ARTIFACT_URL})"
878892
} >> "$GITHUB_STEP_SUMMARY"
879893
894+
# Symbol artifacts have no downstream consumer (Sentry uploads from the on-disk build/
895+
# folder below; release drafting attaches only the player zips) — keep them only where a
896+
# human might need post-hoc symbolication, and never at default retention.
880897
- name: Upload debug symbols
898+
if: ${{ inputs.is_release_build == true }}
881899
uses: actions/upload-artifact@v6
882900
with:
883901
name: ${{ env.artifact_name }}_debug_symbols
884902
path: |
885903
build/**/*_BackUpThisFolder_ButDontShipItWithYourGame
886904
build/**/*_BurstDebugInformation_DoNotShip
887905
if-no-files-found: error
906+
retention-days: 7
888907

889908
- name: Upload debug symbols to Sentry
890909
if: ${{ needs.prebuild.outputs.sentry_enabled == 'true' }}
@@ -899,22 +918,22 @@ jobs:
899918
--project "$SENTRY_PROJECT" \
900919
"build"
901920
902-
# Will run always (even if failing)
921+
# Runs even on failure, but only once a log actually exists (a run cancelled in-queue
922+
# never writes one, and a guaranteed-failing upload would burn the cancellation grace window).
903923
- name: Upload cloud logs
904-
if: always()
924+
if: ${{ always() && hashFiles('unity_cloud_log.log') != '' }}
905925
uses: actions/upload-artifact@v6
906926
with:
907927
name: ${{ matrix.target }}_${{ needs.prebuild.outputs.install_source }}_unity_log
908928
path: unity_cloud_log.log
909929
if-no-files-found: error
910930

911-
# Will run always (even if failing)
912931
- name: Print cloud logs
913-
if: always()
932+
if: ${{ always() && hashFiles('unity_cloud_log.log') != '' }}
914933
run: cat unity_cloud_log.log
915934

916935
- name: Extract and display errors
917-
if: always()
936+
if: ${{ always() && hashFiles('unity_cloud_log.log') != '' }}
918937
run: |
919938
echo "=== Extracted Errors for ${{ matrix.target }} ${{ needs.prebuild.outputs.install_source }} ==="
920939
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."
@@ -931,15 +950,6 @@ jobs:
931950
path: shader_compilation_report.log
932951
if-no-files-found: warn
933952

934-
# Also runs on failure() so an outer-step timeout doesn't leave a Unity-side build holding a slot.
935-
- name: Cancel Unity Cloud build
936-
if: ${{ cancelled() || failure() }}
937-
env:
938-
API_KEY: ${{ secrets.UNITY_CLOUD_API_KEY }}
939-
ORG_ID: ${{ secrets.UNITY_CLOUD_ORG_ID }}
940-
PROJECT_ID: ${{ secrets.UNITY_CLOUD_PROJECT_ID }}
941-
run: python -u scripts/cloudbuild/build.py --cancel || true
942-
943953
build-gate:
944954
name: Build Gate (Windows + macOS)
945955
runs-on: ubuntu-latest

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ on:
55
pull_request:
66
paths:
77
- 'Explorer/**'
8+
# The rsp-drift job must run when the generator changes, or drift merges unnoticed.
9+
- 'scripts/generate-ignore-warnings.sh'
810
types:
911
- opened
1012
- reopened
@@ -36,6 +38,35 @@ concurrency:
3638

3739
jobs:
3840

41+
# The committed Explorer/Assets/*.rsp files must stay byte-identical to what
42+
# generate-ignore-warnings.sh produces: on drift the preBuildScript rewrites them
43+
# on every cloud build, and the Bee DAG rebuild (~4.5 min per build) silently returns.
44+
rsp-drift:
45+
name: rsp files match generator
46+
runs-on: ubuntu-latest
47+
timeout-minutes: 5
48+
permissions:
49+
contents: read
50+
steps:
51+
- name: Checkout code
52+
uses: actions/checkout@v6
53+
with:
54+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
55+
56+
- name: Regenerate rsp files and fail on drift
57+
run: |
58+
set -euo pipefail
59+
bash scripts/generate-ignore-warnings.sh
60+
# git status --porcelain (not `git diff`) so a newly added, still-untracked
61+
# rsp file also counts as drift.
62+
drift=$(git status --porcelain -- 'Explorer/Assets/*.rsp')
63+
if [[ -n "$drift" ]]; then
64+
echo "Committed rsp files differ from generate-ignore-warnings.sh output:"
65+
echo "$drift"
66+
git diff -- 'Explorer/Assets/*.rsp'
67+
exit 1
68+
fi
69+
3970
changes:
4071
if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')) || (github.event.pull_request.draft == false) || (github.event.label.name == 'force-build') || (github.event.label.name == 'clean-build') || (github.event.label.name == 'force-lint')
4172
name: Detect C# changes

Explorer/Assets/csc.rsp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-nowarn:8618
2+
-nowarn:8625
3+
-nowarn:8602
4+
-nowarn:8604
5+
-nowarn:8619
6+
-nowarn:8620
7+
-nowarn:8603
8+
-nowarn:8600
9+
-nowarn:8601
10+
-nowarn:0649
11+
-nowarn:0414
12+
-nowarn:0168
13+
-nowarn:0219
14+
-nowarn:8632

Explorer/Assets/csc.rsp.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Explorer/Assets/gmcs.rsp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-nowarn:8618
2+
-nowarn:8625
3+
-nowarn:8602
4+
-nowarn:8604
5+
-nowarn:8619
6+
-nowarn:8620
7+
-nowarn:8603
8+
-nowarn:8600
9+
-nowarn:8601
10+
-nowarn:0649
11+
-nowarn:0414
12+
-nowarn:0168
13+
-nowarn:0219
14+
-nowarn:8632

Explorer/Assets/gmcs.rsp.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Explorer/Assets/mcs.rsp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-nowarn:8618
2+
-nowarn:8625
3+
-nowarn:8602
4+
-nowarn:8604
5+
-nowarn:8619
6+
-nowarn:8620
7+
-nowarn:8603
8+
-nowarn:8600
9+
-nowarn:8601
10+
-nowarn:0649
11+
-nowarn:0414
12+
-nowarn:0168
13+
-nowarn:0219
14+
-nowarn:8632

Explorer/Assets/mcs.rsp.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)