Files
bun.sh/cmake/tools/SetupBuildkite.cmake
robobun bfe40e8760 fix(cmake): use BUILDKITE_BUILD_NUMBER to avoid 302 redirect (#26409)
## What does this PR do?

Fixes CMake "No jobs found" error during the build-bun step in CI by
using `BUILDKITE_BUILD_NUMBER` instead of `BUILDKITE_BUILD_ID` (UUID)
for the Buildkite API URL.

### Problem

When `BUN_LINK_ONLY=ON`, `SetupBuildkite.cmake` fetches build info from
the Buildkite API to download artifacts from earlier build steps
(build-cpp, build-zig).

The `BUILDKITE_BUILD_ID` environment variable contains a UUID (e.g.,
`019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac`). When this UUID is used in the
URL, Buildkite returns a **302 redirect** to the numeric build number
URL (e.g., `/builds/35708`).

CMake's `file(DOWNLOAD)` command **does not follow HTTP redirects**, so
the downloaded file is empty. Parsing the empty JSON yields 0 jobs,
triggering the fatal error:

```
CMake Error at cmake/tools/SetupBuildkite.cmake:67 (message):
  No jobs found:
  https://buildkite.com/bun/bun/builds/019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac
```

### Solution

Prefer `BUILDKITE_BUILD_NUMBER` (numeric, e.g., `35708`) when available,
which doesn't redirect. This environment variable is automatically set
by Buildkite.

## How did you verify your code works?

- Verified UUID URL returns 302: `curl -sS -w '%{http_code}'
"https://buildkite.com/bun/bun/builds/019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac"`
→ `302`
- Verified numeric URL returns 200 with JSON: `curl -sS -H "Accept:
application/json" "https://buildkite.com/bun/bun/builds/35708"` → valid
JSON with jobs array

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 23:54:33 -08:00

226 lines
8.8 KiB
CMake

optionx(BUILDKITE_CACHE BOOL "If the build can use Buildkite caches, even if not running in Buildkite" DEFAULT ${BUILDKITE})
if(NOT BUILDKITE_CACHE OR NOT BUN_LINK_ONLY)
return()
endif()
optionx(BUILDKITE_ORGANIZATION_SLUG STRING "The organization slug to use on Buildkite" DEFAULT "bun")
optionx(BUILDKITE_PIPELINE_SLUG STRING "The pipeline slug to use on Buildkite" DEFAULT "bun")
optionx(BUILDKITE_BUILD_ID STRING "The build ID (UUID) to use on Buildkite")
optionx(BUILDKITE_BUILD_NUMBER STRING "The build number to use on Buildkite")
optionx(BUILDKITE_GROUP_ID STRING "The group ID to use on Buildkite")
if(ENABLE_BASELINE)
set(DEFAULT_BUILDKITE_GROUP_KEY ${OS}-${ARCH}-baseline)
else()
set(DEFAULT_BUILDKITE_GROUP_KEY ${OS}-${ARCH})
endif()
optionx(BUILDKITE_GROUP_KEY STRING "The group key to use on Buildkite" DEFAULT ${DEFAULT_BUILDKITE_GROUP_KEY})
if(BUILDKITE)
optionx(BUILDKITE_BUILD_ID_OVERRIDE STRING "The build ID to use on Buildkite")
if(BUILDKITE_BUILD_ID_OVERRIDE)
setx(BUILDKITE_BUILD_ID ${BUILDKITE_BUILD_ID_OVERRIDE})
endif()
endif()
set(BUILDKITE_PATH ${BUILD_PATH}/buildkite)
set(BUILDKITE_BUILDS_PATH ${BUILDKITE_PATH}/builds)
if(NOT BUILDKITE_BUILD_ID)
# TODO: find the latest build on the main branch that passed
return()
endif()
# Use BUILDKITE_BUILD_NUMBER for the URL if available, as the UUID format causes a 302 redirect
# that CMake's file(DOWNLOAD) doesn't follow, resulting in empty response.
if(BUILDKITE_BUILD_NUMBER)
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_NUMBER})
else()
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_ID})
endif()
setx(BUILDKITE_BUILD_PATH ${BUILDKITE_BUILDS_PATH}/builds/${BUILDKITE_BUILD_ID})
file(
DOWNLOAD ${BUILDKITE_BUILD_URL}
HTTPHEADER "Accept: application/json"
TIMEOUT 15
STATUS BUILDKITE_BUILD_STATUS
${BUILDKITE_BUILD_PATH}/build.json
)
if(NOT BUILDKITE_BUILD_STATUS EQUAL 0)
message(FATAL_ERROR "No build found: ${BUILDKITE_BUILD_STATUS} ${BUILDKITE_BUILD_URL}")
return()
endif()
file(READ ${BUILDKITE_BUILD_PATH}/build.json BUILDKITE_BUILD)
# CMake's string(JSON ...) interprets escape sequences like \n, \r, \t.
# We need to escape these specific sequences while preserving valid JSON escapes like \" and \\.
# Strategy: Use a unique placeholder to protect \\ sequences, escape \n/\r/\t, then restore \\.
# This prevents \\n (literal backslash + n) from being corrupted to \\\n.
set(BKSLASH_PLACEHOLDER "___BKSLASH_PLACEHOLDER_7f3a9b2c___")
string(REPLACE "\\\\" "${BKSLASH_PLACEHOLDER}" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\n" "\\\\n" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\r" "\\\\r" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\t" "\\\\t" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "${BKSLASH_PLACEHOLDER}" "\\\\" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(JSON BUILDKITE_BUILD_UUID GET ${BUILDKITE_BUILD} id)
string(JSON BUILDKITE_JOBS GET ${BUILDKITE_BUILD} jobs)
string(JSON BUILDKITE_JOBS_COUNT LENGTH ${BUILDKITE_JOBS})
if(NOT BUILDKITE_JOBS_COUNT GREATER 0)
message(FATAL_ERROR "No jobs found: ${BUILDKITE_BUILD_URL}")
return()
endif()
set(BUILDKITE_JOBS_FAILED)
set(BUILDKITE_JOBS_NOT_FOUND)
set(BUILDKITE_JOBS_NO_ARTIFACTS)
set(BUILDKITE_JOBS_NO_MATCH)
set(BUILDKITE_JOBS_MATCH)
math(EXPR BUILDKITE_JOBS_MAX_INDEX "${BUILDKITE_JOBS_COUNT} - 1")
foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
string(JSON BUILDKITE_JOB GET ${BUILDKITE_JOBS} ${i})
string(JSON BUILDKITE_JOB_ID GET ${BUILDKITE_JOB} id)
string(JSON BUILDKITE_JOB_PASSED GET ${BUILDKITE_JOB} passed)
string(JSON BUILDKITE_JOB_GROUP_ID GET ${BUILDKITE_JOB} group_uuid)
string(JSON BUILDKITE_JOB_GROUP_KEY GET ${BUILDKITE_JOB} group_identifier)
string(JSON BUILDKITE_JOB_NAME GET ${BUILDKITE_JOB} step_key)
if(NOT BUILDKITE_JOB_NAME)
string(JSON BUILDKITE_JOB_NAME GET ${BUILDKITE_JOB} name)
endif()
if(NOT BUILDKITE_JOB_PASSED)
list(APPEND BUILDKITE_JOBS_FAILED ${BUILDKITE_JOB_NAME})
continue()
endif()
if(NOT (BUILDKITE_GROUP_ID AND BUILDKITE_GROUP_ID STREQUAL BUILDKITE_JOB_GROUP_ID) AND
NOT (BUILDKITE_GROUP_KEY AND BUILDKITE_GROUP_KEY STREQUAL BUILDKITE_JOB_GROUP_KEY))
list(APPEND BUILDKITE_JOBS_NO_MATCH ${BUILDKITE_JOB_NAME})
continue()
endif()
set(BUILDKITE_ARTIFACTS_URL https://buildkite.com/organizations/${BUILDKITE_ORGANIZATION_SLUG}/pipelines/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_UUID}/jobs/${BUILDKITE_JOB_ID}/artifacts)
set(BUILDKITE_ARTIFACTS_PATH ${BUILDKITE_BUILD_PATH}/artifacts/${BUILDKITE_JOB_ID}.json)
file(
DOWNLOAD ${BUILDKITE_ARTIFACTS_URL}
HTTPHEADER "Accept: application/json"
TIMEOUT 15
STATUS BUILDKITE_ARTIFACTS_STATUS
${BUILDKITE_ARTIFACTS_PATH}
)
if(NOT BUILDKITE_ARTIFACTS_STATUS EQUAL 0)
list(APPEND BUILDKITE_JOBS_NOT_FOUND ${BUILDKITE_JOB_NAME})
continue()
endif()
file(READ ${BUILDKITE_ARTIFACTS_PATH} BUILDKITE_ARTIFACTS)
string(JSON BUILDKITE_ARTIFACTS_LENGTH LENGTH ${BUILDKITE_ARTIFACTS})
if(NOT BUILDKITE_ARTIFACTS_LENGTH GREATER 0)
list(APPEND BUILDKITE_JOBS_NO_ARTIFACTS ${BUILDKITE_JOB_NAME})
continue()
endif()
math(EXPR BUILDKITE_ARTIFACTS_MAX_INDEX "${BUILDKITE_ARTIFACTS_LENGTH} - 1")
foreach(i RANGE 0 ${BUILDKITE_ARTIFACTS_MAX_INDEX})
string(JSON BUILDKITE_ARTIFACT GET ${BUILDKITE_ARTIFACTS} ${i})
string(JSON BUILDKITE_ARTIFACT_ID GET ${BUILDKITE_ARTIFACT} id)
string(JSON BUILDKITE_ARTIFACT_PATH GET ${BUILDKITE_ARTIFACT} path)
if(NOT BUILDKITE_ARTIFACT_PATH MATCHES "\\.(o|a|lib|zip|tar|gz)")
continue()
endif()
if(BUILDKITE)
if(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-profile.a")
set(BUILDKITE_ARTIFACT_PATH libbun-profile.a.gz)
elseif(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-asan.a")
set(BUILDKITE_ARTIFACT_PATH libbun-asan.a.gz)
endif()
set(BUILDKITE_DOWNLOAD_COMMAND buildkite-agent artifact download ${BUILDKITE_ARTIFACT_PATH} . --build ${BUILDKITE_BUILD_UUID} --step ${BUILDKITE_JOB_ID})
else()
set(BUILDKITE_DOWNLOAD_COMMAND curl -L -o ${BUILDKITE_ARTIFACT_PATH} ${BUILDKITE_ARTIFACTS_URL}/${BUILDKITE_ARTIFACT_ID})
endif()
add_custom_command(
COMMENT
"Downloading ${BUILDKITE_ARTIFACT_PATH}"
VERBATIM COMMAND
${BUILDKITE_DOWNLOAD_COMMAND}
WORKING_DIRECTORY
${BUILD_PATH}
OUTPUT
${BUILD_PATH}/${BUILDKITE_ARTIFACT_PATH}
)
if(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-profile.a.gz")
add_custom_command(
COMMENT
"Unpacking libbun-profile.a.gz"
VERBATIM COMMAND
gunzip libbun-profile.a.gz
WORKING_DIRECTORY
${BUILD_PATH}
OUTPUT
${BUILD_PATH}/libbun-profile.a
DEPENDS
${BUILD_PATH}/libbun-profile.a.gz
)
elseif(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-asan.a.gz")
add_custom_command(
COMMENT
"Unpacking libbun-asan.a.gz"
VERBATIM COMMAND
gunzip libbun-asan.a.gz
WORKING_DIRECTORY
${BUILD_PATH}
OUTPUT
${BUILD_PATH}/libbun-asan.a
DEPENDS
${BUILD_PATH}/libbun-asan.a.gz
)
endif()
endforeach()
list(APPEND BUILDKITE_JOBS_MATCH ${BUILDKITE_JOB_NAME})
endforeach()
if(BUILDKITE_JOBS_FAILED)
list(SORT BUILDKITE_JOBS_FAILED COMPARE STRING)
list(JOIN BUILDKITE_JOBS_FAILED " " BUILDKITE_JOBS_FAILED)
message(WARNING "The following jobs were found, but failed: ${BUILDKITE_JOBS_FAILED}")
endif()
if(BUILDKITE_JOBS_NOT_FOUND)
list(SORT BUILDKITE_JOBS_NOT_FOUND COMPARE STRING)
list(JOIN BUILDKITE_JOBS_NOT_FOUND " " BUILDKITE_JOBS_NOT_FOUND)
message(WARNING "The following jobs were found, but could not fetch their data: ${BUILDKITE_JOBS_NOT_FOUND}")
endif()
if(BUILDKITE_JOBS_NO_MATCH)
list(SORT BUILDKITE_JOBS_NO_MATCH COMPARE STRING)
list(JOIN BUILDKITE_JOBS_NO_MATCH " " BUILDKITE_JOBS_NO_MATCH)
message(WARNING "The following jobs were found, but did not match the group ID: ${BUILDKITE_JOBS_NO_MATCH}")
endif()
if(BUILDKITE_JOBS_NO_ARTIFACTS)
list(SORT BUILDKITE_JOBS_NO_ARTIFACTS COMPARE STRING)
list(JOIN BUILDKITE_JOBS_NO_ARTIFACTS " " BUILDKITE_JOBS_NO_ARTIFACTS)
message(WARNING "The following jobs were found, but had no artifacts: ${BUILDKITE_JOBS_NO_ARTIFACTS}")
endif()
if(BUILDKITE_JOBS_MATCH)
list(SORT BUILDKITE_JOBS_MATCH COMPARE STRING)
list(JOIN BUILDKITE_JOBS_MATCH " " BUILDKITE_JOBS_MATCH)
message(STATUS "The following jobs were found, and matched the group ID: ${BUILDKITE_JOBS_MATCH}")
endif()
if(NOT BUILDKITE_JOBS_FAILED AND NOT BUILDKITE_JOBS_NOT_FOUND AND NOT BUILDKITE_JOBS_NO_MATCH AND NOT BUILDKITE_JOBS_NO_ARTIFACTS AND NOT BUILDKITE_JOBS_MATCH)
message(FATAL_ERROR "Something went wrong with Buildkite?")
endif()