Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download the NDK in Bazel #51

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,31 @@ To use the Android NDK rules, add the following to your `WORKSPACE` file:
sha256 = RULES_ANDROID_NDK_SHA,
strip_prefix = "rules_android_ndk-%s" % RULES_ANDROID_NDK_COMMIT,
)
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
android_ndk_repository(name = "androidndk")

Then, set the `ANDROID_NDK_HOME` environment variable or the `path` attribute of
`android_ndk_repository` to the path of the local Android NDK installation
directory.
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
android_ndk_repository(name = "androidndk", version = "r25c")

# either register the toolchains, or use `--extra_toolchains` when invoking Bazel
register_toolchains("@androidndk//:all")

You can also customize the `base_url` attribute if, for example, you mirror the NDK archives
on a private server.

Some sha256 checksums are included in this repository, but these might not be up to date,
if you want to use a version of the NDK that's not included, you can also specify the `sha256sums`
attribute which maps platforms to checksums, like so:

```
android_ndk_repository(
name = "androidndk",
version = "r25c"
sha256sums = {
"windows": "f70093964f6cbbe19268f9876a20f92d3a593db3ad2037baadd25fd8d71e84e2",
"darwin": "b01bae969a5d0bfa0da18469f650a1628dc388672f30e0ba231da5c74245bc92",
"linux": "769ee342ea75f80619d985c2da990c48b3d8eaf45f48783a2d48870d04b46108",
}
)
```

The `api_level` attribute can also be used to set the Android API level to build
against.
Expand Down
110 changes: 54 additions & 56 deletions rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,72 @@

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

load(":sha256sums.bzl", "ndk_sha256")

def _ndk_platform(ctx):
os_name = ctx.os.name.lower()
if os_name == "linux":
return "linux"
elif os_name.startswith("mac os"):
return "darwin"
elif os_name.startswith("windows"):
return "windows"
else:
fail("Unsupported platform for the Android NDK: {}", ctx.os.name)

def _android_ndk_repository_impl(ctx):
"""Install the Android NDK files.
"""Download and extract the Android NDK files.

Args:
ctx: An implementation context.

Returns:
A final dict of configuration attributes and values.
"""
ndk_path = ctx.attr.path or ctx.os.environ.get("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 ctx.os.name == "linux":
ndk_version = ctx.attr.version
ndk_platform = _ndk_platform(ctx)
ndk_url = "{base_url}/android-ndk-{version}-{platform}.zip".format(
base_url = ctx.attr.base_url,
version = ndk_version,
platform = ndk_platform,
)

filename = ndk_url.split("/")[-1]
sha256 = ndk_sha256(filename, ctx)
prefix = "android-ndk-{}".format(ndk_version)

result = ctx.download_and_extract(url = ndk_url, sha256 = sha256, stripPrefix = prefix)
if not result.success:
fail("Failed to download NDK archive", ndk_url)

if ndk_platform == "linux":
clang_directory = "toolchains/llvm/prebuilt/linux-x86_64"
elif ctx.os.name == "mac os x":
elif ndk_platform == "darwin":
# Note: darwin-x86_64 does indeed contain fat binaries with arm64 slices, too.
clang_directory = "toolchains/llvm/prebuilt/darwin-x86_64"
elif ndk_platform == "windows":
clang_directory = "toolchains/llvm/prebuilt/windows-x86_64"
else:
fail("Unsupported operating system: " + ctx.os.name)
fail("Unsupported NDK platform", ndk_platform)

sysroot_directory = "%s/sysroot" % clang_directory

_create_symlinks(ctx, ndk_path, clang_directory, sysroot_directory)
# TODO(#32): Remove this hack
ctx.symlink("sources", "ndk/sources")

api_level = ctx.attr.api_level or 31
api_level = ctx.attr.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)
clang_resource_directory = result.stdout.strip().split(clang_directory)[1].strip("/")

# Use a label relative to the workspace from which this repository rule came
# to get the workspace name.
repository_name = Label("//:BUILD").workspace_name
repository_name = ctx.attr._build.workspace_name

ctx.template(
"BUILD",
Label("//:BUILD.ndk_root.tpl"),
"BUILD.bazel",
ctx.attr._template_ndk_root,
{
"{clang_directory}": clang_directory,
},
Expand All @@ -62,15 +88,15 @@ def _android_ndk_repository_impl(ctx):

ctx.template(
"target_systems.bzl",
Label("//:target_systems.bzl.tpl"),
ctx.attr._template_target_systems,
{
},
executable = False,
)

ctx.template(
"%s/BUILD" % clang_directory,
Label("//:BUILD.ndk_clang.tpl"),
ctx.attr._template_ndk_clang,
{
"{repository_name}": repository_name,
"{api_level}": str(api_level),
Expand All @@ -82,52 +108,24 @@ def _android_ndk_repository_impl(ctx):

ctx.template(
"%s/BUILD" % sysroot_directory,
Label("//:BUILD.ndk_sysroot.tpl"),
ctx.attr._template_ndk_sysroot,
{
"{api_level}": str(api_level),
},
executable = False,
)

# Manually create a partial symlink tree of the NDK to avoid creating BUILD
# files in the real NDK directory.
def _create_symlinks(ctx, ndk_path, clang_directory, sysroot_directory):
# Path needs to end in "/" for replace() below to work
if not ndk_path.endswith("/"):
ndk_path = ndk_path + "/"

for p in ctx.path(ndk_path + clang_directory).readdir():
repo_relative_path = str(p).replace(ndk_path, "")

# Skip sysroot directory, since it gets its own BUILD file
if repo_relative_path != sysroot_directory:
ctx.symlink(p, repo_relative_path)

for p in ctx.path(ndk_path + sysroot_directory).readdir():
repo_relative_path = str(p).replace(ndk_path, "")
ctx.symlink(p, repo_relative_path)

ctx.symlink(ndk_path + "sources", "sources")

# TODO(#32): Remove this hack
ctx.symlink(ndk_path + "sources", "ndk/sources")

_android_ndk_repository = repository_rule(
android_ndk_repository = repository_rule(
attrs = {
"path": attr.string(),
"api_level": attr.int(),
"api_level": attr.int(default = 31),
"version": attr.string(default = "r25b"),
"base_url": attr.string(default = "https://dl.google.com/android/repository"),
"sha256s": attr.string_dict(),
"_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),
},
local = True,
implementation = _android_ndk_repository_impl,
)

def android_ndk_repository(name, **kwargs):
_android_ndk_repository(
name = name,
**kwargs
)
native.register_toolchains("@%s//:all" % name)
native.bind(
name = "android/crosstool",
actual = "@%s//:toolchain" % name,
)
34 changes: 34 additions & 0 deletions sha256sums.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
SHA256 checksums for downloaded NDK archives
"""

_NDK_PACKAGE_SHA256SUMS = {
# r26
"android-ndk-r26-windows.zip": "a748c6634b96991e15cb8902ffa4a498bba2ec6aa8028526de3c4c9dfcf00663",
"android-ndk-r26-darwin.zip": "b2ab2fd17f71e2d2994c8c0ba2e48e99377806e05bf7477093344c26ab71dec0",
"android-ndk-r26-linux.zip": "1505c2297a5b7a04ed20b5d44da5665e91bac2b7c0fbcd3ae99b6ccc3a61289a",
# r25c
"android-ndk-r25c-windows.zip": "f70093964f6cbbe19268f9876a20f92d3a593db3ad2037baadd25fd8d71e84e2",
"android-ndk-r25c-darwin.zip": "b01bae969a5d0bfa0da18469f650a1628dc388672f30e0ba231da5c74245bc92",
"android-ndk-r25c-linux.zip": "769ee342ea75f80619d985c2da990c48b3d8eaf45f48783a2d48870d04b46108",
# r25b
"android-ndk-r25b-windows.zip": "c9a72beda4663ab714c9fb3dc06bb9b9f124f2b5199957c86cd6f57eb59fd49a",
"android-ndk-r25b-darwin.zip": "7e12f1f809878d4f5d5a901809277aa31546d36c10730fade2036d7d95b3607a",
"android-ndk-r25b-linux.zip": "403ac3e3020dd0db63a848dcaba6ceb2603bf64de90949d5c4361f848e44b005",
}

def ndk_sha256(filename, repository_ctx):
"""Get the sha256 for a specific NDK release

Args:
filename: the name of the NDK release file (as seen on https://developer.android.com/ndk/downloads)
repository_ctx: the repository_rule ctx

Returns:
a sha256sum string to use with ctx.download_and_extract
"""
internal_sha256 = _NDK_PACKAGE_SHA256SUMS.get(filename)
external_sha256 = repository_ctx.attr.sha256s.get(filename)
if internal_sha256 == None and external_sha256 == None:
fail("This NDK version is unsupported, and you haven't supplied a custom sha256sum for", filename)
return _NDK_PACKAGE_SHA256SUMS.get(filename)
14 changes: 14 additions & 0 deletions utils/checksums.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
# Download a specific release of the NDK for all three platforms and calculate SHA256 checksums
# to add to the sha256sums.bzl file.
set -eu

ndk_version=$1

cd "$(mktemp -d)"
echo "Working in $PWD"

curl -LO "https://dl.google.com/android/repository/android-ndk-${ndk_version}-windows.zip"
curl -LO "https://dl.google.com/android/repository/android-ndk-${ndk_version}-darwin.zip"
curl -LO "https://dl.google.com/android/repository/android-ndk-${ndk_version}-linux.zip"
sha256sum ./*