diff --git a/bazel/v8/BUILD b/bazel/v8/BUILD new file mode 100644 index 0000000000..5b01f6e3e4 --- /dev/null +++ b/bazel/v8/BUILD @@ -0,0 +1 @@ +licenses(["notice"]) diff --git a/bazel/v8/build_v8.sh b/bazel/v8/build_v8.sh new file mode 100755 index 0000000000..7278c1536f --- /dev/null +++ b/bazel/v8/build_v8.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash + +# Script to build V8's wee8 static library and package it for distribution. +# +# This script uses the self-contained Bazel workspace in bazel/v8/build/ which +# has all V8 dependencies pinned. It does NOT depend on the Envoy repository. +# +# Usage: +# ./build_v8.sh [--arch x86_64] [--output-dir /tmp/v8-out] [--bazel-opts "..."] +# +# The resulting tarball can be uploaded to GitHub releases for consumption by +# the v8_prebuilt repository rule. + +set -e -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="$SCRIPT_DIR/build" +ARCH="x86_64" +OUTPUT_DIR="/tmp/v8-prebuilt" +BAZEL_BUILD_OPTS="" + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" + echo " --output-dir Output directory for the tarball (default: /tmp/v8-prebuilt)" + echo " --bazel-opts Additional Bazel build options" + echo "" + exit 1 +} + +while [[ $# -gt 0 ]]; do + case $1 in + --arch) + ARCH="$2" + shift 2 + ;; + --output-dir) + OUTPUT_DIR="$2" + shift 2 + ;; + --bazel-opts) + BAZEL_BUILD_OPTS="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +if [[ ! -f "$BUILD_DIR/WORKSPACE" ]]; then + echo "Error: Cannot find $BUILD_DIR/WORKSPACE" + echo "This script must be run from the envoy_toolshed repository." + exit 1 +fi + +# Extract V8 version from the WORKSPACE file +V8_VERSION=$(grep '^V8_VERSION' "$BUILD_DIR/WORKSPACE" | sed 's/.*"\(.*\)".*/\1/') +if [[ -z "$V8_VERSION" ]]; then + echo "Error: Could not determine V8 version from $BUILD_DIR/WORKSPACE" + exit 1 +fi + +echo "Building V8 wee8 library:" +echo " V8 version: $V8_VERSION" +echo " Architecture: $ARCH" +echo " Build dir: $BUILD_DIR" +echo " Output dir: $OUTPUT_DIR" +echo "" + +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT + +# Step 1: Build V8's wee8 target using the self-contained build workspace +echo "Step 1: Building @v8//:wee8 ..." +cd "$BUILD_DIR" +# shellcheck disable=SC2086 +bazel build -c opt @v8//:wee8 $BAZEL_BUILD_OPTS + +# Step 2: Collect all .o files from V8 and its V8-specific dependencies +echo "" +echo "Step 2: Collecting object files ..." + +BAZEL_BIN=$(bazel info -c opt output_path)/k8-opt/bin +BAZEL_EXTERNAL="$BAZEL_BIN/external" + +# Bazel puts compiled objects in _objs/ subdirectories, not in .a archives. +# Collect .o files from V8 and V8-specific deps (NOT abseil-cpp or llvm_toolchain). +ALL_OBJECTS=() +for dep_dir in v8 dragonbox fast_float fp16 simdutf highway; do + if [[ -d "$BAZEL_EXTERNAL/$dep_dir" ]]; then + while IFS= read -r -d '' obj; do + ALL_OBJECTS+=("$obj") + done < <(find "$BAZEL_EXTERNAL/$dep_dir" -name '*.pic.o' -print0) + fi +done + +if [[ ${#ALL_OBJECTS[@]} -eq 0 ]]; then + echo "Error: No object files found. Build may have failed." + exit 1 +fi + +echo " Found ${#ALL_OBJECTS[@]} object files" + +# Step 3: Create a single static library from all object files +echo "" +echo "Step 3: Creating libwee8.a ..." + +STAGING_DIR="$WORK_DIR/staging" +mkdir -p "$STAGING_DIR/lib" "$STAGING_DIR/include" + +ar rcs "$STAGING_DIR/lib/libwee8.a" "${ALL_OBJECTS[@]}" +echo " Created libwee8.a ($(du -h "$STAGING_DIR/lib/libwee8.a" | cut -f1))" + +# Step 4: Copy headers +echo "" +echo "Step 4: Copying headers ..." + +V8_EXTERNAL_SRC=$(bazel info output_base)/external/v8 + +# Copy all V8 public API headers (needed by proxy_wasm_cpp_host and other consumers) +cp -r "$V8_EXTERNAL_SRC/include" "$STAGING_DIR/" + +# Copy wasm-api headers at both locations: +# - include/ for direct #include "wasm.h" usage +# - third_party/wasm-api/ for V8 internal #include "third_party/wasm-api/wasm.hh" +cp "$V8_EXTERNAL_SRC/third_party/wasm-api/wasm.h" "$STAGING_DIR/include/" +cp "$V8_EXTERNAL_SRC/third_party/wasm-api/wasm.hh" "$STAGING_DIR/include/" +mkdir -p "$STAGING_DIR/third_party/wasm-api" +cp "$V8_EXTERNAL_SRC/third_party/wasm-api/wasm.h" "$STAGING_DIR/third_party/wasm-api/" +cp "$V8_EXTERNAL_SRC/third_party/wasm-api/wasm.hh" "$STAGING_DIR/third_party/wasm-api/" + +# Copy V8 internal headers required by proxy_wasm_cpp_host (src/wasm/c-api.h +# and its transitive includes). Uses a Python script to resolve the dependency +# tree and copy only what's needed. +python3 -c " +import re, os, shutil, sys +v8_root = sys.argv[1] +staging = sys.argv[2] +visited = set() +queue = ['src/wasm/c-api.h'] +while queue: + f = queue.pop(0) + if f in visited: + continue + visited.add(f) + src = os.path.join(v8_root, f) + if not os.path.exists(src): + continue + dst = os.path.join(staging, f) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copy2(src, dst) + with open(src) as fh: + for line in fh: + m = re.match(r'#include\s+\"(src/[^\"]+)\"', line) + if m: + queue.append(m.group(1)) +print(' Copied %d internal V8 headers (src/)' % len(visited)) +" "$V8_EXTERNAL_SRC" "$STAGING_DIR" + +HEADER_COUNT=$(find "$STAGING_DIR/include" "$STAGING_DIR/src" "$STAGING_DIR/third_party" -name '*.h' -o -name '*.hh' 2>/dev/null | wc -l) +echo " Total headers: $HEADER_COUNT" + +# Step 5: Package +echo "" +echo "Step 5: Packaging ..." + +mkdir -p "$OUTPUT_DIR" +TARBALL="$OUTPUT_DIR/v8-wee8-${V8_VERSION}-linux-${ARCH}.tar.xz" + +cd "$STAGING_DIR" +tar -cJf "$TARBALL" lib/ include/ src/ third_party/ + +echo " Created: $TARBALL" +echo " Size: $(du -h "$TARBALL" | cut -f1)" +echo "" +echo "SHA256: $(sha256sum "$TARBALL" | cut -d' ' -f1)" +echo "" +echo "Done!" diff --git a/bazel/v8/extensions.bzl b/bazel/v8/extensions.bzl new file mode 100644 index 0000000000..4dc5c89015 --- /dev/null +++ b/bazel/v8/extensions.bzl @@ -0,0 +1,45 @@ +"""Module extension for pre-built V8 library configuration in bzlmod.""" + +load(":v8_libs.bzl", "v8_prebuilt") + +def _v8_libs_impl(module_ctx): + """Implementation of the v8_libs module extension.""" + setup_tag = None + for mod in module_ctx.modules: + for tag in mod.tags.setup: + if setup_tag == None: + setup_tag = tag + else: + fail("Multiple setup() calls found for v8_extension. Only one configuration is allowed since the repository name is fixed to @v8.") + + if setup_tag: + v8_prebuilt( + name = "v8", + version = setup_tag.version, + sha256 = { + "x86_64": setup_tag.sha256_x86_64, + "aarch64": setup_tag.sha256_aarch64, + }, + ) + +_setup = tag_class( + attrs = { + "version": attr.string( + mandatory = True, + doc = "V8 version to use", + ), + "sha256_x86_64": attr.string( + doc = "SHA256 hash for x86_64 architecture", + ), + "sha256_aarch64": attr.string( + doc = "SHA256 hash for aarch64 architecture", + ), + }, +) + +v8_extension = module_extension( + implementation = _v8_libs_impl, + tag_classes = { + "setup": _setup, + }, +) diff --git a/bazel/v8/v8_libs.bzl b/bazel/v8/v8_libs.bzl new file mode 100644 index 0000000000..ccc539c337 --- /dev/null +++ b/bazel/v8/v8_libs.bzl @@ -0,0 +1,120 @@ +"""Repository rule for pre-built V8 (wee8) static library.""" + +_ARCH_MAP = { + "amd64": "x86_64", + "x86_64": "x86_64", + "aarch64": "aarch64", + "arm64": "aarch64", +} + +_BUILD_FILE_CONTENT = """ +package(default_visibility = ["//visibility:public"]) + +cc_import( + name = "wee8_import", + static_library = "lib/libwee8.a", + alwayslink = True, +) + +cc_library( + name = "wee8", + hdrs = glob(["include/**/*.h", "include/**/*.hh", "src/**/*.h", "third_party/**/*.h", "third_party/**/*.hh"]), + defines = [ + "GOOGLE3", + "V8_ADVANCED_BIGINT_ALGORITHMS", + "V8_CONCURRENT_MARKING", + "V8_DEPRECATION_WARNINGS", + "V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA", + "V8_ENABLE_EXTENSIBLE_RO_SNAPSHOT", + "V8_ENABLE_LAZY_SOURCE_POSITIONS", + "V8_ENABLE_MAGLEV", + "V8_ENABLE_SPARKPLUG", + "V8_ENABLE_TURBOFAN", + "V8_ENABLE_UNDEFINED_DOUBLE", + "V8_ENABLE_WEBASSEMBLY", + "V8_HAVE_TARGET_OS", + "V8_IMMINENT_DEPRECATION_WARNINGS", + "V8_TARGET_ARCH_X64", + "V8_TARGET_OS_LINUX", + "V8_TLS_USED_IN_LIBRARY", + "V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP=64", + ], + includes = [".", "include", "third_party"], + deps = [ + ":wee8_import", + "@abseil-cpp//absl/container:btree", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/container:flat_hash_set", + "@abseil-cpp//absl/functional:overload", + "@abseil-cpp//absl/synchronization", + "@abseil-cpp//absl/time", + ], + visibility = ["//visibility:public"], +) +""" + +def _v8_prebuilt_impl(ctx): + """Downloads pre-built V8 wee8 library from GitHub releases.""" + + # Auto-detect host architecture + host_arch = ctx.os.arch + arch = _ARCH_MAP.get(host_arch) + if not arch: + fail("Unsupported host architecture for V8 pre-built: %s" % host_arch) + + # Allow local testing via V8_PREBUILT_PATH environment variable. + # When set, it should point to a directory containing the tarball, e.g.: + # export V8_PREBUILT_PATH=/tmp/v8-prebuilt + local_path = ctx.os.environ.get("V8_PREBUILT_PATH", "") + if local_path: + tarball = "{path}/v8-wee8-{version}-linux-{arch}.tar.xz".format( + path = local_path, + version = ctx.attr.version, + arch = arch, + ) + ctx.extract(ctx.path(tarball)) + else: + sha256 = ctx.attr.sha256.get(arch, "") + if not sha256: + fail("No V8 pre-built SHA256 provided for architecture: %s" % arch) + + ctx.download_and_extract( + url = "https://github.com/envoyproxy/toolshed/releases/download/v8-v{version}/v8-wee8-{version}-linux-{arch}.tar.xz".format( + version = ctx.attr.version, + arch = arch, + ), + sha256 = sha256, + ) + ctx.file("BUILD.bazel", _BUILD_FILE_CONTENT) + +v8_prebuilt = repository_rule( + implementation = _v8_prebuilt_impl, + attrs = { + "version": attr.string( + mandatory = True, + doc = "V8 version (e.g., '14.6.202.10')", + ), + "sha256": attr.string_dict( + mandatory = True, + doc = "SHA256 hashes keyed by architecture (x86_64, aarch64)", + ), + }, + environ = ["V8_PREBUILT_PATH"], + doc = "Downloads pre-built V8 wee8 static library for use with proxy-wasm", +) + +def setup_v8_prebuilt(version, sha256): + """Setup function for WORKSPACE. + + Creates @v8 repository with pre-built wee8 library. + The host architecture is auto-detected at fetch time. + + Args: + version: V8 version to download (must be already published). + sha256: Dict of {arch: sha256} for each supported architecture. + """ + v8_prebuilt( + name = "v8", + version = version, + sha256 = sha256, + )