xref: /aosp_15_r20/external/bazelbuild-rules_android/rules/android_sdk_repository/rule.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Bazel rule for Android sdk repository."""
16
17load("//rules:android_revision.bzl", "compare_android_revisions", "parse_android_revision")
18
19_SDK_REPO_TEMPLATE = Label(":template.bzl")
20_EMPTY_SDK_REPO_TEMPLATE = Label(":empty.template.bzl")
21
22_BUILD_TOOLS_DIR = "build-tools"
23_PLATFORMS_DIR = "platforms"
24_SYSTEM_IMAGES_DIR = "system-images"
25_LOCAL_MAVEN_REPOS = [
26    "extras/android/m2repository",
27    "extras/google/m2repository",
28    "extras/m2repository",
29]
30_DIRS_TO_LINK = [
31    _BUILD_TOOLS_DIR,
32    "emulator",
33    "platform-tools",
34    _PLATFORMS_DIR,
35    _SYSTEM_IMAGES_DIR,
36] + _LOCAL_MAVEN_REPOS
37
38_MIN_BUILD_TOOLS_VERSION = parse_android_revision("30.0.0")
39
40def _read_api_levels(repo_ctx, android_sdk_path):
41    platforms_dir = "%s/%s" % (android_sdk_path, _PLATFORMS_DIR)
42    api_levels = []
43    platforms_path = repo_ctx.path(platforms_dir)
44    if not platforms_path.exists:
45        return api_levels
46    for entry in platforms_path.readdir():
47        name = entry.basename
48        if name.startswith("android-"):
49            level = int(name[len("android-"):])
50            api_levels.append(level)
51    return api_levels
52
53def _newest_build_tools(repo_ctx, android_sdk_path):
54    build_tools_dir = "%s/%s" % (android_sdk_path, _BUILD_TOOLS_DIR)
55    highest = None
56    build_tools_path = repo_ctx.path(build_tools_dir)
57    if not build_tools_path.exists:
58        return None
59    for entry in build_tools_path.readdir():
60        name = entry.basename
61        revision = parse_android_revision(name)
62        highest = compare_android_revisions(highest, revision)
63    return highest
64
65def _find_system_images(repo_ctx, android_sdk_path):
66    system_images_dir = "%s/%s" % (android_sdk_path, _SYSTEM_IMAGES_DIR)
67    system_images = []
68
69    system_images_path = repo_ctx.path(system_images_dir)
70    if not system_images_path.exists:
71        return system_images
72
73    # The directory structure needed is "system-images/android-API/apis-enabled/arch"
74    for api_entry in system_images_path.readdir():
75        for enabled_entry in api_entry.readdir():
76            for arch_entry in enabled_entry.readdir():
77                image_path = "%s/%s/%s/%s" % (
78                    _SYSTEM_IMAGES_DIR,
79                    api_entry.basename,
80                    enabled_entry.basename,
81                    arch_entry.basename,
82                )
83                system_images.append(image_path)
84
85    return system_images
86
87def _android_sdk_repository_impl(repo_ctx):
88    # Determine the SDK path to use, either from the attribute or the environment.
89    android_sdk_path = repo_ctx.attr.path
90    if not android_sdk_path:
91        android_sdk_path = repo_ctx.os.environ.get("ANDROID_HOME")
92    if not android_sdk_path:
93        # Create an empty repository that allows non-Android code to build.
94        repo_ctx.template("BUILD.bazel", _EMPTY_SDK_REPO_TEMPLATE)
95        return None
96
97    # Symlink the needed contents to this repository.
98    for dir_to_link in _DIRS_TO_LINK:
99        source = "%s/%s" % (android_sdk_path, dir_to_link)
100        dest = dir_to_link
101        repo_ctx.symlink(source, dest)
102
103    # Read list of supported SDK levels
104    api_levels = _read_api_levels(repo_ctx, android_sdk_path)
105    if len(api_levels) == 0:
106        fail("No Android SDK apis found in the Android SDK at %s. Please install APIs from the Android SDK Manager." % android_sdk_path)
107
108    # Determine default SDK level.
109    default_api_level = max(api_levels)
110    if repo_ctx.attr.api_level:
111        default_api_level = int(repo_ctx.attr.api_level)
112    if default_api_level not in api_levels:
113        fail("Android SDK api level %s was requested but it is not installed in the Android SDK at %s. The api levels found were %s. Please choose an available api level or install api level %s from the Android SDK Manager." % (
114            default_api_level,
115            android_sdk_path,
116            api_levels,
117            default_api_level,
118        ))
119
120    # Determine build_tools directory (and version)
121    build_tools = None
122    if repo_ctx.attr.build_tools_version:
123        build_tools = parse_android_revision(repo_ctx.attr.build_tools_version)
124    else:
125        build_tools = _newest_build_tools(repo_ctx, android_sdk_path)
126
127    # Check validity of build_tools
128    if not build_tools:
129        fail("Unable to determine build tools version")
130    if compare_android_revisions(build_tools, _MIN_BUILD_TOOLS_VERSION) != build_tools:
131        fail("Bazel requires Android build tools version %s or newer, %s was provided" % (
132            _MIN_BUILD_TOOLS_VERSION.dir,
133            build_tools.dir,
134        ))
135
136    # Determine system image dirs
137    system_images = _find_system_images(repo_ctx, android_sdk_path)
138
139    # Write the build file.
140    repo_ctx.symlink(Label(":helper.bzl"), "helper.bzl")
141    repo_ctx.template(
142        "BUILD.bazel",
143        _SDK_REPO_TEMPLATE,
144        substitutions = {
145            "__repository_name__": repo_ctx.name,
146            "__build_tools_version__": build_tools.version,
147            "__build_tools_directory__": build_tools.dir,
148            "__api_levels__": ",".join([str(level) for level in api_levels]),
149            "__default_api_level__": str(default_api_level),
150            "__system_image_dirs__": "\n".join(["'%s'," % d for d in system_images]),
151            # TODO(katre): implement these.
152            #"__exported_files__": "",
153        },
154    )
155
156    # repo is reproducible
157    return None
158
159_android_sdk_repository = repository_rule(
160    implementation = _android_sdk_repository_impl,
161    attrs = {
162        "api_level": attr.int(default = 0),
163        "build_tools_version": attr.string(),
164        "path": attr.string(),
165    },
166    environ = ["ANDROID_HOME"],
167    local = True,
168)
169
170def _bind(repo_name, bind_name, target):
171    native.bind(name = bind_name, actual = "@%s//%s" % (repo_name, target))
172
173def android_sdk_repository(
174        name,
175        path = "",
176        api_level = 0,
177        build_tools_version = ""):
178    """Create a repository with Android SDK bindings and toolchains.
179
180    The SDK will be located at the given path, or via the ANDROID_HOME
181    environment variable if the path attribute is unset.
182
183    Args:
184      name: The repository name.
185      api_level: The SDK API level to use.
186      build_tools_version: The build_tools in the SDK to use.
187      path: The path to the Android SDK.
188    """
189
190    _android_sdk_repository(
191        name = name,
192        path = path,
193        api_level = api_level,
194        build_tools_version = build_tools_version,
195    )
196
197    _bind(name, "android/sdk", ":sdk")
198    _bind(name, "android/d8_jar_import", ":d8_jar_import")
199    _bind(name, "android/dx_jar_import", ":dx_jar_import")
200    _bind(name, "android_sdk_for_testing", ":files")
201    _bind(name, "has_android_sdk", ":has_android_sdk")
202    native.register_toolchains("@%s//:all" % name)
203