Skip to content
Closed
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
2 changes: 2 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ build --@rules_rust//rust/settings:clippy_flags=-Dwarnings

# Darwin sandbox reuse otherwise traverses the complete SDK forest per action.
build:macos-sandbox --experimental_inmemory_sandbox_stashes
# Reuse shared SDK input hashes when checking the hosted disk action cache.
build:macos-sandbox --experimental_remote_merkle_tree_cache

# The LLVM serializer runs on Linux; serialized product targets remain separate.
build:wasix-aot --extra_execution_platforms=//build_defs/wasix:llvm_serializer_linux_x86_64
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,17 @@ jobs:
if-no-files-found: error
retention-days: 7

- name: Upload Linux build diagnostics
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: native-linux-build-diagnostics-${{ matrix.platform }}
path: |
${{ runner.temp }}/*build-capture/diagnostics/
${{ runner.temp }}/ci-requests/**/diagnostics/
if-no-files-found: ignore
retention-days: 7

- *ci_request_receipts

- name: Save bounded standard Bazel caches
Expand Down
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,15 @@ test_suite(
"//build_defs/foundation:toolchain_metadata_test",
"//build_defs/host:host_sdk_test",
"//build_defs/host:profile_analysis_test",
"//build_defs/host:apple_sdk_inputs_test",
"//build_defs/native:host_runtime_test_helpers_test",
"//build_defs/native:windows_exports_test",
"//build_defs/native:host_config_policy_test",
"//build_defs/native:compiler_probe_tool_inputs_test",
"//build_defs/extensions:forced_header_server_inputs_test",
"//build_defs/extensions:forced_header_embedded_inputs_test",
"//build_defs/extensions:mobile_host_ios_xcframework_test",
"//build_defs/extensions:mobile_cpp_reuse_test",
"//build_defs/extensions:mobile_host_android_arm64_v8a_test",
"//build_defs/extensions:mobile_host_android_x86_64_test",
"//build_defs/native:postgres_probe_header_test",
Expand Down
18 changes: 18 additions & 0 deletions build_defs/android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ reuses `rules_android_ndk`'s maintained C/C++ configuration and declares the
compiler, headers, target libraries and link tools as action inputs. Small local
registration targets keep unrelated platform analysis from acquiring the NDK.

Released native slices and APK native dependencies use
`rules_android`'s `android_split_transition`. The release producer sets its API
floor from the compatibility policy before that split. Packaged archives and
runtime data enter through `android_product_inputs`, which clears the consuming
app's Android split settings and restores the Linux producer context. Executable
tools retain `cfg = "exec"`. This keeps app configuration out of the released
products without copying upstream's native transition or lowering the app's Java
minimum SDK.
`android_native_member` forwards registration and the C++ library together from
that native context, and verifies that both reference the same archive. This
also preserves reuse when the consuming app raises its minimum SDK.

`//src/examples/native/react-native-expo:android_cpp_reuse_test` compares the actual
selected release archive Files with the Files linked into the example APK for
every declared ABI, at the app's minimum SDK and at a higher app SDK. Its
inventory comes from the existing carrier metadata and selected extensions;
adding an object file does not require updating the test.

Run the acquisition-policy tests and actual cross-compiled ELF checks together:

```sh
Expand Down
2 changes: 1 addition & 1 deletion build_defs/android/app-query-test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def _android_min_sdk_test_impl(ctx):
for minimum in [24, 26]:
configuration = android_app_configuration({}, struct(abi = abi, min_sdk_version = minimum))
asserts.equals(env, minimum, configuration[str(Label("@rules_android//rules:min_sdk_version"))])
asserts.equals(env, ["//build_defs/android:android_" + platform], configuration["//command_line_option:android_platforms"])
asserts.equals(env, [str(Label("//build_defs/android:android_" + platform))], configuration["//command_line_option:android_platforms"])
asserts.equals(env, "opt", configuration["//command_line_option:compilation_mode"])
# The maintained dependency transition preserves an incoming SDK
# rather than invoking its dormant clamp or lowering the setting.
Expand Down
46 changes: 37 additions & 9 deletions build_defs/android/app.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

load("@aspect_rules_js//npm:providers.bzl", "NpmPackageInfo")
load("@rules_android//providers:providers.bzl", "AndroidSdkInfo", "ApkInfo")
load("@rules_android//rules:android_split_transition.bzl", "android_split_transition")
load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
Expand All @@ -10,6 +11,7 @@ load("//build_defs/foundation:bun.bzl", "BUN_SOURCE_TOOLCHAINS", "BUN_TEST_ENTRY
load("//build_defs/extensions:mobile.bzl", "MobileExtensionInfo")
load("//build_defs/extensions:final_leaf.bzl", "ExtensionFinalLeafInfo")
load("@oliphaunt_metadata//:metadata.bzl", "EXTENSIONS")
load(":configuration.bzl", "ANDROID_TARGET_PLATFORMS", "android_native_inputs", "android_product_inputs")

_DESCRIPTORS = {row["sql-name"]: row for row in EXTENSIONS.values()}

Expand Down Expand Up @@ -274,7 +276,8 @@ def _final_aar_impl(ctx):
return [DefaultInfo(files = depset([output]))]

android_final_aar = rule(implementation = _final_aar_impl, toolchains = BUN_SOURCE_TOOLCHAINS,
attrs = dict(_RUNNER, **{"source": attr.label(mandatory = True, allow_files = True, cfg = "exec"),
attrs = dict(_RUNNER, **{"source": attr.label(mandatory = True, allow_files = True, cfg = android_product_inputs),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
"kind": attr.string(default = "aar", values = ["aar", "runtime", "seed"]),
"catalog_profile": attr.string(default = "standard", values = ["standard", "icu"]),
"package": attr.string(), "abi": attr.string(default = "", values = ["", "arm64-v8a", "x86_64"])}))
Expand Down Expand Up @@ -307,25 +310,46 @@ def _exec_npm_impl(ctx):
src = ctx.file.archive, npm_package_store_infos = depset())]

android_workspace_npm = rule(implementation = _exec_npm_impl, attrs = {
"archive": attr.label(allow_single_file = [".tgz"], cfg = "exec"),
"archive": attr.label(allow_single_file = [".tgz"], cfg = android_product_inputs),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
"error": attr.string(),
"package_name": attr.string(mandatory = True), "version": attr.string(mandatory = True),
})

def _native_member_impl(ctx):
member = ctx.attr.member[0][MobileExtensionInfo]
library = ctx.attr.library[0][CcInfo]
archives = [archive for item in library.linking_context.linker_inputs.to_list() for linked in item.libraries for archive in [linked.static_library, linked.pic_static_library] if archive]
if member.archive not in archives:
fail("Android registration and C++ link must consume the same configured member archive")
return [ctx.attr.member[0][DefaultInfo], member, library]

# A released native product keeps its compatibility floor when an app raises
# its Java/Dex floor. Forward both registration and the actual C++ link provider
# from the same upstream ABI split, including the library's transitive deps.
android_native_member = rule(implementation = _native_member_impl, cfg = android_native_inputs, attrs = {
"platform": attr.label(mandatory = True),
"member": attr.label(mandatory = True, providers = [MobileExtensionInfo], cfg = android_split_transition),
"library": attr.label(mandatory = True, providers = [CcInfo], cfg = android_split_transition),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
})

def _link_receipt_impl(ctx):
members, inputs = [], [ctx.file.library, ctx.file.registry]
archives = []
for target in ctx.attr.members:
member = target[MobileExtensionInfo]
members.append({"archive": member.archive.path, "registration": member.registration.path})
inputs += [member.archive, member.registration]
archives.append(member.archive)
nm = [file for file in ctx.files._nm if file.basename == "llvm-nm"]
if len(nm) != 1:
fail("Static extension proof needs the actual pinned NDK nm tool")
output = ctx.actions.declare_file(ctx.label.name + ".json")
_action(ctx, "extension-receipt", {"library": ctx.file.library.path, "registry": ctx.file.registry.path,
"members": members, "selectedExtensions": ctx.attr.selected_extensions, "moduleStems": _module_stems(ctx.attr.selected_extensions), "abi": ctx.attr.abi, "nm": nm[0].path, "output": output.path},
inputs + ctx.files._nm, [output], "AndroidSelectedStaticExtensionLink")
return [DefaultInfo(files = depset([output]))]
return [DefaultInfo(files = depset([output])), OutputGroupInfo(mobile_static_archives = depset(archives))]

android_extension_receipt = rule(implementation = _link_receipt_impl, toolchains = BUN_SOURCE_TOOLCHAINS,
attrs = dict(_RUNNER, **{
Expand All @@ -347,13 +371,16 @@ def _runtime_assets_impl(ctx):
output = ctx.actions.declare_directory(ctx.label.name + "/assets")
if len(ctx.files.payload) != 1 or not ctx.files.payload[0].is_directory:
fail("App runtime assets need the actual declared native payload tree")
leaves, inputs = [], []
leaves, inputs, archives = [], [], []
target = "android-" + ctx.attr.abi
for dependency in ctx.attr.leaves:
leaf = dependency[ExtensionFinalLeafInfo]
if leaf.family != "native" or leaf.target != target or leaf.metadata["runtimeVersion"] != ctx.attr.version:
fail("App selected carrier differs from the declared Android ABI or native version")
members = leaf.metadata.get("members", [leaf.metadata])
selected = [row["archive"] for member in members if member["sqlName"] in ctx.attr.selected_extensions for row in member.get("staticArchives", []) if row["target"] == target]
# Retain the actual producer Files for the configured reuse regression.
archives += [file for file in dependency[OutputGroupInfo].mobile_static_archives.to_list() if file.path in selected]
leaves.append({"product": leaf.product, "owner": leaf.owner, "version": leaf.version, "target": leaf.target,
"runtimeVersion": leaf.metadata["runtimeVersion"], "sqlNames": sorted([member["sqlName"] for member in members]),
"github": [file.path for file in leaf.github], "frozen": leaf.frozen.path})
Expand All @@ -363,16 +390,17 @@ def _runtime_assets_impl(ctx):
"icu": ctx.file.icu.path if ctx.file.icu else None,
"receipt": ctx.file.receipt.path, "output": output.path},
ctx.files.payload + [ctx.file.receipt] + ctx.files.icu + inputs, [output], "ExpoSelectedRuntimeAssets", tools = [ctx.attr._formatter[DefaultInfo].files_to_run])
return [DefaultInfo(files = depset([output]))]
return [DefaultInfo(files = depset([output])), OutputGroupInfo(mobile_raw_archives = depset(archives), mobile_linked_archives = ctx.attr.receipt[OutputGroupInfo].mobile_static_archives)]

android_app_runtime_assets = rule(implementation = _runtime_assets_impl, toolchains = BUN_SOURCE_TOOLCHAINS,
attrs = dict(_RUNNER, **{
"payload": attr.label(default = Label("//src/native/runtime:public_payload"), cfg = "exec", allow_files = True),
"icu": attr.label(allow_single_file = True, cfg = "exec"),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
"payload": attr.label(default = Label("//src/native/runtime:public_payload"), cfg = android_product_inputs, allow_files = True),
"icu": attr.label(allow_single_file = True, cfg = android_product_inputs),
"version": attr.string(mandatory = True), "selected_extensions": attr.string_list(mandatory = True),
"mobile_static_modules": attr.string_list(mandatory = True), "receipt": attr.label(mandatory = True, allow_single_file = [".json"]),
"abi": attr.string(mandatory = True, values = ["arm64-v8a", "x86_64"]),
"leaves": attr.label_list(mandatory = True, providers = [ExtensionFinalLeafInfo], cfg = "exec"),
"leaves": attr.label_list(mandatory = True, providers = [ExtensionFinalLeafInfo], cfg = android_product_inputs),
"_helpers": attr.label_list(default = _STATIC_CARRIER_HELPERS, allow_files = True),
"_formatter": attr.label(default = Label("//src/native/runtime/packaging:resources"), cfg = "exec", executable = True),
}))
Expand All @@ -397,7 +425,7 @@ android_jni_aar = rule(implementation = _jni_aar_impl, toolchains = BUN_SOURCE_T
_ANDROID_MIN_SDK = str(Label("@rules_android//rules:min_sdk_version"))

def _apk_abi_impl(settings, attr):
platform = "//build_defs/android:android_" + ("arm64" if attr.abi == "arm64-v8a" else "x86_64")
platform = str(ANDROID_TARGET_PLATFORMS["android-" + attr.abi])
return {"//command_line_option:android_platforms": [platform], "//command_line_option:compilation_mode": "opt",
_ANDROID_MIN_SDK: attr.min_sdk_version}

Expand Down
32 changes: 32 additions & 0 deletions build_defs/android/configuration.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Keep released producer inputs independent of the consuming Android app."""

load("@oliphaunt_toolchain_metadata//:android-platforms.bzl", "ANDROID_PLATFORMS")

ANDROID_TARGET_PLATFORMS = {"android-arm64-v8a": Label("//build_defs/android:android_arm64"), "android-x86_64": Label("//build_defs/android:android_x86_64")}
_MIN_SDK = str(Label("@rules_android//rules:min_sdk_version"))

def _native_inputs_impl(_settings, attr):
targets = [target for target, platform in ANDROID_TARGET_PLATFORMS.items() if platform == attr.platform]
if len(targets) != 1:
fail("Android native construction needs a declared ABI platform")
return {"//command_line_option:android_platforms": [str(attr.platform)], _MIN_SDK: ANDROID_PLATFORMS[targets[0]]["api"]}

# Set the product floor before upstream's split; never lower an app's JVM floor.
android_native_inputs = transition(implementation = _native_inputs_impl, inputs = [], outputs = ["//command_line_option:android_platforms", _MIN_SDK])

def _product_inputs_impl(_settings, _attr):
# These are Linux-produced archives/data, not tools or APK dependencies.
# The producer owns any outgoing ABI split and its minimum SDK.
return {
"//command_line_option:platforms": [str(Label("//build_defs/native:linux_x86_64_glibc_2_28"))],
"//command_line_option:android_platforms": [],
"//command_line_option:Android configuration distinguisher": "main",
"//command_line_option:dynamic_mode": "default",
_MIN_SDK: 0,
}

android_product_inputs = transition(
implementation = _product_inputs_impl,
inputs = [],
outputs = ["//command_line_option:platforms", "//command_line_option:android_platforms", "//command_line_option:Android configuration distinguisher", "//command_line_option:dynamic_mode", _MIN_SDK],
)
42 changes: 42 additions & 0 deletions build_defs/android/reuse-test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Compare the actual release and app-link archive Files for each supported ABI."""

load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load("@rules_android//rules:android_split_transition.bzl", "android_split_transition")
load(":app.bzl", "android_app_configuration")
load(":configuration.bzl", "ANDROID_TARGET_PLATFORMS")

def _apps_impl(settings, attr):
return {target + "/" + str(minimum): android_app_configuration(settings, struct(abi = target.removeprefix("android-"), min_sdk_version = minimum)) for target in ANDROID_TARGET_PLATFORMS for minimum in [attr.min_sdk_version, attr.min_sdk_version + 1]}

_apps = transition(implementation = _apps_impl, inputs = [], outputs = ["//command_line_option:android_platforms", "//command_line_option:compilation_mode", str(Label("@rules_android//rules:min_sdk_version"))])

def _view_impl(ctx):
return [ctx.attr.assets[0][OutputGroupInfo]]

_view = rule(implementation = _view_impl, attrs = {
"assets": attr.label(default = "//src/examples/native/react-native-expo:selected_runtime_assets", cfg = android_split_transition),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
})

def _reuse_impl(ctx):
env = unittest.begin(ctx)
asserts.equals(env, sorted([target + "/" + str(minimum) for target in ANDROID_TARGET_PLATFORMS for minimum in [ctx.attr.min_sdk_version, ctx.attr.min_sdk_version + 1]]), sorted(ctx.split_attr.apps))
for abi, targets in ctx.split_attr.apps.items():
asserts.equals(env, 1, len(targets))
views = targets[0][OutputGroupInfo]
raw = views.mobile_raw_archives.to_list()
linked = views.mobile_linked_archives.to_list()
asserts.true(env, len(raw) > 0, abi + " must observe actual release archives")
asserts.equals(env, sorted([file.path for file in raw]), sorted([file.path for file in linked]), abi + " release and APK link must consume the same configured archive Files")
return unittest.end(env)

_reuse_test = unittest.make(_reuse_impl, attrs = {
"apps": attr.label_list(cfg = _apps),
"min_sdk_version": attr.int(mandatory = True),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
})

def android_cpp_reuse_test(name, min_sdk_version):
_view(name = name + "_view", testonly = True, tags = ["manual"])
_reuse_test(name = name, apps = [":" + name + "_view"], min_sdk_version = min_sdk_version,
target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64"], size = "small")
10 changes: 4 additions & 6 deletions build_defs/android/toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load("@rules_cc//cc:cc_toolchain_config_lib.bzl", "feature", "flag_group", "flag_set")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo")
load("@rules_android//rules:android_split_transition.bzl", "android_split_transition")
load(":configuration.bzl", "android_native_inputs")

def _android_cc_config_impl(ctx):
# This is a qualification bound, not a second version authority. An SDK or
Expand Down Expand Up @@ -70,11 +72,6 @@ android_cc_config = rule(
provides = [CcToolchainConfigInfo],
)

def _platform_impl(settings, attr):
return {"//command_line_option:platforms": [str(attr.platform)]}

_android_platform = transition(implementation = _platform_impl, inputs = [], outputs = ["//command_line_option:platforms"])

def _android_artifact_impl(ctx):
files = ctx.attr.artifact[0][DefaultInfo].files
if ctx.attr.output:
Expand All @@ -88,8 +85,9 @@ def _android_artifact_impl(ctx):

android_artifact = rule(
implementation = _android_artifact_impl,
cfg = android_native_inputs,
attrs = {
"artifact": attr.label(mandatory = True, cfg = _android_platform),
"artifact": attr.label(mandatory = True, cfg = android_split_transition),
"platform": attr.label(mandatory = True),
"output": attr.string(default = ""),
"_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
Expand Down
Loading
Loading