[bazel] Add hermetic Android NDK C++ toolchain.

See //toolchain/README.md to understand how I arrived at this design.

This toolchain can be tested via the following Bazel invocations:

    $ bazel build //... --config=android-arm-hermetic
    $ bazel build //... --config=android-arm-hermetic-rbe
    $ bazel build //... --config=android-arm64-hermetic
    $ bazel build //... --config=android-arm64-hermetic-rbe

In a follow-up CL I will delete the non-hermetic toolchain that uses the `android_ndk_repository`[1] built-in Bazel rule, and I will update the skcms-Android-ARM-Bazel-Build-RBE and skcms-Android-ARM64-Bazel-Build-RBE tasks to use the hermetic toolchain.

[1] https://bazel.build/reference/be/android#android_ndk_repository

Bug: skia:12400
Change-Id: I917c65a64e5e6320842d25940b2ca5477073f577
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/528654
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
diff --git a/.bazelrc b/.bazelrc
index 6313017..e342760 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -96,3 +96,30 @@
 build:android-arm64-rbe --extra_toolchains=@rbe_linux_toolchains//config:cc-toolchain
 build:android-arm64-rbe --extra_execution_platforms=@rbe_linux_toolchains//config:platform
 build:android-arm64-rbe --platforms=@rbe_linux_toolchains//config:platform
+
+# The experimental Android configurations below use a hermetic toolchain that does not require a
+# local copy of the Android NDK.
+
+# Experimental hermetic Android configuration for 32-bit ARM (armeabi-v7a ABI).
+build:android-arm-hermetic --crosstool_top=//toolchain:ndk_toolchain
+build:android-arm-hermetic --cpu=armeabi-v7a
+build:android-arm-hermetic --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
+
+# Experimental hermetic Android RBE configuration for 32-bit ARM (armeabi-v7a ABI).
+build:android-arm-hermetic-rbe --config=remote
+build:android-arm-hermetic-rbe --config=android-arm-hermetic
+build:android-arm-hermetic-rbe --extra_toolchains=@rbe_linux_toolchains//config:cc-toolchain
+build:android-arm-hermetic-rbe --extra_execution_platforms=@rbe_linux_toolchains//config:platform
+build:android-arm-hermetic-rbe --platforms=@rbe_linux_toolchains//config:platform
+
+# Experimental hermetic Android configuration for 64-bit ARM (arm64-v8a ABI).
+build:android-arm64-hermetic --crosstool_top=//toolchain:ndk_toolchain
+build:android-arm64-hermetic --cpu=arm64-v8a
+build:android-arm64-hermetic --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
+
+# Experimental hermetic Android RBE configuration for 64-bit ARM (arm64-v8a ABI).
+build:android-arm64-hermetic-rbe --config=remote
+build:android-arm64-hermetic-rbe --config=android-arm64-hermetic
+build:android-arm64-hermetic-rbe --extra_toolchains=@rbe_linux_toolchains//config:cc-toolchain
+build:android-arm64-hermetic-rbe --extra_execution_platforms=@rbe_linux_toolchains//config:platform
+build:android-arm64-hermetic-rbe --platforms=@rbe_linux_toolchains//config:platform
diff --git a/WORKSPACE b/WORKSPACE
index 21e7bfb..1386439 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -24,6 +24,12 @@
   api_level = 31,
 )
 
+load("//toolchain:download_toolchains.bzl", "download_toolchains")
+
+download_toolchains(
+    android_ndk_repository_name = "android_ndk",
+)
+
 ##################################
 # Docker rules and dependencies. #
 ##################################
diff --git a/toolchain/BUILD.bazel b/toolchain/BUILD.bazel
new file mode 100644
index 0000000..091c575
--- /dev/null
+++ b/toolchain/BUILD.bazel
@@ -0,0 +1,77 @@
+load(":ndk_cc_toolchain_config.bzl", "ndk_cc_toolchain_config")
+
+package(default_visibility = ["//visibility:public"])
+
+# https://bazel.build/reference/be/c-cpp#cc_toolchain_suite
+cc_toolchain_suite(
+    name = "ndk_toolchain",
+    toolchains = {
+        "arm64-v8a": ":ndk_arm64-v8a_toolchain",
+        "armeabi-v7a": ":ndk_armeabi-v7a_toolchain",
+    },
+    tags = [
+      "manual",  # Exclude it from wildcard queries, e.g. "bazel build //...".
+    ],
+)
+
+############################
+# arm64-v8a C++ toolchain. #
+############################
+
+# https://bazel.build/reference/be/c-cpp#cc_toolchain
+cc_toolchain(
+    name = "ndk_arm64-v8a_toolchain",
+    all_files = ":ndk_arm64-v8a_toolchain_all_files",
+    ar_files = ":ndk_arm64-v8a_toolchain_all_files",
+    compiler_files = ":ndk_arm64-v8a_toolchain_all_files",
+    dwp_files = ":ndk_arm64-v8a_toolchain_all_files",
+    dynamic_runtime_lib = "@android_ndk//:arm64-v8a_dynamic_runtime_libraries",
+    linker_files = ":ndk_arm64-v8a_toolchain_all_files",
+    objcopy_files = ":ndk_arm64-v8a_toolchain_all_files",
+    strip_files = ":ndk_arm64-v8a_toolchain_all_files",
+    static_runtime_lib = "@android_ndk//:arm64-v8a_static_runtime_libraries",
+    supports_param_files = False,
+    toolchain_identifier = "ndk-arm64-v8a-toolchain",
+    toolchain_config = ":ndk_arm64-v8a_toolchain_config",
+)
+
+filegroup(
+    name = "ndk_arm64-v8a_toolchain_all_files",
+    srcs = glob(["trampolines/*.sh"]) + ["@android_ndk//:arm64-v8a_all_files"],
+)
+
+ndk_cc_toolchain_config(
+    name = "ndk_arm64-v8a_toolchain_config",
+    cpu = "arm64-v8a",
+)
+
+##############################
+# armeabi-v7a C++ toolchain. #
+##############################
+
+# https://bazel.build/reference/be/c-cpp#cc_toolchain
+cc_toolchain(
+    name = "ndk_armeabi-v7a_toolchain",
+    all_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    ar_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    compiler_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    dwp_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    dynamic_runtime_lib = "@android_ndk//:armeabi-v7a_dynamic_runtime_libraries",
+    linker_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    objcopy_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    strip_files = ":ndk_armeabi-v7a_toolchain_all_files",
+    static_runtime_lib = "@android_ndk//:armeabi-v7a_static_runtime_libraries",
+    supports_param_files = False,
+    toolchain_identifier = "ndk-armeabi-v7a-toolchain",
+    toolchain_config = ":ndk_armeabi-v7a_toolchain_config",
+)
+
+filegroup(
+    name = "ndk_armeabi-v7a_toolchain_all_files",
+    srcs = glob(["trampolines/*.sh"]) + ["@android_ndk//:armeabi-v7a_all_files"],
+)
+
+ndk_cc_toolchain_config(
+    name = "ndk_armeabi-v7a_toolchain_config",
+    cpu = "armeabi-v7a",
+)
diff --git a/toolchain/README.md b/toolchain/README.md
new file mode 100644
index 0000000..dc278ef
--- /dev/null
+++ b/toolchain/README.md
@@ -0,0 +1,37 @@
+# Hermetic Android NDK C++ toolchain
+
+This directory defines a hermetic C++ toolchain suite to compile with the Android NDK.
+
+## Motivation
+
+Bazel has a built-in
+[`android_ndk_repository`](https://bazel.build/reference/be/android#android_ndk_repository) rule,
+which generates C++ toolchains based on a local NDK installation provided via the
+`ANDROID_NDK_HOME` environment variable. However, this rule breaks
+[hermeticity](https://bazel.build/concepts/hermeticity), and requires the user to provide an NDK
+installation.
+
+This directory provides a `download_android_ndk` repository rule, which downloads the Android NDK
+under `external/android_ndk`, and a C++ toolchain suite that targets 32- and 64-bit ARM.
+
+## Design
+
+The C++ toolchain suite is based on the C++ toolchain generated by the `android_ndk_repository`
+rule.
+
+Steps taken:
+
+- Build SkCMS with the `android_ndk_repository` rule at
+[this revision](https://skia.googlesource.com/skcms/+/30c8e303800c256febb03a09fdcda7f75d119b1b/WORKSPACE#22).
+- Inspect the contents of `bazel-skcms/external/androidndk/BUILD.bazel` and
+`bazel-skcms/external/androidndk/cc_toolchain_config.bzl`, which are generated by said rule.
+- Extract the useful parts into the C++ toolchain defined in this directory.
+
+### Trampoline scripts
+
+The
+[`cc_common.create_cc_toolchain_config_info`](https://bazel.build/rules/lib/cc_common#create_cc_toolchain_config_info)
+function expects tool paths to point to files under the directory in which it is invoked. This
+means we cannot directly reference tools under `external/android_ndk`. The solution is to use
+"trampoline" scripts that pass through any command-line arguments to the NDK binaries under
+`external/android_sdk`.
diff --git a/toolchain/download_toolchains.bzl b/toolchain/download_toolchains.bzl
new file mode 100644
index 0000000..444483a
--- /dev/null
+++ b/toolchain/download_toolchains.bzl
@@ -0,0 +1,42 @@
+"""
+This module defines the download_android_ndk repository rule.
+"""
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+_download_android_ndk_rule_name = "android_ndk"
+
+def _download_android_ndk(name):
+    """Downloads the Android NDK under external/android_ndk.
+
+    Args:
+      name: Name of the external repository. This MUST equal "android_ndk".
+    """
+
+    # The toolchain assumes that the NDK is found at /external/android_ndk, so we must enforce this
+    # name.
+    if name != _download_android_ndk_rule_name:
+        fail("The name of this rule MUST be \"%s\"" % _download_android_ndk_rule_name)
+
+    # Archive taken from https://github.com/android/ndk/wiki/Unsupported-Downloads#r21e.
+    http_archive(
+        name = _download_android_ndk_rule_name,
+        urls = [
+            "https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip",
+            "https://storage.googleapis.com/skia-world-readable/bazel/ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e.zip",
+        ],
+        sha256 = "ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e",
+        strip_prefix = "android-ndk-r21e",
+        build_file = Label("//toolchain:ndk.BUILD"),
+    )
+
+def download_toolchains(android_ndk_repository_name):
+    """Downloads the toolchains needed to build this repository.
+
+    Args:
+      android_ndk_repository_name: Name of the external repository with the android NDK. This MUST
+        equal "android_ndk".
+    """
+    _download_android_ndk(android_ndk_repository_name)
+
+# Path to the Android NDK from the point of view of the cc_toolchain rule.
+NDK_PATH = "external/%s" % _download_android_ndk_rule_name
diff --git a/toolchain/ndk.BUILD b/toolchain/ndk.BUILD
new file mode 100644
index 0000000..347a53a
--- /dev/null
+++ b/toolchain/ndk.BUILD
@@ -0,0 +1,65 @@
+# This file is based on the `external/androidndk/BUILD.bazel` file produced by the built-in
+# `android_ndk_repository` Bazel rule[1], which was used to build the SkCMS repository up until
+# this revision[2].
+#
+# The paths in this file point to locations inside the expanded Android NDK ZIP file (found at
+# external/android_ndk), and must be updated every time we upgrade to a new Android NDK version.
+#
+# [1] https://github.com/bazelbuild/bazel/blob/4710ef82ce34572878e07c52e83a0144d707f140/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryFunction.java
+# [2] https://skia.googlesource.com/skcms/+/30c8e303800c256febb03a09fdcda7f75d119b1b/WORKSPACE#22
+
+filegroup(
+    name = "arm64-v8a_all_files",
+    srcs = glob(["toolchains/llvm/**"]) + glob([
+        "platforms/android-29/arch-arm64/**/*",
+        "sources/cxx-stl/llvm-libc++/include/**/*",
+        "sources/cxx-stl/llvm-libc++abi/include/**/*",
+        "sources/android/support/include/**/*",
+        "sysroot/**/*",
+        "toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/**/*",
+    ]) + [
+      ":arm64-v8a_dynamic_runtime_libraries",
+      ":arm64-v8a_static_runtime_libraries",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "arm64-v8a_dynamic_runtime_libraries",
+    srcs = glob(["sources/cxx-stl/llvm-libc++/libs/arm64-v8a/*.so"]),
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "arm64-v8a_static_runtime_libraries",
+    srcs = glob(["sources/cxx-stl/llvm-libc++/libs/arm64-v8a/*.a"]),
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "armeabi-v7a_all_files",
+    srcs = glob(["toolchains/llvm/**"]) + glob([
+        "platforms/android-29/arch-arm/**/*",
+        "sources/cxx-stl/llvm-libc++/include/**/*",
+        "sources/cxx-stl/llvm-libc++abi/include/**/*",
+        "sources/android/support/include/**/*",
+        "sysroot/**/*",
+        "toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/**/*",
+    ]) + [
+      ":armeabi-v7a_dynamic_runtime_libraries",
+      ":armeabi-v7a_static_runtime_libraries",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "armeabi-v7a_dynamic_runtime_libraries",
+    srcs = glob(["sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*.so"]),
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "armeabi-v7a_static_runtime_libraries",
+    srcs = glob(["sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*.a"]),
+    visibility = ["//visibility:public"],
+)
diff --git a/toolchain/ndk_cc_toolchain_config.bzl b/toolchain/ndk_cc_toolchain_config.bzl
new file mode 100644
index 0000000..ccf38ea
--- /dev/null
+++ b/toolchain/ndk_cc_toolchain_config.bzl
@@ -0,0 +1,404 @@
+"""This module defines the ndk_cc_toolchain_config rule.
+
+This file is based on the `external/androidndk/cc_toolchain_config.bzl` file produced by the
+built-in `android_ndk_repository` Bazel rule[1], which was used to build the SkCMS repository up
+until this revision[2].
+
+The paths in this file point to locations inside the expanded Android NDK ZIP file (found at
+external/android_ndk), and must be updated every time we upgrade to a new Android NDK version.
+
+[1] https://github.com/bazelbuild/bazel/blob/4710ef82ce34572878e07c52e83a0144d707f140/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryFunction.java#L422
+[2] https://skia.googlesource.com/skcms/+/30c8e303800c256febb03a09fdcda7f75d119b1b/WORKSPACE#22
+"""
+
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+load(
+    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
+    "feature",
+    "flag_group",
+    "flag_set",
+    "tool_path",
+    "with_feature_set",
+)
+load("download_toolchains.bzl", "NDK_PATH")
+
+# Supported CPUs.
+_ARMEABI_V7A = "armeabi-v7a"
+_ARM64_V8A = "arm64-v8a"
+
+_all_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.assemble,
+    ACTION_NAMES.preprocess_assemble,
+    ACTION_NAMES.cpp_header_parsing,
+    ACTION_NAMES.cpp_module_compile,
+    ACTION_NAMES.cpp_module_codegen,
+    ACTION_NAMES.clif_match,
+    ACTION_NAMES.lto_backend,
+]
+
+_all_link_actions = [
+    ACTION_NAMES.cpp_link_executable,
+    ACTION_NAMES.cpp_link_dynamic_library,
+    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+]
+
+def _get_default_compile_flags(cpu):
+    if cpu == _ARMEABI_V7A:
+        return [
+            "-D__ANDROID_API__=29",
+            "-isystem",
+            NDK_PATH + "/sysroot/usr/include/arm-linux-androideabi",
+            "-target",
+            "armv7-none-linux-androideabi",
+            "-march=armv7-a",
+            "-mfloat-abi=softfp",
+            "-mfpu=vfpv3-d16",
+            "-gcc-toolchain",
+            NDK_PATH + "/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64",
+            "-fpic",
+            "-no-canonical-prefixes",
+            "-Wno-invalid-command-line-argument",
+            "-Wno-unused-command-line-argument",
+            "-funwind-tables",
+            "-fstack-protector-strong",
+            "-fno-addrsig",
+            "-Werror=return-type",
+            "-Werror=int-to-pointer-cast",
+            "-Werror=pointer-to-int-cast",
+            "-Werror=implicit-function-declaration",
+        ]
+    if cpu == _ARM64_V8A:
+        return [
+            "-gcc-toolchain",
+            NDK_PATH + "/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64",
+            "-target",
+            "aarch64-none-linux-android",
+            "-fpic",
+            "-isystem",
+            NDK_PATH + "/sysroot/usr/include/aarch64-linux-android",
+            "-D__ANDROID_API__=29",
+            "-no-canonical-prefixes",
+            "-Wno-invalid-command-line-argument",
+            "-Wno-unused-command-line-argument",
+            "-funwind-tables",
+            "-fstack-protector-strong",
+            "-fno-addrsig",
+            "-Werror=return-type",
+            "-Werror=int-to-pointer-cast",
+            "-Werror=pointer-to-int-cast",
+            "-Werror=implicit-function-declaration",
+        ]
+    fail("Unknown CPU: " + cpu)
+
+def _get_default_link_flags(cpu):
+    if cpu == _ARMEABI_V7A:
+        return [
+            "-target",
+            "armv7-none-linux-androideabi",
+            "-gcc-toolchain",
+            NDK_PATH + "/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64",
+            "-L",
+            NDK_PATH + "/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a",
+            "-no-canonical-prefixes",
+            "-Wl,-z,relro",
+            "-Wl,--gc-sections",
+        ]
+    if cpu == _ARM64_V8A:
+        return [
+            "-gcc-toolchain",
+            NDK_PATH + "/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64",
+            "-target",
+            "aarch64-none-linux-android",
+            "-L",
+            NDK_PATH + "/sources/cxx-stl/llvm-libc++/libs/arm64-v8a",
+            "-no-canonical-prefixes",
+            "-Wl,-z,relro",
+            "-Wl,--gc-sections",
+        ]
+    fail("Unknown CPU: " + cpu)
+
+def _get_default_dbg_flags(cpu):
+    if cpu == _ARMEABI_V7A:
+        return ["-g", "-fno-strict-aliasing", "-O0", "-UNDEBUG"]
+    if cpu == _ARM64_V8A:
+        return ["-O0", "-g", "-UNDEBUG"]
+    fail("Unknown CPU: " + cpu)
+
+def _get_default_opt_flags(cpu):
+    if cpu == _ARMEABI_V7A:
+        return ["-mthumb", "-Os", "-g", "-DNDEBUG"]
+    if cpu == _ARM64_V8A:
+        return ["-O2", "-g", "-DNDEBUG"]
+    fail("Unknown CPU: " + cpu)
+
+def _get_toolchain_identifier(cpu):
+    if cpu == _ARMEABI_V7A:
+        return "ndk-armeabi-v7a-toolchain"
+    if cpu == _ARM64_V8A:
+        return "ndk-arm64-v8a-toolchain"
+    fail("Unknown CPU: " + cpu)
+
+def _get_target_system_name(cpu):
+    if cpu == _ARMEABI_V7A:
+        return "arm-linux-androideabi"
+    if cpu == _ARM64_V8A:
+        return "aarch64-linux-android"
+    fail("Unknown CPU: " + cpu)
+
+def _get_builtin_sysroot(cpu):
+    if cpu == _ARMEABI_V7A:
+        return NDK_PATH + "/platforms/android-29/arch-arm"
+    if cpu == _ARM64_V8A:
+        return NDK_PATH + "/platforms/android-29/arch-arm64"
+    fail("Unknown CPU: " + cpu)
+
+def _get_tool_paths(cpu):
+    # The cc_common.create_cc_toolchain_config_info function expects tool paths to point to files
+    # under the directory in which it is invoked. This means we cannot directly reference tools
+    # under external/android_ndk. The solution is to use "trampoline" scripts that pass through
+    # any command-line arguments to the NDK binaries under external/android_sdk.
+
+    if cpu == _ARMEABI_V7A:
+        return [
+            tool_path(
+                name = "ar",
+                path = "trampolines/arm-linux-androideabi-ar.sh",
+            ),
+            tool_path(
+                name = "cpp",
+                path = "trampolines/clang.sh",
+            ),
+            tool_path(
+                name = "dwp",
+                path = "trampolines/arm-linux-androideabi-dwp.sh",
+            ),
+            tool_path(
+                name = "gcc",
+                path = "trampolines/clang.sh",
+            ),
+            tool_path(
+                name = "gcov",
+                path = "/bin/false",
+            ),
+            tool_path(
+                name = "ld",
+                path = "trampolines/arm-linux-androideabi-ld.sh",
+            ),
+            tool_path(
+                name = "nm",
+                path = "trampolines/arm-linux-androideabi-nm.sh",
+            ),
+            tool_path(
+                name = "objcopy",
+                path = "trampolines/arm-linux-androideabi-objcopy.sh",
+            ),
+            tool_path(
+                name = "objdump",
+                path = "trampolines/arm-linux-androideabi-objdump.sh",
+            ),
+            tool_path(
+                name = "strip",
+                path = "trampolines/arm-linux-androideabi-strip.sh",
+            ),
+        ]
+    if cpu == _ARM64_V8A:
+        return [
+            tool_path(
+                name = "ar",
+                path = "trampolines/aarch64-linux-android-ar.sh",
+            ),
+            tool_path(
+                name = "cpp",
+                path = "trampolines/clang.sh",
+            ),
+            tool_path(
+                name = "dwp",
+                path = "trampolines/aarch64-linux-android-dwp.sh",
+            ),
+            tool_path(
+                name = "gcc",
+                path = "trampolines/clang.sh",
+            ),
+            tool_path(
+                name = "gcov",
+                path = "/bin/false",
+            ),
+            tool_path(
+                name = "ld",
+                path = "trampolines/aarch64-linux-android-ld.sh",
+            ),
+            tool_path(
+                name = "nm",
+                path = "trampolines/aarch64-linux-android-nm.sh",
+            ),
+            tool_path(
+                name = "objcopy",
+                path = "trampolines/aarch64-linux-android-objcopy.sh",
+            ),
+            tool_path(
+                name = "objdump",
+                path = "trampolines/aarch64-linux-android-objdump.sh",
+            ),
+            tool_path(
+                name = "strip",
+                path = "trampolines/aarch64-linux-android-strip.sh",
+            ),
+        ]
+    fail("Unknown CPU: " + cpu)
+
+def _ndk_cc_toolchain_config_impl(ctx):
+    default_compile_flags = _get_default_compile_flags(ctx.attr.cpu)
+    unfiltered_compile_flags = [
+        "-isystem",
+        NDK_PATH + "/sources/cxx-stl/llvm-libc++/include",
+        "-isystem",
+        NDK_PATH + "/sources/cxx-stl/llvm-libc++abi/include",
+        "-isystem",
+        NDK_PATH + "/sources/android/support/include",
+        "-isystem",
+        NDK_PATH + "/sysroot/usr/include",
+    ]
+    default_link_flags = _get_default_link_flags(ctx.attr.cpu)
+    default_fastbuild_flags = [""]
+    default_dbg_flags = _get_default_dbg_flags(ctx.attr.cpu)
+    default_opt_flags = _get_default_opt_flags(ctx.attr.cpu)
+
+    opt_feature = feature(name = "opt")
+    fastbuild_feature = feature(name = "fastbuild")
+    dbg_feature = feature(name = "dbg")
+    supports_dynamic_linker_feature = feature(name = "supports_dynamic_linker", enabled = True)
+    supports_pic_feature = feature(name = "supports_pic", enabled = True)
+    static_link_cpp_runtimes_feature = feature(name = "static_link_cpp_runtimes", enabled = True)
+
+    default_compile_flags_feature = feature(
+        name = "default_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = _all_compile_actions,
+                flag_groups = [flag_group(flags = default_compile_flags)],
+            ),
+            flag_set(
+                actions = _all_compile_actions,
+                flag_groups = [flag_group(flags = default_fastbuild_flags)],
+                with_features = [with_feature_set(features = ["fastbuild"])],
+            ),
+            flag_set(
+                actions = _all_compile_actions,
+                flag_groups = [flag_group(flags = default_dbg_flags)],
+                with_features = [with_feature_set(features = ["dbg"])],
+            ),
+            flag_set(
+                actions = _all_compile_actions,
+                flag_groups = [flag_group(flags = default_opt_flags)],
+                with_features = [with_feature_set(features = ["opt"])],
+            ),
+        ],
+    )
+
+    default_link_flags_feature = feature(
+        name = "default_link_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = _all_link_actions,
+                flag_groups = [flag_group(flags = default_link_flags)],
+            ),
+        ],
+    )
+
+    user_compile_flags_feature = feature(
+        name = "user_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = _all_compile_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["%{user_compile_flags}"],
+                        iterate_over = "user_compile_flags",
+                        expand_if_available = "user_compile_flags",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    sysroot_feature = feature(
+        name = "sysroot",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = _all_compile_actions + _all_link_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["--sysroot=%{sysroot}"],
+                        expand_if_available = "sysroot",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    unfiltered_compile_flags_feature = feature(
+        name = "unfiltered_compile_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = _all_compile_actions,
+                flag_groups = [flag_group(flags = unfiltered_compile_flags)],
+            ),
+        ],
+    )
+
+    features = [
+        default_compile_flags_feature,
+        default_link_flags_feature,
+        supports_dynamic_linker_feature,
+        supports_pic_feature,
+        static_link_cpp_runtimes_feature,
+        fastbuild_feature,
+        dbg_feature,
+        opt_feature,
+        user_compile_flags_feature,
+        sysroot_feature,
+        unfiltered_compile_flags_feature,
+    ]
+
+    cxx_builtin_include_directories = [
+        NDK_PATH + "/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.9/include",
+        "%sysroot%/usr/include",
+        NDK_PATH + "/sysroot/usr/include",
+    ]
+
+    # https://bazel.build/rules/lib/cc_common#create_cc_toolchain_config_info
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        toolchain_identifier = _get_toolchain_identifier(ctx.attr.cpu),
+        host_system_name = "local",
+        target_system_name = _get_target_system_name(ctx.attr.cpu),
+        target_cpu = ctx.attr.cpu,
+        target_libc = "local",
+        compiler = "clang9.0.9",
+        abi_version = ctx.attr.cpu,
+        abi_libc_version = "local",
+        features = features,
+        tool_paths = _get_tool_paths(ctx.attr.cpu),
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        builtin_sysroot = _get_builtin_sysroot(ctx.attr.cpu),
+    )
+
+ndk_cc_toolchain_config = rule(
+    implementation = _ndk_cc_toolchain_config_impl,
+    attrs = {
+        "cpu": attr.string(
+            mandatory = True,
+            values = [_ARMEABI_V7A, _ARM64_V8A],
+            doc = "Target CPU.",
+        )
+    },
+    provides = [CcToolchainConfigInfo],
+)
diff --git a/toolchain/trampolines/aarch64-linux-android-ar.sh b/toolchain/trampolines/aarch64-linux-android-ar.sh
new file mode 100755
index 0000000..08b0744
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-ar.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar $@
diff --git a/toolchain/trampolines/aarch64-linux-android-dwp.sh b/toolchain/trampolines/aarch64-linux-android-dwp.sh
new file mode 100755
index 0000000..de8fb1d
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-dwp.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-dwp $@
diff --git a/toolchain/trampolines/aarch64-linux-android-ld.sh b/toolchain/trampolines/aarch64-linux-android-ld.sh
new file mode 100755
index 0000000..6d0158a
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-ld.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ld $@
diff --git a/toolchain/trampolines/aarch64-linux-android-nm.sh b/toolchain/trampolines/aarch64-linux-android-nm.sh
new file mode 100755
index 0000000..4bf5e74
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-nm.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-nm $@
diff --git a/toolchain/trampolines/aarch64-linux-android-objcopy.sh b/toolchain/trampolines/aarch64-linux-android-objcopy.sh
new file mode 100755
index 0000000..84d17a3
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-objcopy.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-objcopy $@
diff --git a/toolchain/trampolines/aarch64-linux-android-objdump.sh b/toolchain/trampolines/aarch64-linux-android-objdump.sh
new file mode 100755
index 0000000..c38e60d
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-objdump.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-objdump $@
diff --git a/toolchain/trampolines/aarch64-linux-android-strip.sh b/toolchain/trampolines/aarch64-linux-android-strip.sh
new file mode 100755
index 0000000..e396c9d
--- /dev/null
+++ b/toolchain/trampolines/aarch64-linux-android-strip.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-ar.sh b/toolchain/trampolines/arm-linux-androideabi-ar.sh
new file mode 100755
index 0000000..ee99c68
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-ar.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-dwp.sh b/toolchain/trampolines/arm-linux-androideabi-dwp.sh
new file mode 100755
index 0000000..b046d28
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-dwp.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-dwp $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-ld.sh b/toolchain/trampolines/arm-linux-androideabi-ld.sh
new file mode 100755
index 0000000..c9c7a87
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-ld.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ld $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-nm.sh b/toolchain/trampolines/arm-linux-androideabi-nm.sh
new file mode 100755
index 0000000..fe1fa2a
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-nm.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-nm $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-objcopy.sh b/toolchain/trampolines/arm-linux-androideabi-objcopy.sh
new file mode 100755
index 0000000..9fc92a3
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-objcopy.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objcopy $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-objdump.sh b/toolchain/trampolines/arm-linux-androideabi-objdump.sh
new file mode 100755
index 0000000..b161b35
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-objdump.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump $@
diff --git a/toolchain/trampolines/arm-linux-androideabi-strip.sh b/toolchain/trampolines/arm-linux-androideabi-strip.sh
new file mode 100755
index 0000000..b8f7499
--- /dev/null
+++ b/toolchain/trampolines/arm-linux-androideabi-strip.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip $@
diff --git a/toolchain/trampolines/clang.sh b/toolchain/trampolines/clang.sh
new file mode 100755
index 0000000..2d743f0
--- /dev/null
+++ b/toolchain/trampolines/clang.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+external/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang $@
diff --git a/toolchain/trampolines/gen_trampolines/gen_trampolines.go b/toolchain/trampolines/gen_trampolines/gen_trampolines.go
new file mode 100644
index 0000000..68e66a3
--- /dev/null
+++ b/toolchain/trampolines/gen_trampolines/gen_trampolines.go
@@ -0,0 +1,75 @@
+// Helper program to generate trampoline scripts for NDK tools.
+//
+// This program is meant to be run by hand when making changes to the hermetic Android NDK
+// toolchain, e.g. when upgrading to a new Android NDK version.
+//
+// Trampoline scripts are necessary because the `cc_common.create_cc_toolchain_config_info`[1]
+// built-in Bazel function expects tool paths to point to files under the directory in which it is
+// invoked, thus we cannot directly reference tools under `external/android_ndk`. The solution is
+// to use trampoline scripts that pass through any command-line arguments to the NDK binaries under
+// `external/android_sdk`.
+//
+// [1] https://bazel.build/rules/lib/cc_common#create_cc_toolchain_config_info
+package main
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+const bazelNdkPath = "external/android_ndk"
+
+// Paths relative to the Android NDK root directory. These paths can be determined by inspecting
+// the Android NDK ZIP file downloaded by the `download_toolchains` macro defined in
+// //toolchains/download_toolchains.bzl.
+var tools = []string{
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar",
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-dwp",
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ld",
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-nm",
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objcopy",
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump",
+	"toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-dwp",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ld",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-nm",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-objcopy",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-objdump",
+	"toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip",
+	"toolchains/llvm/prebuilt/linux-x86_64/bin/clang",
+}
+
+const trampolineScriptTemplate = `#!/bin/sh
+%s $@
+`
+
+func main() {
+	ndkDirFlag := flag.String("ndk-dir", "", "Path to a local copy of the NDK. Used only to verify that the tool paths assumed by this program are valid. Required.")
+	outDirFlag := flag.String("out-dir", "", "Directory where to save the trampoline scripts. Required.")
+	flag.Parse()
+
+	if *ndkDirFlag == "" || *outDirFlag == "" {
+		flag.Usage()
+		os.Exit(1)
+	}
+
+	for _, tool := range tools {
+		// Verify that the tool exists in the NDK.
+		ndkPath := filepath.Join(*ndkDirFlag, tool)
+		if _, err := os.Stat(ndkPath); errors.Is(err, os.ErrNotExist) {
+			fmt.Fprintf(os.Stderr, "File %s not found.", ndkPath)
+			os.Exit(1)
+		}
+
+		// Generate trampoline script.
+		trampolineScript := fmt.Sprintf(trampolineScriptTemplate, filepath.Join(bazelNdkPath, tool))
+		trampolineScriptPath := filepath.Join(*outDirFlag, filepath.Base(tool)+".sh")
+		if err := os.WriteFile(trampolineScriptPath, []byte(trampolineScript), 0750); err != nil {
+			fmt.Fprintf(os.Stderr, "Error writing file %s: %s", trampolineScriptPath, err)
+		}
+	}
+}