xref: /aosp_15_r20/external/bazelbuild-rules_android/rules/android_sdk_repository/helper.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1# Copyright 2016 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"""Helpers for the build file used in android_sdk_repository."""
16
17load("@local_config_platform//:constraints.bzl", "HOST_CONSTRAINTS")
18load("@rules_java//java:defs.bzl", "java_binary", "java_import")
19
20def _bool_flag_impl(_unused_ctx):
21    pass
22
23_bool_flag = rule(
24    implementation = _bool_flag_impl,
25    build_setting = config.bool(flag = True),
26)
27
28def _int_flag_impl(ctx):
29    allowed_values = ctx.attr.values
30    value = ctx.build_setting_value
31    if len(allowed_values) != 0 and value not in ctx.attr.values:
32        fail(
33            "Error setting %s: invalid value '%d'. Allowed values are %s" % (
34                ctx.label,
35                value,
36                ",".join([str(val) for val in allowed_values]),
37            ),
38        )
39
40int_flag = rule(
41    implementation = _int_flag_impl,
42    build_setting = config.int(flag = True),
43    attrs = {
44        "values": attr.int_list(
45            doc = "The list of allowed values for this setting. An error is raised if any other value is given.",
46        ),
47    },
48    doc = "An int-typed build setting that can be set on the command line",
49)
50
51def _create_config_setting_rule():
52    """Create config_setting rule for windows.
53
54    These represent the matching --host_cpu values.
55    """
56    name = "windows"
57    if not native.existing_rule(name):
58        native.config_setting(
59            name = name,
60            values = {"host_cpu": "x64_" + name},
61        )
62
63    native.config_setting(
64        name = "d8_standalone_dexer",
65        values = {"define": "android_standalone_dexing_tool=d8_compat_dx"},
66    )
67
68    native.config_setting(
69        name = "dx_standalone_dexer",
70        values = {"define": "android_standalone_dexing_tool=dx_compat_dx"},
71    )
72
73    _bool_flag(
74        name = "allow_proguard",
75        build_setting_default = True,
76    )
77
78    native.config_setting(
79        name = "disallow_proguard",
80        flag_values = {":allow_proguard": "false"},
81    )
82
83def create_android_sdk_rules(
84        name,
85        build_tools_version,
86        build_tools_directory,
87        api_levels,
88        default_api_level):
89    """Generate android_sdk rules for the API levels in the Android SDK.
90
91    Args:
92      name: string, the name of the repository being generated.
93      build_tools_version: string, the version of Android's build tools to use.
94      build_tools_directory: string, the directory name of the build tools in
95          sdk's build-tools directory.
96      api_levels: list of ints, the API levels from which to get android.jar
97          et al. and create android_sdk rules.
98      default_api_level: int, the API level to alias the default sdk to if
99          --android_sdk is not specified on the command line.
100    """
101
102    _create_config_setting_rule()
103
104    int_flag(
105        name = "api_level",
106        build_setting_default = default_api_level,
107        values = api_levels,
108        visibility = ["//visibility:public"],
109    )
110
111    windows_only_files = [
112        "build-tools/%s/aapt.exe" % build_tools_directory,
113        "build-tools/%s/aidl.exe" % build_tools_directory,
114        "build-tools/%s/zipalign.exe" % build_tools_directory,
115        "platform-tools/adb.exe",
116    ] + native.glob(
117        ["build-tools/%s/aapt2.exe" % build_tools_directory],
118        allow_empty = True,
119    )
120
121    linux_only_files = [
122        "build-tools/%s/aapt" % build_tools_directory,
123        "build-tools/%s/aidl" % build_tools_directory,
124        "build-tools/%s/zipalign" % build_tools_directory,
125        "platform-tools/adb",
126    ] + native.glob(
127        ["extras", "build-tools/%s/aapt2" % build_tools_directory],
128        allow_empty = True,
129        exclude_directories = 0,
130    )
131
132    # This filegroup is used to pass the minimal contents of the SDK to the
133    # Android integration tests. Note that in order to work on Windows, we cannot
134    # include directories and must keep the size small.
135    native.filegroup(
136        name = "files",
137        srcs = [
138            "build-tools/%s/lib/apksigner.jar" % build_tools_directory,
139            "build-tools/%s/lib/d8.jar" % build_tools_directory,
140            "build-tools/%s/lib/dx.jar" % build_tools_directory,
141            "build-tools/%s/mainDexClasses.rules" % build_tools_directory,
142            ":build_tools_libs",
143        ] + [
144            "platforms/android-%d/%s" % (api_level, filename)
145            for api_level in api_levels
146            for filename in ["android.jar", "framework.aidl"]
147        ] + select({
148            ":windows": windows_only_files,
149            "//conditions:default": linux_only_files,
150        }),
151    )
152
153    for api_level in api_levels:
154        if api_level >= 23:
155            # Android 23 removed most of org.apache.http from android.jar and moved it
156            # to a separate jar.
157            java_import(
158                name = "org_apache_http_legacy-%d" % api_level,
159                jars = ["platforms/android-%d/optional/org.apache.http.legacy.jar" % api_level],
160            )
161
162        if api_level >= 28:
163            # Android 28 removed most of android.test from android.jar and moved it
164            # to separate jars.
165            java_import(
166                name = "legacy_test-%d" % api_level,
167                jars = [
168                    "platforms/android-%d/optional/android.test.base.jar" % api_level,
169                    "platforms/android-%d/optional/android.test.mock.jar" % api_level,
170                    "platforms/android-%d/optional/android.test.runner.jar" % api_level,
171                ],
172                neverlink = 1,
173            )
174
175        native.config_setting(
176            name = "api_%d_enabled" % api_level,
177            flag_values = {
178                ":api_level": str(api_level),
179            },
180        )
181
182        # TODO(katre): Use the Starlark android_sdk
183        native.android_sdk(
184            name = "sdk-%d" % api_level,
185            aapt = select({
186                ":windows": "build-tools/%s/aapt.exe" % build_tools_directory,
187                "//conditions:default": ":aapt_binary",
188            }),
189            aapt2 = select({
190                ":windows": "build-tools/%s/aapt2.exe" % build_tools_directory,
191                "//conditions:default": ":aapt2_binary",
192            }),
193            adb = select({
194                ":windows": "platform-tools/adb.exe",
195                "//conditions:default": "platform-tools/adb",
196            }),
197            aidl = select({
198                ":windows": "build-tools/%s/aidl.exe" % build_tools_directory,
199                "//conditions:default": ":aidl_binary",
200            }),
201            android_jar = "platforms/android-%d/android.jar" % api_level,
202            apksigner = ":apksigner",
203            build_tools_version = build_tools_version,
204            dx = select({
205                "d8_standalone_dexer": ":d8_compat_dx",
206                "dx_standalone_dexer": ":dx_binary",
207                "//conditions:default": ":d8_compat_dx",
208            }),
209            framework_aidl = "platforms/android-%d/framework.aidl" % api_level,
210            legacy_main_dex_list_generator = ":generate_main_dex_list",
211            main_dex_classes = "build-tools/%s/mainDexClasses.rules" % build_tools_directory,
212            proguard = select({
213                ":disallow_proguard": ":fail",
214                "//conditions:default": "@bazel_tools//tools/jdk:proguard",
215            }),
216            shrinked_android_jar = "platforms/android-%d/android.jar" % api_level,
217            # See https://github.com/bazelbuild/bazel/issues/8757
218            tags = ["__ANDROID_RULES_MIGRATION__"],
219            zipalign = select({
220                ":windows": "build-tools/%s/zipalign.exe" % build_tools_directory,
221                "//conditions:default": ":zipalign_binary",
222            }),
223        )
224
225        native.toolchain(
226            name = "sdk-%d-toolchain" % api_level,
227            exec_compatible_with = HOST_CONSTRAINTS,
228            target_compatible_with = [
229                "@platforms//os:android",
230            ],
231            toolchain = ":sdk-%d" % api_level,
232            toolchain_type = "@bazel_tools//tools/android:sdk_toolchain_type",
233            target_settings = [
234                ":api_%d_enabled" % api_level,
235            ],
236        )
237
238    create_dummy_sdk_toolchain()
239
240    native.alias(
241        name = "org_apache_http_legacy",
242        actual = ":org_apache_http_legacy-%d" % default_api_level,
243    )
244
245    sdk_alias_dict = {
246        "//conditions:default": "sdk-%d" % default_api_level,
247    }
248
249    for api_level in api_levels:
250        sdk_alias_dict[":api_%d_enabled" % api_level] = "sdk-%d" % api_level
251
252    native.alias(
253        name = "sdk",
254        actual = select(
255            sdk_alias_dict,
256            no_match_error = "Unknown Android SDK level, valid levels are %s" % ",".join([str(level) for level in api_levels]),
257        ),
258    )
259
260    java_binary(
261        name = "apksigner",
262        main_class = "com.android.apksigner.ApkSignerTool",
263        runtime_deps = ["build-tools/%s/lib/apksigner.jar" % build_tools_directory],
264    )
265
266    native.filegroup(
267        name = "build_tools_libs",
268        srcs = native.glob([
269            "build-tools/%s/lib/**" % build_tools_directory,
270            # Build tools version 24.0.0 added a lib64 folder.
271            "build-tools/%s/lib64/**" % build_tools_directory,
272        ], allow_empty = True),
273    )
274
275    for tool in ["aapt", "aapt2", "aidl", "zipalign"]:
276        native.genrule(
277            name = tool + "_runner",
278            srcs = [],
279            outs = [tool + "_runner.sh"],
280            cmd = "\n".join([
281                "cat > $@ << 'EOF'",
282                "#!/bin/bash",
283                "set -eu",
284                # The tools under build-tools/VERSION require the libraries under
285                # build-tools/VERSION/lib, so we can't simply depend on them as a
286                # file like we do with aapt.
287                # On Windows however we can use these binaries directly because
288                # there's no runfiles support so Bazel just creates a junction to
289                # {SDK}/build-tools.
290                "SDK=$${0}.runfiles/%s" % name,
291                # If $${SDK} is not a directory, it means that this tool is running
292                # from a runfiles directory, in the case of
293                # android_instrumentation_test. Hence, use the androidsdk
294                # that's already present in the runfiles of the current context.
295                "if [[ ! -d $${SDK} ]] ; then",
296                "  SDK=$$(pwd)/../%s" % name,
297                "fi",
298                "tool=$${SDK}/build-tools/%s/%s" % (build_tools_directory, tool),
299                "exec env LD_LIBRARY_PATH=$${SDK}/build-tools/%s/lib64 $$tool $$*" % build_tools_directory,
300                "EOF\n",
301            ]),
302        )
303
304        native.sh_binary(
305            name = tool + "_binary",
306            srcs = [tool + "_runner.sh"],
307            data = [
308                ":build_tools_libs",
309                "build-tools/%s/%s" % (build_tools_directory, tool),
310            ],
311        )
312
313    native.sh_binary(
314        name = "fail",
315        srcs = select({
316            ":windows": [":generate_fail_cmd"],
317            "//conditions:default": [":generate_fail_sh"],
318        }),
319    )
320
321    native.genrule(
322        name = "generate_fail_sh",
323        outs = ["fail.sh"],
324        cmd = "echo -e '#!/bin/bash\\nexit 1' >> $@; chmod +x $@",
325        executable = 1,
326    )
327
328    native.genrule(
329        name = "generate_fail_cmd",
330        outs = ["fail.cmd"],
331        cmd = "echo @exit /b 1 > $@",
332        executable = 1,
333    )
334
335    native.genrule(
336        name = "main_dex_list_creator_source",
337        srcs = [],
338        outs = ["main_dex_list_creator.sh"],
339        cmd = "\n".join([
340            "cat > $@ <<'EOF'",
341            "#!/bin/bash",
342            "",
343            "MAIN_DEX_LIST=$$1",
344            "STRIPPED_JAR=$$2",
345            "JAR=$$3",
346            "" +
347            "JAVA_BINARY=$$0.runfiles/%s/main_dex_list_creator_java" % name,
348            "$$JAVA_BINARY $$STRIPPED_JAR $$JAR > $$MAIN_DEX_LIST",
349            "exit $$?",
350            "",
351            "EOF\n",
352        ]),
353    )
354
355    native.sh_binary(
356        name = "main_dex_list_creator",
357        srcs = ["main_dex_list_creator.sh"],
358        data = [":main_dex_list_creator_java"],
359    )
360    java_binary(
361        name = "main_dex_list_creator_java",
362        main_class = "com.android.multidex.ClassReferenceListBuilder",
363        runtime_deps = [":dx_jar_import"],
364    )
365    java_binary(
366        name = "dx_binary",
367        main_class = "com.android.dx.command.Main",
368        runtime_deps = [":dx_jar_import"],
369    )
370    java_import(
371        name = "dx_jar_import",
372        jars = ["build-tools/%s/lib/dx.jar" % build_tools_directory],
373    )
374    java_binary(
375        name = "generate_main_dex_list",
376        jvm_flags = [
377            "-XX:+TieredCompilation",
378            "-XX:TieredStopAtLevel=1",
379            # Consistent with what we use for desugar.
380            "-Xms8g",
381            "-Xmx8g",
382        ],
383        main_class = "com.android.tools.r8.GenerateMainDexList",
384        runtime_deps = ["@bazel_tools//src/tools/android/java/com/google/devtools/build/android/r8"],
385    )
386    java_binary(
387        name = "d8_compat_dx",
388        main_class = "com.google.devtools.build.android.r8.CompatDx",
389        runtime_deps = [
390            "@bazel_tools//src/tools/android/java/com/google/devtools/build/android/r8",
391        ],
392    )
393    native.alias(
394        name = "d8_jar_import",
395        actual = "@android_gmaven_r8//jar",
396    )
397
398TAGDIR_TO_TAG_MAP = {
399    "google_apis_playstore": "playstore",
400    "google_apis": "google",
401    "default": "android",
402    "android-tv": "tv",
403    "android-wear": "wear",
404}
405
406ARCHDIR_TO_ARCH_MAP = {
407    "x86": "x86",
408    "armeabi-v7a": "arm",
409}
410
411# buildifier: disable=unnamed-macro
412def create_system_images_filegroups(system_image_dirs):
413    """Generate filegroups for the system images in the Android SDK.
414
415    Args:
416      system_image_dirs: list of strings, the directories containing system image
417          files to be used to create android_device rules.
418    """
419
420    # These images will need to be updated as Android releases new system images.
421    # We are intentionally not adding future releases because there is no
422    # guarantee that they will work out of the box. Supported system images should
423    # be added here once they have been confirmed to work with the Bazel Android
424    # testing infrastructure.
425    system_images = [
426        (tag, str(api), arch)
427        for tag in ["android", "google"]
428        for api in [10] + list(range(15, 20)) + list(range(21, 30))
429        for arch in ("x86", "arm")
430    ] + [
431        ("playstore", str(api), "x86")
432        for api in list(range(24, 30))
433    ]
434    tv_images = [
435        ("tv", str(api), "x86")
436        for api in range(21, 30)
437    ] + [
438        ("tv", "21", "arm"),
439        ("tv", "23", "arm"),
440    ]
441    wear_images = [
442        ("wear", str(api), "x86")
443        for api in [23, 25, 26, 28]
444    ] + [
445        ("wear", str(api), "arm")
446        for api in [23, 25]
447    ]
448    supported_system_images = system_images + tv_images + wear_images
449
450    installed_system_images_dirs = {}
451    for system_image_dir in system_image_dirs:
452        apidir, tagdir, archdir = system_image_dir.split("/")[1:]
453        if "-" not in apidir:
454            continue
455        api = apidir.split("-")[1]  # "android-24" --> "24", "android-O" --> "O"
456        if tagdir not in TAGDIR_TO_TAG_MAP:
457            continue
458        tag = TAGDIR_TO_TAG_MAP[tagdir]
459        if archdir not in ARCHDIR_TO_ARCH_MAP:
460            continue
461        arch = ARCHDIR_TO_ARCH_MAP[archdir]
462        if (tag, api, arch) in supported_system_images:
463            name = "emulator_images_%s_%s_%s" % (tag, api, arch)
464            installed_system_images_dirs[name] = system_image_dir
465        else:
466            # TODO(bazel-team): If the user has an unsupported system image installed,
467            # should we print a warning? This includes all 64-bit system-images.
468            pass
469
470    for (tag, api, arch) in supported_system_images:
471        name = "emulator_images_%s_%s_%s" % (tag, api, arch)
472        if name in installed_system_images_dirs:
473            system_image_dir = installed_system_images_dirs[name]
474
475            # For supported system images that exist in /sdk/system-images/, we
476            # create a filegroup with their contents.
477            native.filegroup(
478                name = name,
479                srcs = native.glob([
480                    "%s/**" % system_image_dir,
481                ]),
482            )
483            native.filegroup(
484                name = "%s_qemu2_extra" % name,
485                srcs = native.glob(["%s/kernel-ranchu" % system_image_dir], allow_empty = True),
486            )
487        else:
488            # For supported system images that are not installed in the SDK, we
489            # create a "poison pill" genrule to display a helpful error message to
490            # a user who attempts to run a test against an android_device that
491            # they don't have the system image for installed.
492            native.genrule(
493                name = name,
494                outs = [
495                    # Necessary so that the build doesn't fail in analysis because
496                    # android_device expects a file named source.properties.
497                    "poison_pill_for_%s/source.properties" % name,
498                ],
499                cmd = """echo \
500          This rule requires that the Android SDK used by Bazel has the \
501          following system image installed: %s. Please install this system \
502          image through the Android SDK Manager and try again. ; \
503          exit 1
504          """ % name,
505            )
506            native.filegroup(
507                name = "%s_qemu2_extra" % name,
508                srcs = [],
509            )  # buildifier: disable=unnamed-macro
510
511# This is a dummy sdk toolchain that matches any platform. It will
512# fail if actually resolved to and used.
513# buildifier: disable=unnamed-macro
514def create_dummy_sdk_toolchain():
515    "Create a dummy SDK for fallback builds"
516
517    native.toolchain(
518        name = "sdk-dummy-toolchain",
519        toolchain = ":sdk-dummy",
520        toolchain_type = "@bazel_tools//tools/android:sdk_toolchain_type",
521    )
522
523    native.filegroup(name = "jar-filegroup", srcs = ["dummy.jar"])
524
525    native.genrule(
526        name = "genrule",
527        srcs = [],
528        outs = ["empty.sh"],
529        cmd = "echo '' >> \"$@\"",
530        executable = 1,
531    )
532
533    native.sh_binary(name = "empty-binary", srcs = [":genrule"])
534
535    native.android_sdk(
536        name = "sdk-dummy",
537        aapt = ":empty-binary",
538        adb = ":empty-binary",
539        aidl = ":empty-binary",
540        android_jar = ":jar-filegroup",
541        apksigner = ":empty-binary",
542        dx = ":empty-binary",
543        framework_aidl = "dummy.jar",
544        main_dex_classes = "dummy.jar",
545        main_dex_list_creator = ":empty-binary",
546        proguard = ":empty-binary",
547        shrinked_android_jar = "dummy.jar",
548        tags = ["__ANDROID_RULES_MIGRATION__"],
549        zipalign = ":empty-binary",
550    )
551