Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 56 additions & 12 deletions .github/workflows/build-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ on:
platforms:
description: Which platforms to build
type: choice
options: [all, android, ios]
options: [all, android, ios, macos]
default: all
release_tag:
description: >-
Publish a release under this tag when the matrix succeeds
(e.g. v8-14.9.207.39-4). Leave empty to build without releasing.
type: string
default: ''
push:
branches: [main]
tags: ['v8-*']
Expand All @@ -35,14 +41,31 @@ jobs:
outputs:
v8_version: ${{ steps.cfg.outputs.v8_version }}
ndk_release: ${{ steps.cfg.outputs.ndk_release }}
apple_variants: ${{ steps.cfg.outputs.apple_variants }}
steps:
- uses: actions/checkout@v7
- id: cfg
env:
# Anything other than a manual dispatch builds the whole matrix.
PLATFORMS: ${{ github.event_name == 'workflow_dispatch' && inputs.platforms || 'all' }}
run: |
set -a; . ./config.env; set +a
echo "v8_version=$V8_VERSION" >> "$GITHUB_OUTPUT"
echo "ndk_release=$ANDROID_NDK_RELEASE" >> "$GITHUB_OUTPUT"

# iOS and macOS share a job (same runner, same checkout, same script)
# but are separately selectable, so the variant list is built here
# rather than filtered inside the job.
ios='"arm64-device","arm64-simulator","x64-simulator","arm64-catalyst","x64-catalyst"'
macos='"arm64-macos","x64-macos"'
case "$PLATFORMS" in
ios) variants="[$ios]" ;;
macos) variants="[$macos]" ;;
android) variants="[]" ;;
*) variants="[$ios,$macos]" ;;
esac
echo "apple_variants=$variants" >> "$GITHUB_OUTPUT"

android:
needs: config
if: ${{ github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || inputs.platforms == 'android' }}
Expand Down Expand Up @@ -114,15 +137,17 @@ jobs:
path: v8-*-src-headers.tar.gz
retention-days: 7

ios:
# Named for the platform family rather than "ios": it builds the catalyst and
# macOS variants too, which are not iOS targets.
apple:
needs: config
if: ${{ github.event_name != 'workflow_dispatch' || inputs.platforms == 'all' || inputs.platforms == 'ios' }}
if: ${{ needs.config.outputs.apple_variants != '[]' }}
runs-on: macos-15
timeout-minutes: 300
strategy:
fail-fast: false
matrix:
variant: [arm64-device, arm64-simulator, x64-simulator, arm64-catalyst, x64-catalyst]
variant: ${{ fromJSON(needs.config.outputs.apple_variants) }}
steps:
- uses: actions/checkout@v7

Expand All @@ -134,23 +159,39 @@ jobs:
- name: Fetch and patch V8
run: scripts/matrix/fetch.sh --platform ios

# The build emits `slice`, which is the dist/ directory and the asset
# stem. It is not simply "ios-<variant>": the macOS variants produce
# macos-<cpu>, since they are target_os="mac" rather than an iOS
# target_environment.
- name: Build ${{ matrix.variant }}
id: build
run: scripts/matrix/build-ios.sh --variant ${{ matrix.variant }}

- name: Package
run: |
tar -czf "v8-${{ needs.config.outputs.v8_version }}-ios-${{ matrix.variant }}.tar.gz" \
-C dist "ios-${{ matrix.variant }}"
tar -czf "v8-${{ needs.config.outputs.v8_version }}-${{ steps.build.outputs.slice }}.tar.gz" \
-C dist "${{ steps.build.outputs.slice }}"

- uses: actions/upload-artifact@v7
with:
name: v8-ios-${{ matrix.variant }}
path: v8-*-ios-${{ matrix.variant }}.tar.gz
name: v8-${{ steps.build.outputs.slice }}
path: v8-*-${{ steps.build.outputs.slice }}.tar.gz
retention-days: 7

release:
needs: [config, android, ios]
if: startsWith(github.ref, 'refs/tags/v8-')
needs: [config, android, apple]
# A pushed v8-* tag releases automatically; a manual run releases only when
# it was given a release_tag, so a finished matrix can be published without
# rebuilding it.
#
# The condition has to be spelled out rather than left to the default: a
# skipped dependency skips this job too, and `platforms: ios` legitimately
# skips the android job. !cancelled() re-enables it, so the failure check
# has to be explicit or a partial matrix would publish an incomplete release.
if: >-
!cancelled()
&& !contains(needs.*.result, 'failure')
&& (startsWith(github.ref, 'refs/tags/v8-') || inputs.release_tag != '')
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
Expand Down Expand Up @@ -185,7 +226,10 @@ jobs:
echo
echo "Verify with \`sha256sum -c SHA256SUMS\`. The gn args are in \`scripts/matrix/build-*.sh\`."
} > notes.md
gh release create "${GITHUB_REF_NAME}" \
--title "${GITHUB_REF_NAME}" \
TAG="${{ inputs.release_tag || github.ref_name }}"
gh release create "$TAG" \
--title "$TAG" \
--target "${GITHUB_SHA}" \
--latest \
--notes-file notes.md \
artifacts/*.tar.gz artifacts/SHA256SUMS
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,17 @@ from a pinned revision, with the gn args under review, is the point of this repo

## Matrix

| Platform | Targets | Runner |
|---|---|---|
| Android | `arm64-v8a`, `x86_64`, `armeabi-v7a`, `x86` | `ubuntu-24.04` |
| Apple | `arm64-device`, `arm64-simulator`, `x64-simulator`, `arm64-catalyst`, `x64-catalyst` | `macos-15` |
| Platform | Targets | Runner | `platforms` |
|---|---|---|---|
| Android | `arm64-v8a`, `x86_64`, `armeabi-v7a`, `x86` | `ubuntu-24.04` | `android` |
| iOS | `arm64-device`, `arm64-simulator`, `x64-simulator`, `arm64-catalyst`, `x64-catalyst` | `macos-15` | `ios` |
| macOS | `arm64-macos`, `x64-macos` | `macos-15` | `macos` |

iOS and macOS share one job — same runner, same checkout, same script — but are
separately selectable. Dispatching with `platforms: macos` builds the two macOS
variants and nothing else; `all` (the default, and what every non-dispatch event
uses) builds all eleven targets. The variant list is computed in the `config`
job, so an unselected family produces no jobs at all rather than jobs that skip.

Each target is its own job with its own checkout. That costs one `gclient sync`
per job, but the sync is about 6 minutes against 90–145 minutes of compile, so
Expand All @@ -50,6 +57,23 @@ This replaces the previous arrangement of one branch per architecture. Nine
branches meant nine places to apply a V8 bump or a patch, and cutting a release
needs a single ref to tag.

### macOS is a target_os, not a target_environment

The iOS variants select an environment (`device`, `simulator`, `catalyst`) under
`target_os="ios"`. macOS is `target_os="mac"` with no environment at all —
passing `target_environment` there is an error rather than a no-op — so the
macOS variants set it empty and the script omits the arg.

Like catalyst, macOS permits JIT and so is deliberately **not** lite mode. It
does keep the rest of the shared arg set, `cppgc_enable_caged_heap=false`
included: a release ships one `include/` for every slice, and that flag changes
`Member<T>`'s size, so a slice built with the cage would be an ABI mismatch
against the headers every other slice shares.

Assets are named `macos-<cpu>` rather than `ios-<variant>`; `build-ios.sh`
emits the resolved slice name as a step output so the workflow does not have to
recompute it.

### visionOS needs no build of its own

V8's build system has no visionOS support — `target_platform` accepts only
Expand Down Expand Up @@ -114,14 +138,15 @@ scripts/matrix/build-android.sh --abi arm64-v8a --ndk-root "$ANDROID_HOME/ndk/<v

scripts/matrix/fetch.sh --platform ios
scripts/matrix/build-ios.sh --variant arm64-device
scripts/matrix/build-ios.sh --variant arm64-macos # same checkout as iOS
```

Sources land in `.v8/`, output in `dist/<platform>-<target>/`. The two platforms
need different DEPS, so use separate `--v8-dir` checkouts to keep both without
re-syncing.

To iterate on one platform in CI without rebuilding the other (~2.5 hours per
platform), dispatch the workflow with `platforms: android` or `platforms: ios`.
To iterate on one platform in CI without rebuilding the others (~2.5 hours per
platform), dispatch the workflow with `platforms: android`, `ios` or `macos`.

## Patches

Expand Down
2 changes: 2 additions & 0 deletions config.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ ANDROID_NDK_RELEASE=r29
IOS_DEPLOYMENT_TARGET=13.0
# arm64 macabi does not exist below 14.0.
CATALYST_DEPLOYMENT_TARGET=14.0
# Keep in step with the runtime's CMAKE_OSX_DEPLOYMENT_TARGET.
MACOS_DEPLOYMENT_TARGET=13.3
5 changes: 5 additions & 0 deletions scripts/matrix/build-android.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ GN_ARGS="
v8_enable_i18n_support=false
v8_enable_webassembly=true
v8_enable_sandbox=false
# Off deliberately, and not just to match the Apple slices. Pointer
# compression brings the sandbox's restrictions on external memory, which
# the runtime needs for ArrayBuffer backing stores. It defaults to ON for
# 64-bit targets, so leaving it unset is not the same as disabling it.
v8_enable_pointer_compression=false
v8_enable_temporal_support=false
v8_enable_v8_checks=false
v8_enable_debugging_features=false
Expand Down
60 changes: 50 additions & 10 deletions scripts/matrix/build-ios.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#!/bin/bash
set -e
#
# Builds the V8 static libraries for one Apple variant into dist/ios-<variant>/.
# Builds the V8 static libraries for one Apple variant into dist/<slice>/.
#
# Usage: build-ios.sh --variant <variant> [--v8-dir <path>] [-- <ninja args>]
#
# Variants: arm64-device, arm64-simulator, x64-simulator,
# arm64-catalyst, x64-catalyst
# arm64-catalyst, x64-catalyst, arm64-macos, x64-macos
#
# visionOS has no variant of its own: V8's target_platform accepts only
# iphoneos/tvos, and it needs none. The platform tag on a member of a *static*
# archive is advisory -- only the final linked image carries LC_BUILD_VERSION --
# so a visionOS binary links the iOS archives directly. Consumers map
# arm64-device onto xros and arm64-simulator onto xrsimulator.
#
# Unlike Android, V8 has no monolith target here -- the iOS runtime links a set
# of per-module archives, so each is rebuilt from its objects. ninja emits thin
Expand All @@ -26,7 +32,7 @@ usage() {
Usage: $(basename "$0") --variant <variant> [--v8-dir <path>] [-- <ninja args>]

--variant arm64-device | arm64-simulator | x64-simulator
| arm64-catalyst | x64-catalyst
| arm64-catalyst | x64-catalyst | arm64-macos | x64-macos
EOF
}

Expand All @@ -42,15 +48,27 @@ while [ $# -gt 0 ]; do
esac
done

# TARGET_ENV is an iOS notion (target_environment); macOS is target_os="mac"
# with no environment, so it is left empty and keyed off TARGET_OS instead.
TARGET_OS=ios
case "$VARIANT" in
arm64-device) CPU=arm64; TARGET_ENV=device ;;
arm64-simulator) CPU=arm64; TARGET_ENV=simulator ;;
x64-simulator) CPU=x64; TARGET_ENV=simulator ;;
arm64-catalyst) CPU=arm64; TARGET_ENV=catalyst ;;
x64-catalyst) CPU=x64; TARGET_ENV=catalyst ;;
arm64-macos) CPU=arm64; TARGET_ENV=""; TARGET_OS=mac ;;
x64-macos) CPU=x64; TARGET_ENV=""; TARGET_OS=mac ;;
*) echo "Invalid --variant '$VARIANT'" >&2; usage >&2; exit 1 ;;
esac

# Release slice name, also the dist/ directory and the asset stem.
if [ "$TARGET_OS" = "mac" ]; then
SLICE="macos-$CPU"
else
SLICE="ios-$VARIANT"
fi

[ "$(uname)" = "Darwin" ] || { echo "Apple variants require a macOS host." >&2; exit 1; }

MODULES=(
Expand Down Expand Up @@ -82,13 +100,12 @@ MODULES=(
# -- four tries, then Oilpan's fatal OOM handler, before any JS runs. It crashed
# on an iPad Air while an iPad Pro was fine.
GN_ARGS="
target_os=\"ios\"
target_os=\"$TARGET_OS\"
treat_warnings_as_errors=false
icu_use_data_file=false
use_custom_libcxx=false
is_component_build=false
is_debug=false
ios_enable_code_signing=false
v8_control_flow_integrity=false
v8_monolithic=false
v8_static_library=true
Expand All @@ -104,21 +121,40 @@ GN_ARGS="
symbol_level=0
"

if [ "$TARGET_ENV" = "catalyst" ]; then
# is_official_build here implies ThinLTO, which emits bitcode rather than
# object code and cannot be vendored, hence the explicit override.
if [ "$TARGET_OS" = "mac" ]; then
# macOS permits JIT, so like catalyst this is deliberately not lite mode.
# is_official_build implies ThinLTO, which emits bitcode rather than object
# code and cannot be vendored, hence the explicit override.
#
# It also defaults chrome_pgo_phase to 2 on desktop targets, which makes gn
# exec_script //tools/update_pgo_profiles.py at generation time. That script
# is Chromium's and does not exist in a standalone V8 checkout, so gn fails
# outright. iOS/catalyst never hit this because PGO is off for iOS targets.
GN_ARGS="$GN_ARGS
is_official_build=true
use_thin_lto=false
chrome_pgo_phase=0
mac_deployment_target=\"$MACOS_DEPLOYMENT_TARGET\""
elif [ "$TARGET_ENV" = "catalyst" ]; then
GN_ARGS="$GN_ARGS
is_official_build=true
use_thin_lto=false
ios_enable_code_signing=false
ios_deployment_target=\"$CATALYST_DEPLOYMENT_TARGET\""
else
GN_ARGS="$GN_ARGS
v8_enable_lite_mode=true
ios_enable_code_signing=false
ios_deployment_target=\"$IOS_DEPLOYMENT_TARGET\""
fi

# target_environment is an iOS-only gn arg; passing it for target_os="mac"
# is an error rather than a no-op.
TARGET_ENV_ARG=""
[ -n "$TARGET_ENV" ] && TARGET_ENV_ARG="target_environment=\"$TARGET_ENV\""

OUTFOLDER="out.gn/$VARIANT-release"
DIST="$ROOT_DIR/dist/ios-$VARIANT"
DIST="$ROOT_DIR/dist/$SLICE"

cd "$V8_DIR"

Expand Down Expand Up @@ -146,7 +182,7 @@ archive_lib() {
}

rm -rf "$OUTFOLDER"
gn gen "$OUTFOLDER" --args="$GN_ARGS target_environment=\"$TARGET_ENV\" target_cpu=\"$CPU\" v8_target_cpu=\"$CPU\""
gn gen "$OUTFOLDER" --args="$GN_ARGS $TARGET_ENV_ARG target_cpu=\"$CPU\" v8_target_cpu=\"$CPU\""

echo "Building $VARIANT: $(date)"
ninja "${NINJA_ARGS[@]}" -C "$OUTFOLDER" "${MODULES[@]}" inspector
Expand Down Expand Up @@ -186,3 +222,7 @@ cp "$OUTFOLDER/gen/include/inspector/"*.h "$DIST/include/inspector/"

echo "$V8_VERSION" > "$DIST/V8_VERSION"
du -sh "$DIST/lib"

# The dist directory and asset stem differ from the variant for macOS, so hand
# the resolved name back rather than recomputing it in the workflow.
[ -n "$GITHUB_OUTPUT" ] && echo "slice=$SLICE" >> "$GITHUB_OUTPUT"
Loading