Skip to content

Commit 4ab8198

Browse files
committed
Add cross-platform NDK repository support
1 parent 2966647 commit 4ab8198

8 files changed

Lines changed: 144 additions & 42 deletions

File tree

BUILD

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
# Do not remove, this empty BUILD file is necessary for _android_ndk_repository_impl in rules.bzl.
1+
# Note that this BUILD file is necessary for `android_ndk_repository` in `rules.bzl`.
2+
3+
exports_files([
4+
"LICENSE",
5+
] + glob([
6+
"*.tpl",
7+
]))

BUILD.ndk_clang.tpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Declarations for the NDK's Clang directory."""
22

3+
load("@rules_cc//cc/toolchains:cc_toolchain_suite.bzl", "cc_toolchain_suite")
34
load("@rules_cc//cc/toolchains:cc_toolchain.bzl", "cc_toolchain")
45
load("@@{repository_name}//:ndk_cc_toolchain_config.bzl", "ndk_cc_toolchain_config_rule")
56
load("//:target_systems.bzl", "TARGET_SYSTEM_NAMES")
@@ -52,7 +53,7 @@ filegroup(
5253
"lib64/**/*",
5354
"lib/**/*",
5455
],
55-
# Need to allow_empty here because previous NDK versions had
56+
# Need to allow_empty here because previous NDK versions had
5657
# "lib" & "lib64" directories but recent ones only have "lib".
5758
allow_empty = True,
5859
),

BUILD.ndk_root.tpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ alias(
1919
"@platforms//os:android",
2020
CPU_CONSTRAINT[target_system_name],
2121
],
22+
exec_compatible_with = {exec_compatible_with},
2223
toolchain = "//{clang_directory}:cc_toolchain_%s" % target_system_name,
2324
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
2425
) for target_system_name in TARGET_SYSTEM_NAMES]

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ http_archive(
102102
urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.17/rules_cc-0.0.17.tar.gz"],
103103
)
104104
105-
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
105+
load("@rules_android_ndk//:rules.bzl", "local_android_ndk_repository")
106106
107-
android_ndk_repository(name = "androidndk")
107+
local_android_ndk_repository(name = "androidndk")
108108
109109
register_toolchains("@androidndk//:all")
110110
```
111111

112112
Then, set the `ANDROID_NDK_HOME` environment variable or the `path` attribute of
113-
`android_ndk_repository` to the path of the local Android NDK installation
113+
`local_android_ndk_repository` to the path of the local Android NDK installation
114114
directory. If the path starts with `$WORKSPACE_ROOT`, then this string is
115115
replaced with the root path of the Bazel workspace.
116116

examples/basic/WORKSPACE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ http_archive(
7272
)
7373

7474
# --SNIP--: Everything below this lines goes into the example WORKSPACE snippet in the release notes.
75-
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
75+
load("@rules_android_ndk//:rules.bzl", "local_android_ndk_repository")
7676

77-
android_ndk_repository(name = "androidndk")
77+
local_android_ndk_repository(name = "androidndk")
7878

7979
register_toolchains("@androidndk//:all")

examples/cpu_features/WORKSPACE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ http_archive(
7474
)
7575

7676
# --SNIP--: Everything below this lines goes into the example WORKSPACE snippet in the release notes.
77-
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
77+
load("@rules_android_ndk//:rules.bzl", "local_android_ndk_repository")
7878

79-
android_ndk_repository(name = "androidndk")
79+
local_android_ndk_repository(name = "androidndk")
8080

8181
register_toolchains("@androidndk//:all")

extension.bzl

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

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

17-
load(":rules.bzl", "android_ndk_repository")
17+
load(":rules.bzl", "DEFAULT_API_LEVEL", "local_android_ndk_repository")
1818

1919
def _android_ndk_repository_extension_impl(module_ctx):
2020
root_modules = [m for m in module_ctx.modules if m.is_root and m.tags.configure]
@@ -31,17 +31,24 @@ def _android_ndk_repository_extension_impl(module_ctx):
3131
kwargs["api_level"] = module.tags.configure[0].api_level
3232
kwargs["path"] = module.tags.configure[0].path
3333

34-
android_ndk_repository(
34+
local_android_ndk_repository(
3535
name = "androidndk",
3636
**kwargs
3737
)
3838

39+
_CONFIGURE_TAG_CLASS = tag_class(attrs = {
40+
"api_level": attr.int(
41+
doc = "The minimum Android API level to target.",
42+
default = DEFAULT_API_LEVEL,
43+
),
44+
"path": attr.string(
45+
doc = "The path to the local Android NDK installation. If not set, ANDROID_NDK_HOME environment variable is used.",
46+
),
47+
})
48+
3949
android_ndk_repository_extension = module_extension(
4050
implementation = _android_ndk_repository_extension_impl,
4151
tag_classes = {
42-
"configure": tag_class(attrs = {
43-
"path": attr.string(),
44-
"api_level": attr.int(),
45-
}),
52+
"configure": _CONFIGURE_TAG_CLASS,
4653
},
4754
)

rules.bzl

Lines changed: 114 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,70 @@
1414

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

17-
def _android_ndk_repository_impl(ctx):
17+
DEFAULT_API_LEVEL = 31
18+
19+
_EXEC_CONSTRAINTS = {
20+
"darwin-arm64": [
21+
"@platforms//os:macos",
22+
"@platforms//cpu:aarch64",
23+
],
24+
"darwin-x86_64": [
25+
"@platforms//os:macos",
26+
"@platforms//cpu:x86_64",
27+
],
28+
"linux-x86_64": [
29+
"@platforms//os:linux",
30+
"@platforms//cpu:x86_64",
31+
],
32+
"windows-x86_64": [
33+
"@platforms//os:windows",
34+
"@platforms//cpu:x86_64",
35+
],
36+
}
37+
38+
def _get_clang_resource_dir(ctx, clang_directory, is_windows):
39+
clang_resource_dir = getattr(ctx.attr, "clang_resource_dir", None)
40+
if clang_resource_dir:
41+
return clang_resource_dir
42+
43+
result = ctx.execute([clang_directory + "/bin/clang", "--print-resource-dir"])
44+
if result.return_code != 0:
45+
fail("Failed to execute clang: %s" % result.stderr)
46+
stdout = result.stdout.strip()
47+
if is_windows:
48+
stdout = stdout.replace("\\", "/")
49+
return stdout.split(clang_directory)[1].strip("/")
50+
51+
def _android_ndk_repository_impl(ctx, ndk_path = None):
1852
"""Install the Android NDK files.
1953
2054
Args:
2155
ctx: An implementation context.
56+
ndk_path: The path to the ndk
2257
2358
Returns:
2459
A final dict of configuration attributes and values.
2560
"""
26-
ndk_path = ctx.attr.path or ctx.getenv("ANDROID_NDK_HOME", None)
27-
if not ndk_path:
28-
fail("Either the ANDROID_NDK_HOME environment variable or the " +
29-
"path attribute of android_ndk_repository must be set.")
61+
if ndk_path == None:
62+
ndk_path = ctx.path(Label(ctx.attr.anchor)).dirname
63+
3064
if ndk_path.startswith("$WORKSPACE_ROOT"):
3165
ndk_path = str(ctx.workspace_root) + ndk_path.removeprefix("$WORKSPACE_ROOT")
3266

3367
is_windows = False
3468
executable_extension = ""
35-
if ctx.os.name == "linux":
69+
exec_compatible_with = None
70+
platform = ctx.os.name
71+
if hasattr(ctx.attr, "platform"):
72+
platform = ctx.attr.platform
73+
exec_compatible_with = _EXEC_CONSTRAINTS[platform]
74+
75+
if platform.startswith("linux"):
3676
clang_directory = "toolchains/llvm/prebuilt/linux-x86_64"
37-
elif ctx.os.name == "mac os x":
77+
elif platform.startswith(("mac", "darwin")):
3878
# Note: darwin-x86_64 does indeed contain fat binaries with arm64 slices, too.
3979
clang_directory = "toolchains/llvm/prebuilt/darwin-x86_64"
40-
elif ctx.os.name.startswith("windows"):
80+
elif platform.startswith("windows"):
4181
clang_directory = "toolchains/llvm/prebuilt/windows-x86_64"
4282
is_windows = True
4383
executable_extension = ".exe"
@@ -48,15 +88,9 @@ def _android_ndk_repository_impl(ctx):
4888

4989
_create_symlinks(ctx, ndk_path, clang_directory, sysroot_directory)
5090

51-
api_level = ctx.attr.api_level or 31
91+
api_level = ctx.attr.api_level or DEFAULT_API_LEVEL
5292

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

6195
# Use a label relative to the workspace from which this repository rule came
6296
# to get the workspace name.
@@ -67,6 +101,7 @@ def _android_ndk_repository_impl(ctx):
67101
ctx.attr._template_ndk_root,
68102
{
69103
"{clang_directory}": clang_directory,
104+
"{exec_compatible_with}": repr(exec_compatible_with),
70105
},
71106
executable = False,
72107
)
@@ -83,11 +118,11 @@ def _android_ndk_repository_impl(ctx):
83118
"%s/BUILD.bazel" % clang_directory,
84119
ctx.attr._template_ndk_clang,
85120
{
86-
"{repository_name}": repository_name,
87121
"{api_level}": str(api_level),
88122
"{clang_resource_directory}": clang_resource_directory,
89-
"{sysroot_directory}": sysroot_directory,
90123
"{executable_extension}": executable_extension,
124+
"{repository_name}": repository_name,
125+
"{sysroot_directory}": sysroot_directory,
91126
},
92127
executable = False,
93128
)
@@ -124,16 +159,68 @@ def _create_symlinks(ctx, ndk_path, clang_directory, sysroot_directory):
124159
# TODO(#32): Remove this hack
125160
ctx.symlink(ndk_path + "sources", "ndk/sources")
126161

162+
_COMMON_ATTR = {
163+
"api_level": attr.int(
164+
doc = "The minimum Android API level to target.",
165+
default = DEFAULT_API_LEVEL,
166+
),
167+
"_build": attr.label(
168+
default = Label("//:BUILD"),
169+
allow_single_file = True,
170+
),
171+
"_template_ndk_clang": attr.label(
172+
default = Label("//:BUILD.ndk_clang.tpl"),
173+
allow_single_file = True,
174+
),
175+
"_template_ndk_root": attr.label(
176+
default = Label("//:BUILD.ndk_root.tpl"),
177+
allow_single_file = True,
178+
),
179+
"_template_ndk_sysroot": attr.label(
180+
default = Label(":BUILD.ndk_sysroot.tpl"),
181+
allow_single_file = True,
182+
),
183+
"_template_target_systems": attr.label(
184+
default = Label("//:target_systems.bzl.tpl"),
185+
allow_single_file = True,
186+
),
187+
}
188+
127189
android_ndk_repository = repository_rule(
128-
attrs = {
129-
"path": attr.string(),
130-
"api_level": attr.int(),
131-
"_build": attr.label(default = ":BUILD", allow_single_file = True),
132-
"_template_ndk_root": attr.label(default = ":BUILD.ndk_root.tpl", allow_single_file = True),
133-
"_template_target_systems": attr.label(default = ":target_systems.bzl.tpl", allow_single_file = True),
134-
"_template_ndk_clang": attr.label(default = ":BUILD.ndk_clang.tpl", allow_single_file = True),
135-
"_template_ndk_sysroot": attr.label(default = ":BUILD.ndk_sysroot.tpl", allow_single_file = True),
190+
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 local_android_ndk_repository instead.",
191+
implementation = _android_ndk_repository_impl,
192+
attrs = _COMMON_ATTR | {
193+
"anchor": attr.string(
194+
doc = "A label to a file in the NDK directory. The directory containing this file is used as the NDK root path.",
195+
mandatory = True,
196+
),
197+
"clang_resource_dir": attr.string(
198+
doc = "The Clang resource directory path. Pass an empty string to auto-detect by running clang --print-resource-dir.",
199+
mandatory = True,
200+
),
201+
"platform": attr.string(
202+
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.",
203+
values = _EXEC_CONSTRAINTS.keys(),
204+
mandatory = True,
205+
),
206+
},
207+
)
208+
209+
def _local_android_ndk_repository_impl(ctx):
210+
ndk_path = ctx.attr.path or ctx.getenv("ANDROID_NDK_HOME", None)
211+
if not ndk_path:
212+
fail("Either the ANDROID_NDK_HOME environment variable or the " +
213+
"path attribute of android_ndk_repository must be set.")
214+
215+
return _android_ndk_repository_impl(ctx, ndk_path)
216+
217+
local_android_ndk_repository = repository_rule(
218+
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.",
219+
implementation = _local_android_ndk_repository_impl,
220+
attrs = _COMMON_ATTR | {
221+
"path": attr.string(
222+
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.",
223+
),
136224
},
137225
local = True,
138-
implementation = _android_ndk_repository_impl,
139226
)

0 commit comments

Comments
 (0)