diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index d42b08815..a90c83c77 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -15,7 +15,7 @@ on: env: NPM_TAG: "next" EMULATOR_NAME: "runtime-emu" - NDK_VERSION: r27d + NDK_VERSION: r29 ANDROID_API: 33 ANDROID_ABI: x86_64 NDK_ARCH: linux @@ -93,6 +93,8 @@ jobs: NPM_TAG=$(node ./scripts/get-npm-tag.js) echo NPM_VERSION=$NPM_VERSION >> $GITHUB_OUTPUT echo NPM_TAG=$NPM_TAG >> $GITHUB_OUTPUT + - name: Fetch prebuilt V8 + run: ./download_v8.sh - name: Build npm package run: ./gradlew -PgitCommitVersion=${{ github.sha }} -PnoCCache --stacktrace - name: Upload npm package artifact @@ -144,6 +146,8 @@ jobs: run: | npm install npm install --prefix ./test-app/tools + - name: Fetch prebuilt V8 + run: ./download_v8.sh - name: SBG tests run: ./gradlew runSbgTests --stacktrace - name: Enable KVM diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index aaa36a3eb..8fddbf676 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -5,7 +5,7 @@ on: env: NPM_TAG: "pr" EMULATOR_NAME: "runtime-emu" - NDK_VERSION: r27d + NDK_VERSION: r29 ANDROID_API: 33 ANDROID_ABI: x86_64 NDK_ARCH: linux @@ -67,6 +67,8 @@ jobs: NPM_TAG=$(node ./scripts/get-npm-tag.js) echo NPM_VERSION=$NPM_VERSION >> $GITHUB_OUTPUT echo NPM_TAG=$NPM_TAG >> $GITHUB_OUTPUT + - name: Fetch prebuilt V8 + run: ./download_v8.sh - name: Build npm package run: ./gradlew -PgitCommitVersion=${{ github.sha }} -PnoCCache --stacktrace - name: Upload npm package artifact @@ -112,6 +114,8 @@ jobs: run: | npm install npm install --prefix ./test-app/tools + - name: Fetch prebuilt V8 + run: ./download_v8.sh - name: SBG tests run: ./gradlew runSbgTests --stacktrace - name: Enable KVM diff --git a/.gitignore b/.gitignore index 58cff5e30..67618ca90 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,20 @@ thumbs.db android-runtime.iml test-app/build-tools/*.log test-app/analytics/build-statistics.json -package-lock.json \ No newline at end of file +package-lock.json + +# V8 prebuilts and the headers that must match them, installed by +# download_v8.sh from the release pinned in V8_RELEASE. +# +# The headers are ignored along with the libraries on purpose: keeping a +# vendored copy in git is how it drifts out of step with the binaries it +# describes. Sourcing both from one verified artifact makes that impossible. +/.v8-prebuilt/ +test-app/runtime/src/main/libs/*/libv8_monolith.a +test-app/runtime/src/main/libs/.v8-release-stamp +test-app/runtime/src/main/cpp/include/** +!test-app/runtime/src/main/cpp/include/zip.h +!test-app/runtime/src/main/cpp/include/zipconf.h +test-app/runtime/src/main/cpp/v8_inspector/src/ +test-app/runtime/src/main/cpp/v8_inspector/third_party/ +test-app/runtime/src/main/cpp/v8_inspector/absl/ diff --git a/V8_RELEASE b/V8_RELEASE new file mode 100644 index 000000000..29a466814 --- /dev/null +++ b/V8_RELEASE @@ -0,0 +1 @@ +v8-14.9.207.39-1 diff --git a/build.sh b/build.sh index 2f42b49c9..b235d117c 100755 --- a/build.sh +++ b/build.sh @@ -11,6 +11,9 @@ adb version echo "Update submodule" git submodule update --init +echo "Fetch the prebuilt V8 (no-op once in place; V8_SKIP_DOWNLOAD=1 to skip)" +./download_v8.sh + echo "Cleanup old build and test artefacts" rm -rf consoleLog.txt rm -rf test-app/dist/*.xml diff --git a/docs/README.md b/docs/README.md index 761f8d702..cb09a6942 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,3 +2,12 @@ - [Error handling](error-handling.md) — global `error`/`unhandledrejection` events, `reportError`, catching Java exceptions in JS (`error.nativeException`), forwarding JS throws to Java callers (`interop.escapeException`), JS stacks on Java exceptions (`com.tns.JavaScriptStackTrace`), configuration flags, and crash-reporter integration. - [Implementing additional Chrome DevTools protocol Domains](extending-inspector.md) + +## Knowledge + +Notes on work that is done, kept because the reasoning is expensive to +reconstruct rather than because anything needs doing. + +- [V8 10.3 → 14.9 migration](knowledge/v8-14-migration.md) — the API changes and + their site counts, why each non-default gn arg exists, the accessor rules that + are not mechanical, and the traps that only show up at runtime. diff --git a/docs/knowledge/v8-14-migration.md b/docs/knowledge/v8-14-migration.md new file mode 100644 index 000000000..2fdca0cb5 --- /dev/null +++ b/docs/knowledge/v8-14-migration.md @@ -0,0 +1,234 @@ +# V8 10.3 → 14.9 migration notes (Android) + +Pinned version: **14.9.207.39** (`branch-heads/14.9`). +The libraries are built by [NativeScript/v8-buildscripts](https://github.com/NativeScript/v8-buildscripts) +and installed here by `download_v8.sh` from the release pinned in `V8_RELEASE`. The iOS runtime moved to the same +version; where the two runtimes hit the same API change the notes are kept in +sync. + +## What ships + +`libv8_monolith.a` per ABI under `test-app/runtime/src/main/libs//`, plus +the public headers under `test-app/runtime/src/main/cpp/include/` and the +vendored V8 internals under `test-app/runtime/src/main/cpp/v8_inspector/`. +All three come from the same release artifact; they are a matched set and +must never be updated separately. + +## Build configuration + +The gn args reproduce the 10.3 build: JIT and WebAssembly on, i18n off. What is +new, and why: + +- **`use_allocator_shim=false`** — the shim interposes on `malloc` through + linker `--wrap` flags that the embedder would also have to pass. Without it + the link fails on `__real_realpath` / `__real_getcwd`. PartitionAlloc itself + stays enabled (V8 depends on the target); it just no longer replaces malloc. +- **`use_thin_lto=false`** — `is_official_build` now turns ThinLTO on, and a + ThinLTO build emits LLVM bitcode rather than object code, which the app's NDK + cannot link. +- **`chrome_pgo_phase=0`** — `is_official_build` also turns PGO on, and + standalone V8 has no `tools/update_pgo_profiles.py`. +- **`v8_enable_temporal_support=false`** — Temporal is implemented in Rust and + pulls in a Rust sysroot this build does not link. 10.3 had no Temporal. +- **`v8_array_buffer_internal_field_count=2`** and + **`v8_array_buffer_view_internal_field_count=2`** — both default to `0` in + 14.9 and defaulted to `2` in 10.3. When an `ArrayBuffer`/`SharedArrayBuffer`/ + typed array is marshalled to a Java NIO buffer, + `JSToJavaObjectsConverter` calls `ObjectManager::Link` on the buffer object + itself, and `Link` stores its `JSInstanceInfo` in internal field 0. With zero + internal fields every such conversion throws *"Trying to link invalid 'this' + to a Java object"*. `v8-array-buffer.h` still falls back to `2` when the macro + is undefined, which is what the runtime compiles against, so leaving the gn + default in place also puts the two sides out of agreement. +- **`v8_enable_sandbox=false`** — pinned rather than left to default. It would + otherwise follow pointer compression (on for 64-bit) and change the object + layout the runtime compiles against, which is a much larger change than a + version bump. +- **`android_ndk_root`** pinned to the NDK the runtime is built with — see + below. + +The build deletes its output directory before each run. ninja never +removes outputs orphaned by a config change, so reusing one across V8 versions +silently keeps stale objects, and the packaging step would vendor them. + +### The NDK has to match on both sides + +V8's bundled NDK (CIPD `30.0.14608247`) is newer than any released one. Its +libc++ exports symbols the runtime's `libc++_static.a` does not — the link fails +on `std::__ndk1::__hash_memory`, referenced from `liveedit.cc`. `libc++` is only +ABI-compatible with itself across a static link, so V8 is built against the same +NDK the runtime uses, via the `android_ndk_root` gn arg (made overridable by +`android_build.patch`). + +**The runtime moved from NDK r27d to r29.** This is forced, not optional: V8 +14.9's `src/base/atomicops.h` uses `std::atomic_ref` unconditionally, and +r27d ships libc++ 18, which does not implement it (`__cpp_lib_atomic_ref` is +commented out in its ``). The runtime compiles those headers because +`v8_inspector` vendors V8 internals. r29 is the first released NDK with a +libc++ new enough. `minSdk` is unchanged at 21. + +### Building on macOS + +Chromium asserts a Linux host for Android targets. Everything below that assert +still handles macOS — the host-arch block maps arm64 hosts to the +`darwin-x86_64` NDK tag deliberately, and `android_toolchain_root` is only read +for the (host-independent) sysroot — so `android_build.patch` relaxes +the assert. It also lowers `min_supported_sdk_version` from 23 to 21; that floor +exists for Java/dex tooling and this build produces only the native +`v8_monolith` target. + +Two things the macOS clang package does not carry, both handled by buildscripts' `fetch_v8.sh`: + +- the Android **compiler-rt builtins** (`libclang_rt.builtins-*-android.a`), + which only the Linux clang package bundles — they are extracted from it; +- a `darwin-x86_64` directory in the CIPD NDK, which only ships `linux-x86_64` — + symlinked (only relevant when `android_ndk_root` is left at its default). + +Because not every ABI can be rebuilt on every host, the gradle builds accept +`-Pabis=arm64-v8a,x86_64` to restrict `abiFilters` to the ones that have a +current `libv8_monolith.a`. + +**The 32-bit ABIs cannot be built on an Apple Silicon host.** `armeabi-v7a` and +`x86` need mksnapshot to run V8's simulator for a 32-bit target, and +`v8config.h` hard-errors with *"Target architecture arm is only supported on arm +and ia32 host"*. The Linux x64 path is unaffected — it builds mksnapshot as a +32-bit x86 host binary — so those two ABIs have to come from a Linux x64 +builder, which is where CI builds them anyway. + +## API changes applied to the runtime + +| Change | Sites | Migration | +|---|---|---| +| `Context/Object/Function/Promise/Message::GetIsolate()` removed | 46 | `v8::Isolate::GetCurrent()` | +| `External::New` / `External::Value()` take a type tag | 47 | `v8::kExternalPointerTypeTagDefault` | +| `PropertyCallbackInfo::This()` removed | 25 | `Holder()`, or a function-backed accessor — see below | +| `Object/ObjectTemplate/Function::SetAccessor` | 21 | `SetNativeDataProperty` / `SetAccessorProperty` | +| Accessor callbacks take `Local` | 23 | was `Local` | +| `Object::CreationContext()` | 7 | `GetCreationContext(isolate).ToLocalChecked()` | +| `ScriptOrigin` no longer takes an `Isolate*` | 8 | drop the first argument | +| `AccessControl` removed | 5 | drop the argument | +| `GetInternalField` returns `Local` | 4 | `.As()` | +| Interceptor callbacks return `v8::Intercepted` | 2 | see below | +| `SetIndexedPropertyHandler` | 1 | `SetHandler(IndexedPropertyHandlerConfiguration(...))` | +| `V8Inspector::connect` needs a trust level | 3 | `kFullyTrusted` | +| `V8ConsoleMessage::createForConsoleAPI` takes a span | 2 | `{args.data(), args.size()}` | +| `FunctionCallbackInfo` is no longer copyable | 1 | `ArgsWrapper` holds a reference | + +`unistd.h` also has to be included explicitly in `ModuleInternal.cpp` and +`WorkerWrapper.cpp`; `usleep`/`read` used to arrive transitively through headers +that no longer pull it in. + +### Accessors that are inherited need a real accessor pair + +`PropertyCallbackInfo` no longer exposes the receiver at all, and +`SetNativeDataProperty` is not a drop-in replacement for `SetAccessor` on +anything that is inherited from. Six accessors in `MetadataNode` are installed +on an object other than the one they are read through: + +- on the **constructor function**, which derived constructors inherit: + `class`, `nullObject`, and static fields; +- on the **implementation object**, which instances inherit: `super`; +- on the **prototype template**, which instances inherit: instance fields and + properties. + +All six are now `SetAccessorProperty` with `FunctionTemplate`-backed +getter/setter, whose `FunctionCallbackInfo::This()` still returns the receiver. +Static fields matter most: they have a setter, and `SetNativeDataProperty` +installs something data-like, so `Derived.baseField = x` would shadow the base's +property with an own data property and never reach the native setter. + +The accessors that stay `SetNativeDataProperty` are the ones installed as own +properties on the object they are read through — the array wrapper's `length`, +the package object's children, inner types on a constructor, and the `URL*` +instance templates. For those `Holder() == This()`. + +The rule of thumb: if an accessor lives on anything that is inherited from, use +`SetAccessorProperty` with function-backed callbacks. `SetNativeDataProperty` is +only safe where nothing inherits it. + +This costs an allocation the old API did not: each converted accessor now needs +a real `Function` object rather than an `AccessorInfo`. On the prototype +template that is deferred to instantiation, but static fields are materialised +eagerly, so a class with many static fields pays for them when its constructor +function is first built. + +#### One guard could not be translated directly + +`FieldAccessorGetter/SetterCallback` used `thiz->StrictEquals(info.Holder())` to +detect an instance field being read straight off the prototype. Function-backed +accessors have no holder, so the check is now +`!objectManager->IsJsRuntimeObject(thiz)` — being a runtime-managed object is +the property that actually distinguishes an instance from the prototype. The +two agree for every receiver the old check could see; the new one additionally +returns `undefined` (rather than reaching `GetJavaField` with a non-instance) +for something like `Object.create(SomeClass.prototype).field`. + +### Interceptors + +Only one interceptor pair exists here — the array wrapper's indexed +getter/setter. Both handle the access completely, so both return +`Intercepted::kYes`; neither ever fell through to the ordinary lookup. The +setter's `PropertyCallbackInfo` also changed from `` to ``, so +its old `GetReturnValue().Set(value)` is dropped — the return value is now the +strict-mode success flag, not the stored value. + +The conversion rule in general: a path that set a return value or threw becomes +`kYes`; a path that returned without setting one, **including falling off the +end**, becomes `kNo`. Getting it backwards is silent in both directions. + +### V8 flags must be set before `V8::Initialize()` + +`V8::Initialize()` calls `FlagList::FreezeFlags()`, and changing a flag +afterwards aborts the process. `PrepareV8Runtime` used to apply +`Constants::V8_STARTUP_FLAGS` per isolate, after initialization; it now happens +once in `InitializeV8()`, before `V8::Initialize()`. `Runtime::Init` has already +read the flags out of the Java config by then. + +## Things that did *not* need changing + +- **Resurrecting finalizers.** `ObjectManager` uses + `WeakCallbackType::kFinalizer` in four places. Upstream removed it right after + 10.3.22; buildscripts' `v8_resurrecting_finalizers.patch` restores it. See + the iOS runtime's `V8_RESURRECTING_FINALIZERS.md` for the patch design. +- **Teardown disposal.** The runtime never used + `Isolate::VisitHandlesWithClassIds` or `SetWrapperClassId`, so the registry + the iOS runtime had to grow is not needed here. + +## Test status + +The runtime test suite on an API 35 arm64 emulator: **594 tests, 0 failures, +0 errors, 5 skipped.** All five skips are pre-existing `xit()` in the checked-in +suite (`testNativeModules`, `exceptionHandlingTests` SIGABRT, `testArrays` +memory leak, `TNS require` index.json, `TNS Workers` circular postMessage). + +### Inspector + +Verified end to end against the debug build over the runtime's WebSocket +(`-inspectorServer`, an abstract local socket): `Runtime.enable` +(execution context creation and replay of stored console messages, with stack +traces), `Runtime.evaluate` for arithmetic, object serialisation and Java +interop, `Debugger.enable`, and exception reporting through `exceptionDetails`. +This is the part most exposed to a stale `v8_inspector` tree, since it compiles +against V8 internals rather than the public API. + +## Known follow-ups + +- `armeabi-v7a` and `x86` still hold 10.3 libraries; they need a Linux x64 + builder (see above). +- **`V8_STATIC_ROOTS` is deliberately not defined by the runtime.** V8 is built + with it, and it would let `Value::IsUndefined()`/`IsNull()` and friends use + the inline static-root comparison instead of reading the map. It is left off + because the root addresses are hardcoded constants in `v8-internal.h` that + must match the library exactly (`kBuildDependentTheHoleValue` even varies with + `V8_ENABLE_WEBASSEMBLY`), and 10.3 had no such fast path either — so this is + parity, and enabling it is a measurable but separate change. +- On-device compiled-code caches written by 10.3 are stale. `TryLoadScriptCache` + validates them only by comparing mtimes with the `.js` file, so an app update + (which rewrites the scripts) discards them and they are regenerated. If the + mtimes did happen to match, V8 rejects the data by its own version hash and + recompiles from source — correct, but `SaveScriptCache` is only called on the + no-cache path, so that file would never be refreshed. Pre-existing behaviour, + newly reachable on a version bump. +- Maglev is enabled by default in 14.9 and did not exist in 10.3, so the + monolith carries a whole extra compiler tier. `v8_enable_maglev=false` is the + lever if the size matters more than the warm-up performance. diff --git a/download_v8.sh b/download_v8.sh new file mode 100755 index 000000000..36e3a7997 --- /dev/null +++ b/download_v8.sh @@ -0,0 +1,148 @@ +#!/bin/bash +set -euo pipefail +# +# Installs the prebuilt V8 libraries and headers for the pinned release. +# +# The artifacts are built by NativeScript/v8-buildscripts and published as +# GitHub release assets; they are not committed here because two of the four +# monoliths exceed GitHub's 100 MiB per-file push limit, and because the full +# matrix cannot be produced on any single machine. +# +# Deliberately a standalone script rather than a Gradle task: it is a +# prerequisite you run once, trivial to skip when you already have the +# artifacts, and easy to override with a local V8 build. +# +# Set V8_SKIP_DOWNLOAD=1 to make it a no-op -- use that when you have built V8 +# yourself and do not want a pinned release overwriting it. +# +# Usage: download_v8.sh [--release ] [--abi ]... [--force] +# + +REPO_ROOT="$(cd "$(dirname "$0")" && pwd)" +UPSTREAM="NativeScript/v8-buildscripts" +RELEASE_FILE="$REPO_ROOT/V8_RELEASE" +CACHE_DIR="${V8_PREBUILT_CACHE:-$REPO_ROOT/.v8-prebuilt}" + +CPP_DIR="$REPO_ROOT/test-app/runtime/src/main/cpp" +LIBS_DIR="$REPO_ROOT/test-app/runtime/src/main/libs" +STAMP="$LIBS_DIR/.v8-release-stamp" + +RELEASE="" +FORCE=0 +ABIS=() + +usage() { + cat <] [--abi ]... [--force] + + --release Release to install (default: contents of V8_RELEASE) + --abi Repeatable. armeabi-v7a, arm64-v8a, x86, x86_64 + (default: all four) + --force Reinstall even if the pinned release is already in place + +Downloads are cached in $CACHE_DIR (override with \$V8_PREBUILT_CACHE). +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --release) RELEASE="$2"; shift 2 ;; + --release=*) RELEASE="${1#*=}"; shift ;; + --abi) ABIS+=("$2"); shift 2 ;; + --abi=*) ABIS+=("${1#*=}"); shift ;; + --force) FORCE=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;; + esac +done + +if [ "${V8_SKIP_DOWNLOAD:-0}" != "0" ]; then + echo "V8_SKIP_DOWNLOAD is set; leaving the libraries and headers alone." + exit 0 +fi + +if [ -z "$RELEASE" ]; then + [ -f "$RELEASE_FILE" ] || { echo "Missing $RELEASE_FILE" >&2; exit 1; } + RELEASE="$(tr -d '[:space:]' < "$RELEASE_FILE")" +fi +[ ${#ABIS[@]} -gt 0 ] || ABIS=(arm64-v8a armeabi-v7a x86_64 x86) + +if [ "$FORCE" = "0" ] && [ -f "$STAMP" ] && [ "$(cat "$STAMP")" = "$RELEASE" ]; then + echo "V8 $RELEASE already installed. Use --force to reinstall." + exit 0 +fi + +BASE_URL="https://github.com/$UPSTREAM/releases/download/$RELEASE" +DL="$CACHE_DIR/$RELEASE" +mkdir -p "$DL" + +fetch() { + local name="$1" + if [ -f "$DL/$name" ]; then return 0; fi + echo " downloading $name" + curl -fSL --retry 3 -o "$DL/$name.part" "$BASE_URL/$name" + mv "$DL/$name.part" "$DL/$name" +} + +echo "Installing V8 $RELEASE from $UPSTREAM" +fetch SHA256SUMS + +ASSETS=() +for ABI in "${ABIS[@]}"; do + ASSETS+=("$(grep -oE "v8-[^ ]*-android-$ABI\.tar\.gz" "$DL/SHA256SUMS" | head -1)") +done +ASSETS+=("$(grep -oE 'v8-[^ ]*-src-headers\.tar\.gz' "$DL/SHA256SUMS" | head -1)") + +for a in "${ASSETS[@]}"; do + [ -n "$a" ] || { echo "Release $RELEASE is missing an expected asset." >&2; exit 1; } + fetch "$a" +done + +# Verify before unpacking anything. A release is only trustworthy because the +# archive matches the checksum published with it. +# +# Linux has sha256sum, macOS has shasum; neither has both reliably. +if command -v sha256sum > /dev/null 2>&1; then + SHA256_CHECK="sha256sum -c -" +else + SHA256_CHECK="shasum -a 256 -c -" +fi +echo "Verifying checksums" +( cd "$DL" && grep -E "$(printf '%s|' "${ASSETS[@]}" | sed 's/|$//')" SHA256SUMS | $SHA256_CHECK ) \ + || { echo "Checksum verification FAILED for $RELEASE" >&2; exit 1; } + +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT +for a in "${ASSETS[@]}"; do tar -xzf "$DL/$a" -C "$STAGE"; done + +echo "Installing libraries" +for ABI in "${ABIS[@]}"; do + src="$STAGE/android-$ABI/lib/libv8_monolith.a" + [ -f "$src" ] || { echo "Archive for $ABI has no libv8_monolith.a" >&2; exit 1; } + mkdir -p "$LIBS_DIR/$ABI" + cp "$src" "$LIBS_DIR/$ABI/libv8_monolith.a" +done + +echo "Installing public headers" +# zip.h/zipconf.h belong to libzip and live in the same directory, so the V8 +# headers are replaced selectively rather than by wiping include/. +FIRST_ABI="${ABIS[0]}" +SRC_INC="$STAGE/android-$FIRST_ABI/include" +[ -d "$SRC_INC" ] || { echo "Archive has no include/" >&2; exit 1; } +for entry in cppgc libplatform inspector; do + rm -rf "${CPP_DIR:?}/include/$entry" +done +find "$CPP_DIR/include" -maxdepth 1 -type f \ + ! -name 'zip.h' ! -name 'zipconf.h' -delete +cp -R "$SRC_INC/." "$CPP_DIR/include/" + +echo "Vendoring the inspector's V8 internals" +# The closure is computed here rather than shipped, because what the glue +# includes is this repo's business, not the build repo's. +python3 "$REPO_ROOT/tools/v8/vendor_inspector_sources.py" \ + --v8-dir "$STAGE/src-headers" \ + --gen-dir "$STAGE/src-headers" \ + --dest "$CPP_DIR/v8_inspector" + +echo "$RELEASE" > "$STAMP" +echo "Installed V8 $RELEASE" diff --git a/package.json b/package.json index d31acb66c..3e1ae3033 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "**/*" ], "version_info": { - "v8": "10.3.22.0", + "v8": "14.9.207.39", "gradle": "8.14.3", "gradleAndroid": "8.12.1", - "ndk": "r27d", + "ndk": "r29", "ndkApiLevel": "21", "minSdk": "21", "compileSdk": "35", diff --git a/test-app/app/build.gradle b/test-app/app/build.gradle index 43d5eea96..be2d527fe 100644 --- a/test-app/app/build.gradle +++ b/test-app/app/build.gradle @@ -42,6 +42,9 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' def onlyX86 = project.hasProperty("onlyX86") +// -Pabis=arm64-v8a,x86_64 restricts the build to those ABIs. Needed because the +// 32-bit V8 monoliths can only be produced on a Linux x64 host. +def selectedAbis = project.hasProperty("abis") ? project.property("abis").split(",").collect { it.trim() } : null if (onlyX86) { outLogger.withStyle(Style.Info).println "OnlyX86 build triggered." } @@ -225,7 +228,9 @@ android { minSdkVersion minSdkVer targetSdkVersion computeTargetSdkVersion() ndk { - if (onlyX86) { + if (selectedAbis != null) { + abiFilters selectedAbis as String[] + } else if (onlyX86) { abiFilters 'x86' } else { abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' diff --git a/test-app/runtime/build.gradle b/test-app/runtime/build.gradle index f48b814d7..9b8bb57d1 100644 --- a/test-app/runtime/build.gradle +++ b/test-app/runtime/build.gradle @@ -11,6 +11,9 @@ if (optimizedWithInspector) { } def onlyX86 = project.hasProperty("onlyX86") +// -Pabis=arm64-v8a,x86_64 restricts the build to those ABIs. Needed because the +// 32-bit V8 monoliths can only be produced on a Linux x64 host. +def selectedAbis = project.hasProperty("abis") ? project.property("abis").split(",").collect { it.trim() } : null if (onlyX86) { println "OnlyX86 build triggered." } @@ -21,7 +24,7 @@ if (useCCache) { } -def defaultNdkVersion = "27.3.13750724" +def defaultNdkVersion = "29.0.14206865" def hasNdkVersion = project.hasProperty("ndkVersion") if (hasNdkVersion) { @@ -120,7 +123,9 @@ android { ndk { minSdkVersion NS_DEFAULT_MIN_SDK_VERSION as int - if (onlyX86) { + if (selectedAbis != null) { + abiFilters selectedAbis as String[] + } else if (onlyX86) { abiFilters 'x86' } else { abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' diff --git a/test-app/runtime/src/main/cpp/ArgConverter.cpp b/test-app/runtime/src/main/cpp/ArgConverter.cpp index a8ebe3d66..18cc680b1 100644 --- a/test-app/runtime/src/main/cpp/ArgConverter.cpp +++ b/test-app/runtime/src/main/cpp/ArgConverter.cpp @@ -14,7 +14,7 @@ using namespace std; using namespace tns; void ArgConverter::Init(Local context) { - Isolate* isolate = context->GetIsolate(); + Isolate* isolate = v8::Isolate::GetCurrent(); auto cache = GetTypeLongCache(isolate); auto ft = FunctionTemplate::New(isolate, ArgConverter::NativeScriptLongFunctionCallback); @@ -92,7 +92,7 @@ Local ArgConverter::ConvertJavaArgsToJsArgs(Local context, jobje JEnv env; int argc = env.GetArrayLength(args) / 3; - auto isolate = context->GetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); Local arr(Array::New(isolate, argc)); auto runtime = Runtime::GetRuntime(isolate); diff --git a/test-app/runtime/src/main/cpp/ArgsWrapper.h b/test-app/runtime/src/main/cpp/ArgsWrapper.h index 7ad6fb8f5..f514bf9a8 100644 --- a/test-app/runtime/src/main/cpp/ArgsWrapper.h +++ b/test-app/runtime/src/main/cpp/ArgsWrapper.h @@ -22,7 +22,7 @@ struct ArgsWrapper { : args(a), type(t) { } - v8::FunctionCallbackInfo args; + const v8::FunctionCallbackInfo& args; ArgType type; }; } diff --git a/test-app/runtime/src/main/cpp/ArrayBufferHelper.cpp b/test-app/runtime/src/main/cpp/ArrayBufferHelper.cpp index afcdc3481..94aabf59e 100644 --- a/test-app/runtime/src/main/cpp/ArrayBufferHelper.cpp +++ b/test-app/runtime/src/main/cpp/ArrayBufferHelper.cpp @@ -14,8 +14,8 @@ ArrayBufferHelper::ArrayBufferHelper() void ArrayBufferHelper::CreateConvertFunctions(Local context, const Local& global, ObjectManager* objectManager) { m_objectManager = objectManager; - Isolate* isolate = context->GetIsolate(); - auto extData = External::New(isolate, this); + Isolate* isolate = v8::Isolate::GetCurrent(); + auto extData = External::New(isolate, this, v8::kExternalPointerTypeTagDefault); auto fromFunc = FunctionTemplate::New(isolate, CreateFromCallbackStatic, extData)->GetFunction(context).ToLocalChecked(); auto arrBufferCtorFunc = global->Get(context, ArgConverter::ConvertToV8String(isolate, "ArrayBuffer")).ToLocalChecked().As(); arrBufferCtorFunc->Set(context, ArgConverter::ConvertToV8String(isolate, "from"), fromFunc); @@ -24,7 +24,7 @@ void ArrayBufferHelper::CreateConvertFunctions(Local context, const Loc void ArrayBufferHelper::CreateFromCallbackStatic(const FunctionCallbackInfo& info) { try { auto extData = info.Data().As(); - auto thiz = reinterpret_cast(extData->Value()); + auto thiz = reinterpret_cast(extData->Value(v8::kExternalPointerTypeTagDefault)); thiz->CreateFromCallbackImpl(info); } catch (NativeScriptException& e) { e.ReThrowToV8(); diff --git a/test-app/runtime/src/main/cpp/ArrayElementAccessor.cpp b/test-app/runtime/src/main/cpp/ArrayElementAccessor.cpp index 587e67320..5123e627a 100644 --- a/test-app/runtime/src/main/cpp/ArrayElementAccessor.cpp +++ b/test-app/runtime/src/main/cpp/ArrayElementAccessor.cpp @@ -12,7 +12,7 @@ using namespace tns; Local ArrayElementAccessor::GetArrayElement(Local context, const Local& array, uint32_t index, const string& arraySignature) { JEnv env; - Isolate* isolate = context->GetIsolate(); + Isolate* isolate = v8::Isolate::GetCurrent(); EscapableHandleScope handleScope(isolate); auto runtime = Runtime::GetRuntime(isolate); auto objectManager = runtime->GetObjectManager(); @@ -83,7 +83,7 @@ Local ArrayElementAccessor::GetArrayElement(Local context, const void ArrayElementAccessor::SetArrayElement(Local context, const Local& array, uint32_t index, const string& arraySignature, Local& value) { JEnv env; - Isolate* isolate = context->GetIsolate(); + Isolate* isolate = v8::Isolate::GetCurrent(); HandleScope handleScope(isolate); auto runtime = Runtime::GetRuntime(isolate); auto objectManager = runtime->GetObjectManager(); diff --git a/test-app/runtime/src/main/cpp/ArrayHelper.cpp b/test-app/runtime/src/main/cpp/ArrayHelper.cpp index 078967026..795c2fc78 100644 --- a/test-app/runtime/src/main/cpp/ArrayHelper.cpp +++ b/test-app/runtime/src/main/cpp/ArrayHelper.cpp @@ -20,7 +20,7 @@ void ArrayHelper::Init(const Local& context) { CREATE_ARRAY_HELPER = env.GetStaticMethodID(RUNTIME_CLASS, "createArrayHelper", "(Ljava/lang/String;I)Ljava/lang/Object;"); assert(CREATE_ARRAY_HELPER != nullptr); - auto isolate = context->GetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); auto global = context->Global(); auto arr = global->Get(context, ArgConverter::ConvertToV8String(isolate, "Array")); diff --git a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp index a7fe67236..de1d0b345 100644 --- a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp +++ b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp @@ -593,8 +593,8 @@ CallbackHandlers::GetImplementedInterfaces(JEnv &env, const Local &imple } vector interfacesToImplement; - auto isolate = implementationObject->GetIsolate(); - auto context = implementationObject->CreationContext(); + auto isolate = v8::Isolate::GetCurrent(); + auto context = implementationObject->GetCreationContext(isolate).ToLocalChecked(); Local interfacesName = String::NewFromUtf8Literal(isolate, "interfaces"); Local prop; if (implementationObject->Get(context, interfacesName).ToLocal(&prop) && !prop.IsEmpty() && prop->IsArray()) { @@ -639,8 +639,8 @@ CallbackHandlers::GetMethodOverrides(JEnv &env, const Local &implementat } vector methodNames; - auto isolate = implementationObject->GetIsolate(); - auto context = implementationObject->CreationContext(); + auto isolate = v8::Isolate::GetCurrent(); + auto context = implementationObject->GetCreationContext(isolate).ToLocalChecked(); auto propNames = implementationObject->GetOwnPropertyNames(context).ToLocalChecked(); for (int i = 0; i < propNames->Length(); i++) { auto name = propNames->Get(context, i).ToLocalChecked().As(); @@ -1100,7 +1100,7 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfoGetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); std::string workerPath; diff --git a/test-app/runtime/src/main/cpp/ErrorEvents.cpp b/test-app/runtime/src/main/cpp/ErrorEvents.cpp index 56ef451cf..071c53b6e 100644 --- a/test-app/runtime/src/main/cpp/ErrorEvents.cpp +++ b/test-app/runtime/src/main/cpp/ErrorEvents.cpp @@ -141,7 +141,7 @@ void ErrorEvents::Init(Local context) { }) )js"; - auto isolate = context->GetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); auto runtime = GetRuntimeOrNull(isolate); if (runtime == nullptr) { throw NativeScriptException("ErrorEvents::Init: no runtime for isolate"); diff --git a/test-app/runtime/src/main/cpp/Events.cpp b/test-app/runtime/src/main/cpp/Events.cpp index 42f45aee1..4086a34c3 100644 --- a/test-app/runtime/src/main/cpp/Events.cpp +++ b/test-app/runtime/src/main/cpp/Events.cpp @@ -151,7 +151,7 @@ void Events::Init(Local context) { }) )js"; - auto isolate = context->GetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); auto runtime = static_cast( isolate->GetData((uint32_t) Runtime::IsolateData::RUNTIME)); if (runtime == nullptr) { diff --git a/test-app/runtime/src/main/cpp/Interop.cpp b/test-app/runtime/src/main/cpp/Interop.cpp index cedd7e190..644aff4a8 100644 --- a/test-app/runtime/src/main/cpp/Interop.cpp +++ b/test-app/runtime/src/main/cpp/Interop.cpp @@ -32,7 +32,7 @@ static Local GetBrand(Isolate* isolate) { * `value` is an Error carrying one. Empty handle otherwise. */ static Local GetWrappedJavaThrowable(Local context, Local value) { - auto isolate = context->GetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); auto isWrappedThrowable = [&](Local v) -> bool { if (v.IsEmpty() || !v->IsObject()) { @@ -174,7 +174,7 @@ static void EscapeExceptionCallback(const FunctionCallbackInfo& info) { } void Interop::Init(Local context) { - auto isolate = context->GetIsolate(); + auto isolate = v8::Isolate::GetCurrent(); auto global = context->Global(); auto interop = Object::New(isolate); diff --git a/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp b/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp index a415e0c0a..a37a7706b 100644 --- a/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp +++ b/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp @@ -25,7 +25,7 @@ void JSONObjectHelper::RegisterFromFunction(Isolate *isolate, Local& json } Persistent* serializeFunc = new Persistent(isolate, CreateSerializeFunc(context)); - Local extData = External::New(isolate, serializeFunc); + Local extData = External::New(isolate, serializeFunc, v8::kExternalPointerTypeTagDefault); Local fromFunc; bool ok = FunctionTemplate::New(isolate, ConvertCallbackStatic, extData)->GetFunction(context).ToLocal(&fromFunc); assert(ok); @@ -35,7 +35,7 @@ void JSONObjectHelper::RegisterFromFunction(Isolate *isolate, Local& json void JSONObjectHelper::ConvertCallbackStatic(const FunctionCallbackInfo& info) { try { Local extData = info.Data().As(); - auto poSerializeFunc = reinterpret_cast*>(extData->Value()); + auto poSerializeFunc = reinterpret_cast*>(extData->Value(v8::kExternalPointerTypeTagDefault)); Isolate* isolate = info.GetIsolate(); Local serializeFunc = poSerializeFunc->Get(isolate); @@ -102,7 +102,7 @@ Local JSONObjectHelper::CreateSerializeFunc(Local context) { " }" "})()"; - Isolate* isolate = context->GetIsolate(); + Isolate* isolate = v8::Isolate::GetCurrent(); Local