Skip to content
Merged
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
8 changes: 7 additions & 1 deletion BUILD
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Do not remove, this empty BUILD file is necessary for _android_ndk_repository_impl in rules.bzl.
# Note that this BUILD file is necessary for `android_ndk_repository` in `rules.bzl`.

exports_files([
"LICENSE",
] + glob([
"*.tpl",
]))
3 changes: 2 additions & 1 deletion BUILD.ndk_clang.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Declarations for the NDK's Clang directory."""

load("@rules_cc//cc/toolchains:cc_toolchain_suite.bzl", "cc_toolchain_suite")
load("@rules_cc//cc/toolchains:cc_toolchain.bzl", "cc_toolchain")
load("@@{repository_name}//:ndk_cc_toolchain_config.bzl", "ndk_cc_toolchain_config_rule")
load("//:target_systems.bzl", "TARGET_SYSTEM_NAMES")
Expand Down Expand Up @@ -52,7 +53,7 @@ filegroup(
"lib64/**/*",
"lib/**/*",
],
# Need to allow_empty here because previous NDK versions had
# Need to allow_empty here because previous NDK versions had
# "lib" & "lib64" directories but recent ones only have "lib".
allow_empty = True,
),
Expand Down
1 change: 1 addition & 0 deletions BUILD.ndk_root.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ alias(
"@platforms//os:android",
CPU_CONSTRAINT[target_system_name],
],
exec_compatible_with = {exec_compatible_with},
toolchain = "//{clang_directory}:cc_toolchain_%s" % target_system_name,
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
) for target_system_name in TARGET_SYSTEM_NAMES]
Expand Down
17 changes: 12 additions & 5 deletions extension.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""A bzlmod extension for loading the NDK."""

load(":rules.bzl", "android_ndk_repository")
load(":rules.bzl", "DEFAULT_API_LEVEL", "android_ndk_repository")

def _android_ndk_repository_extension_impl(module_ctx):
root_modules = [m for m in module_ctx.modules if m.is_root and m.tags.configure]
Expand All @@ -36,12 +36,19 @@ def _android_ndk_repository_extension_impl(module_ctx):
**kwargs
)

_CONFIGURE_TAG_CLASS = tag_class(attrs = {
"api_level": attr.int(
doc = "The minimum Android API level to target.",
default = DEFAULT_API_LEVEL,
),
"path": attr.string(
doc = "The path to the local Android NDK installation. If not set, ANDROID_NDK_HOME environment variable is used.",
),
})

android_ndk_repository_extension = module_extension(
implementation = _android_ndk_repository_extension_impl,
tag_classes = {
"configure": tag_class(attrs = {
"path": attr.string(),
"api_level": attr.int(),
}),
"configure": _CONFIGURE_TAG_CLASS,
},
)
150 changes: 120 additions & 30 deletions rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,64 @@

"""A repository rule for integrating the Android NDK."""

def _android_ndk_repository_impl(ctx):
DEFAULT_API_LEVEL = 31

_EXEC_CONSTRAINTS = {
"darwin-arm64": [
"@platforms//os:macos",
"@platforms//cpu:aarch64",
],
"darwin-x86_64": [
"@platforms//os:macos",
"@platforms//cpu:x86_64",
],
"linux-x86_64": [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
"windows-x86_64": [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
}

def _get_clang_resource_dir(ctx, clang_directory, is_windows):
clang_resource_dir = getattr(ctx.attr, "clang_resource_dir", None)
if clang_resource_dir:
return clang_resource_dir

result = ctx.execute([clang_directory + "/bin/clang", "--print-resource-dir"])
if result.return_code != 0:
fail("Failed to execute clang: %s" % result.stderr)
stdout = result.stdout.strip()
if is_windows:
stdout = stdout.replace("\\", "/")
return stdout.split(clang_directory)[1].strip("/")

def _android_ndk_repository_common(ctx, ndk_path):
"""Install the Android NDK files.

Args:
ctx: An implementation context.
ndk_path: The path to the ndk

Returns:
A final dict of configuration attributes and values.
"""
ndk_path = ctx.attr.path or ctx.getenv("ANDROID_NDK_HOME", None)
if not ndk_path:
fail("Either the ANDROID_NDK_HOME environment variable or the " +
"path attribute of android_ndk_repository must be set.")
if ndk_path.startswith("$WORKSPACE_ROOT"):
ndk_path = str(ctx.workspace_root) + ndk_path.removeprefix("$WORKSPACE_ROOT")

is_windows = False
executable_extension = ""
if ctx.os.name == "linux":
exec_compatible_with = None
platform = ctx.os.name
if hasattr(ctx.attr, "platform"):
platform = ctx.attr.platform
exec_compatible_with = _EXEC_CONSTRAINTS[platform]

if platform.startswith("linux"):
clang_directory = "toolchains/llvm/prebuilt/linux-x86_64"
elif ctx.os.name == "mac os x":
elif platform.startswith(("mac", "darwin")):
# Note: darwin-x86_64 does indeed contain fat binaries with arm64 slices, too.
clang_directory = "toolchains/llvm/prebuilt/darwin-x86_64"
elif ctx.os.name.startswith("windows"):
elif platform.startswith("windows"):
clang_directory = "toolchains/llvm/prebuilt/windows-x86_64"
is_windows = True
executable_extension = ".exe"
Expand All @@ -48,15 +82,9 @@ def _android_ndk_repository_impl(ctx):

_create_symlinks(ctx, ndk_path, clang_directory, sysroot_directory)

api_level = ctx.attr.api_level or 31
api_level = ctx.attr.api_level or DEFAULT_API_LEVEL

result = ctx.execute([clang_directory + "/bin/clang", "--print-resource-dir"])
if result.return_code != 0:
fail("Failed to execute clang: %s" % result.stderr)
stdout = result.stdout.strip()
if is_windows:
stdout = stdout.replace("\\", "/")
clang_resource_directory = stdout.split(clang_directory)[1].strip("/")
clang_resource_directory = _get_clang_resource_dir(ctx, clang_directory, is_windows)

# Use a label relative to the workspace from which this repository rule came
# to get the workspace name.
Expand All @@ -67,6 +95,7 @@ def _android_ndk_repository_impl(ctx):
ctx.attr._template_ndk_root,
{
"{clang_directory}": clang_directory,
"{exec_compatible_with}": repr(exec_compatible_with),
},
executable = False,
)
Expand All @@ -83,11 +112,11 @@ def _android_ndk_repository_impl(ctx):
"%s/BUILD.bazel" % clang_directory,
ctx.attr._template_ndk_clang,
{
"{repository_name}": repository_name,
"{api_level}": str(api_level),
"{clang_resource_directory}": clang_resource_directory,
"{sysroot_directory}": sysroot_directory,
"{executable_extension}": executable_extension,
"{repository_name}": repository_name,
"{sysroot_directory}": sysroot_directory,
},
executable = False,
)
Expand Down Expand Up @@ -124,16 +153,77 @@ def _create_symlinks(ctx, ndk_path, clang_directory, sysroot_directory):
# TODO(#32): Remove this hack
ctx.symlink(ndk_path + "sources", "ndk/sources")

_COMMON_ATTR = {
"api_level": attr.int(
doc = "The minimum Android API level to target.",
default = DEFAULT_API_LEVEL,
),
"_build": attr.label(
default = Label("//:BUILD"),
allow_single_file = True,
),
"_template_ndk_clang": attr.label(
default = Label("//:BUILD.ndk_clang.tpl"),
allow_single_file = True,
),
"_template_ndk_root": attr.label(
default = Label("//:BUILD.ndk_root.tpl"),
allow_single_file = True,
),
"_template_ndk_sysroot": attr.label(
default = Label(":BUILD.ndk_sysroot.tpl"),
allow_single_file = True,
),
"_template_target_systems": attr.label(
default = Label("//:target_systems.bzl.tpl"),
allow_single_file = True,
),
}

def _exec_configuration_android_ndk_repository_impl(ctx):
ndk_path = ctx.path(Label(ctx.attr.anchor)).dirname

return _android_ndk_repository_common(ctx, ndk_path)

exec_configuration_android_ndk_repository = repository_rule(
doc = "A repository rule that integrates the Android NDK from a workspace. Uses an anchor label to locate the NDK and requires the host platform and Clang resource directory to be specified. For local NDK installations, use android_ndk_repository instead.",
implementation = _exec_configuration_android_ndk_repository_impl,
attrs = _COMMON_ATTR | {
"anchor": attr.string(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused how this is supposed to work. The NDK distributables are separated by platform: https://developer.android.com/ndk/downloads

So how would you be able to have multiple platforms' NDKs installed in the same directory tree?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean. This is a stand-in for the value of ANDROID_NDK_HOME. Could you describe the dependency tree you're referring to?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say you're on a Mac host building for Linux RBE. On your Mac, you'd have a Mac NDK installed. Where would the files for the Linux NDK come from? You'd need to tell the Linux RBE where those are, somehow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have installed the Linux NDK to another directory on the Mac to support this

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. This would be an important detail to leave in a comment or doc somewhere, since it's not obvious.

On this topic: Can you think of a better way to have an end-to-end test for this feature? Maybe use rules_bazel_integration_test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't have time to address this before you merged (thank you by the way). I've created #128

doc = "A label to a file in the NDK directory. The directory containing this file is used as the NDK root path.",
mandatory = True,
),
"clang_resource_dir": attr.string(
doc = "The Clang resource directory path. Pass an empty string to auto-detect by running clang --print-resource-dir.",
mandatory = True,
),
"platform": attr.string(
doc = "The execution platform for the NDK toolchain (e.g., 'linux-x86_64', 'darwin-arm64', 'windows-x86_64'). Determines which prebuilt toolchain directory is used.",
values = _EXEC_CONSTRAINTS.keys(),
mandatory = True,
),
},
)

def _android_ndk_repository_impl(ctx):
ndk_path = ctx.attr.path or ctx.getenv("ANDROID_NDK_HOME", None)
if not ndk_path:
fail("Either the ANDROID_NDK_HOME environment variable or the " +
"path attribute of android_ndk_repository must be set.")

if ndk_path.startswith("$WORKSPACE_ROOT"):
ndk_path = str(ctx.workspace_root) + ndk_path.removeprefix("$WORKSPACE_ROOT")

return _android_ndk_repository_common(ctx, ndk_path)

android_ndk_repository = repository_rule(
attrs = {
"path": attr.string(),
"api_level": attr.int(),
"_build": attr.label(default = ":BUILD", allow_single_file = True),
"_template_ndk_root": attr.label(default = ":BUILD.ndk_root.tpl", allow_single_file = True),
"_template_target_systems": attr.label(default = ":target_systems.bzl.tpl", allow_single_file = True),
"_template_ndk_clang": attr.label(default = ":BUILD.ndk_clang.tpl", allow_single_file = True),
"_template_ndk_sysroot": attr.label(default = ":BUILD.ndk_sysroot.tpl", allow_single_file = True),
doc = "A repository rule that integrates the Android NDK from a local path. Uses ANDROID_NDK_HOME environment variable or the path attribute. This is the rule used by the bzlmod extension.",
implementation = _android_ndk_repository_impl,
attrs = _COMMON_ATTR | {
"path": attr.string(
doc = "The path to the local Android NDK installation. If not set, ANDROID_NDK_HOME environment variable is used. May start with $WORKSPACE_ROOT to reference the workspace root.",
),
},
environ = ["ANDROID_NDK_HOME"],
local = True,
implementation = _android_ndk_repository_impl,
)