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"""Implementation."""
16
17load(
18    "@rules_android//rules:acls.bzl",
19    _acls = "acls",
20)
21load(
22    "@rules_android//rules:common.bzl",
23    _common = "common",
24)
25load("@rules_android//rules:intellij.bzl", "intellij")
26load(
27    "@rules_android//rules:java.bzl",
28    _java = "java",
29)
30load("@rules_android//rules:providers.bzl", "AndroidLintRulesInfo")
31load(
32    "@rules_android//rules:resources.bzl",
33    _resources = "resources",
34)
35load(
36    "@rules_android//rules:utils.bzl",
37    _get_android_toolchain = "get_android_toolchain",
38    _utils = "utils",
39)
40
41RULE_PREFIX = "_aar"
42ANDROID_MANIFEST = "AndroidManifest.xml"
43LINT_JAR = "lint.jar"
44_UNEXPECTED_LINT_JAR_ERROR = (
45    "In target %s, has_lint_jar attribute is required when the aar contains " +
46    "a lint.jar file."
47)
48
49def _create_aar_artifact(ctx, name):
50    return ctx.actions.declare_file("%s/%s/%s" % (RULE_PREFIX, ctx.label.name, name))
51
52def _create_aar_tree_artifact(ctx, name):
53    return ctx.actions.declare_directory("%s/unzipped/%s/%s" % (RULE_PREFIX, name, ctx.label.name))
54
55# Create an action to extract a file (specified by the parameter filename) from an AAR file.
56def _extract_single_file(
57        ctx,
58        out_file,
59        aar,
60        filename,
61        unzip_tool):
62    args = ctx.actions.args()
63    args.add(aar)
64    args.add(filename)
65    args.add("-d", out_file.dirname)
66
67    ctx.actions.run(
68        executable = unzip_tool,
69        arguments = [args],
70        inputs = [aar],
71        outputs = [out_file],
72        mnemonic = "AarFileExtractor",
73        progress_message = "Extracting %s from %s" % (filename, aar.basename),
74    )
75
76def _extract_resources(
77        ctx,
78        out_resources_dir,
79        out_assets_dir,
80        aar,
81        aar_resources_extractor_tool):
82    args = ctx.actions.args()
83    args.add("--input_aar", aar)
84    args.add("--output_res_dir", out_resources_dir.path)
85    args.add("--output_assets_dir", out_assets_dir.path)
86    ctx.actions.run(
87        executable = aar_resources_extractor_tool,
88        arguments = [args],
89        inputs = [aar],
90        outputs = [out_resources_dir, out_assets_dir],
91        mnemonic = "AarResourcesExtractor",
92        progress_message = "Extracting resources and assets from %s" % aar.basename,
93    )
94
95def _extract_native_libs(
96        ctx,
97        output_zip,
98        aar,
99        android_cpu,
100        aar_native_libs_zip_creator_tool):
101    args = ctx.actions.args()
102    args.add("--input_aar", aar)
103    args.add("--cpu", android_cpu)
104    args.add("--output_zip", output_zip)
105    ctx.actions.run(
106        executable = aar_native_libs_zip_creator_tool,
107        arguments = [args],
108        inputs = [aar],
109        outputs = [output_zip],
110        mnemonic = "AarNativeLibsFilter",
111        progress_message = "Filtering AAR native libs by architecture",
112    )
113
114def _process_resources(
115        ctx,
116        aar,
117        manifest,
118        deps,
119        aar_resources_extractor_tool,
120        unzip_tool):
121    # Extract resources and assets, if they exist.
122    resources = _create_aar_tree_artifact(ctx, "resources")
123    assets = _create_aar_tree_artifact(ctx, "assets")
124    _extract_resources(
125        ctx,
126        resources,
127        assets,
128        aar,
129        aar_resources_extractor_tool,
130    )
131
132    resources_ctx = _resources.process_starlark(
133        ctx,
134        manifest = manifest,
135        assets = [assets],
136        assets_dir = assets.path,
137        resource_files = [resources],
138        stamp_manifest = False,
139        deps = ctx.attr.deps,
140        exports = ctx.attr.exports,
141        exports_manifest = getattr(ctx.attr, "exports_manifest", True),
142
143        # Tool and Processing related inputs
144        aapt = _get_android_toolchain(ctx).aapt2.files_to_run,
145        android_jar = ctx.attr._android_sdk[AndroidSdkInfo].android_jar,
146        android_kit = _get_android_toolchain(ctx).android_kit.files_to_run,
147        busybox = _get_android_toolchain(ctx).android_resources_busybox.files_to_run,
148        java_toolchain = _common.get_java_toolchain(ctx),
149        host_javabase = _common.get_host_javabase(ctx),
150        instrument_xslt = _utils.only(_get_android_toolchain(ctx).add_g3itr_xslt.files.to_list()),
151        xsltproc = _get_android_toolchain(ctx).xsltproc_tool.files_to_run,
152    )
153
154    # TODO: replace android_data
155    # data_ctx = android_data.make_context(ctx.actions, ctx.attr)
156    # resource_apk = android_data.process_aar_import_data(
157    #     data_ctx,
158    #     resources,
159    #     assets,
160    #     manifest,
161    #     deps = deps,
162    # )
163    # resources_ctx["validation_results"].append(
164    #     _utils.only(resource_apk[AndroidResourcesInfo].direct_android_resources.to_list()).java_class_jar,
165    # )
166    # resources_ctx["providers"].append(resource_apk[AndroidResourcesInfo])
167    # resources_ctx["providers"].append(resource_apk[AndroidAssetsInfo])
168
169    if not _acls.in_aar_propagate_resources(str(ctx.label)):
170        resources_ctx["providers"] = []
171
172    return struct(**resources_ctx)
173
174def _extract_jars(
175        ctx,
176        out_jars_tree_artifact,
177        out_jars_params_file,
178        aar,
179        aar_embedded_jars_extractor_tool):
180    args = ctx.actions.args()
181    args.add("--input_aar", aar)
182    args.add("--output_dir", out_jars_tree_artifact.path)
183    args.add("--output_singlejar_param_file", out_jars_params_file)
184    ctx.actions.run(
185        executable = aar_embedded_jars_extractor_tool,
186        arguments = [args],
187        inputs = [aar],
188        outputs = [out_jars_tree_artifact, out_jars_params_file],
189        mnemonic = "AarEmbeddedJarsExtractor",
190        progress_message = "Extracting classes.jar and libs/*.jar from %s" % aar.basename,
191    )
192
193def _merge_jars(
194        ctx,
195        out_jar,
196        jars_tree_artifact,
197        jars_param_file,
198        single_jar_tool):
199    args = ctx.actions.args()
200    args.add("--output", out_jar)
201    args.add("--dont_change_compression")
202    args.add("--normalize")
203    args.add("@" + jars_param_file.path)
204    ctx.actions.run(
205        executable = single_jar_tool,
206        arguments = [args],
207        inputs = [jars_tree_artifact, jars_param_file],
208        outputs = [out_jar],
209        mnemonic = "AarJarsMerger",
210        progress_message = "Merging AAR embedded jars",
211    )
212
213def _extract_and_merge_jars(
214        ctx,
215        out_jar,
216        aar,
217        aar_embedded_jars_extractor_tool,
218        single_jar_tool):
219    """Extracts all the Jars within the AAR and produces a single jar.
220
221    An AAR may have multiple Jar files embedded within it. This method
222    extracts and merges all Jars.
223    """
224    jars_tree_artifact = _create_aar_tree_artifact(ctx, "jars")
225    jars_params_file = _create_aar_artifact(ctx, "jar_merging_params")
226    _extract_jars(
227        ctx,
228        jars_tree_artifact,
229        jars_params_file,
230        aar,
231        aar_embedded_jars_extractor_tool,
232    )
233    _merge_jars(
234        ctx,
235        out_jar,
236        jars_tree_artifact,
237        jars_params_file,
238        single_jar_tool,
239    )
240
241def _create_import_deps_check(
242        ctx,
243        jars_to_check,
244        declared_deps,
245        transitive_deps,
246        bootclasspath,
247        jdeps_output,
248        import_deps_checker_tool,
249        host_javabase):
250    args = ctx.actions.args()
251    args.add_all(jars_to_check, before_each = "--input")
252    args.add_all(declared_deps, before_each = "--directdep")
253    args.add_all(transitive_deps, before_each = "--classpath_entry")
254    args.add_all(bootclasspath, before_each = "--bootclasspath_entry")
255    args.add("--checking_mode=error")
256    args.add("--jdeps_output", jdeps_output)
257    args.add("--rule_label", ctx.label)
258
259    _java.run(
260        ctx = ctx,
261        host_javabase = host_javabase,
262        executable = import_deps_checker_tool,
263        arguments = [args],
264        inputs = depset(
265            jars_to_check,
266            transitive = [
267                declared_deps,
268                transitive_deps,
269                bootclasspath,
270            ],
271        ),
272        outputs = [jdeps_output],
273        mnemonic = "ImportDepsChecker",
274        progress_message = "Checking the completeness of the deps for %s" % jars_to_check,
275    )
276
277def _process_jars(
278        ctx,
279        out_jar,
280        aar,
281        source_jar,
282        r_java,
283        deps,
284        exports,
285        enable_desugar_java8,
286        enable_imports_deps_check,
287        bootclasspath,
288        desugar_java8_extra_bootclasspath,
289        aar_embedded_jars_extractor_tool,
290        import_deps_checker_tool,
291        single_jar_tool,
292        java_toolchain,
293        host_javabase):
294    providers = []
295    validation_results = []
296    r_java_info = [r_java] if r_java else []
297
298    # An aar may have multple Jar files, extract and merge into a single jar.
299    _extract_and_merge_jars(
300        ctx,
301        out_jar,
302        aar,
303        aar_embedded_jars_extractor_tool,
304        single_jar_tool,
305    )
306
307    java_infos = deps + exports
308
309    if enable_desugar_java8:
310        bootclasspath = depset(transitive = [
311            desugar_java8_extra_bootclasspath,
312            bootclasspath,
313        ])
314
315    merged_java_info = java_common.merge(java_infos + r_java_info)
316    jdeps_artifact = _create_aar_artifact(ctx, "jdeps.proto")
317    _create_import_deps_check(
318        ctx,
319        [out_jar],
320        merged_java_info.compile_jars,
321        merged_java_info.transitive_compile_time_jars,
322        bootclasspath,
323        jdeps_artifact,
324        import_deps_checker_tool,
325        host_javabase,
326    )
327    if enable_imports_deps_check:
328        validation_results.append(jdeps_artifact)
329
330    java_info = JavaInfo(
331        out_jar,
332        compile_jar = java_common.stamp_jar(
333            actions = ctx.actions,
334            jar = out_jar,
335            target_label = ctx.label,
336            java_toolchain = java_toolchain,
337        ),
338        source_jar = source_jar,
339        neverlink = False,
340        deps = r_java_info + java_infos,  # TODO(djwhang): Exports are not deps.
341        exports =
342            (r_java_info if _acls.in_aar_import_exports_r_java(str(ctx.label)) else []) +
343            java_infos,  # TODO(djwhang): Deps are not exports.
344        # TODO(djwhang): AarImportTest is not expecting jdeps, enable or remove it completely
345        # jdeps = jdeps_artifact,
346    )
347    providers.append(java_info)
348
349    return struct(
350        java_info = java_info,
351        providers = providers,
352        validation_results = validation_results,
353    )
354
355def _validate_rule(
356        ctx,
357        aar,
358        manifest,
359        checks):
360    package = _java.resolve_package_from_label(ctx.label, ctx.attr.package)
361    validation_output = ctx.actions.declare_file("%s_validation_output" % ctx.label.name)
362
363    args = ctx.actions.args()
364    args.add("-aar", aar)
365    inputs = [aar]
366    args.add("-label", str(ctx.label))
367    if _acls.in_aar_import_pkg_check(str(ctx.label)):
368        args.add("-pkg", package)
369        args.add("-manifest", manifest)
370        inputs.append(manifest)
371    if ctx.attr.has_lint_jar:
372        args.add("-has_lint_jar")
373    args.add("-output", validation_output)
374
375    ctx.actions.run(
376        executable = checks,
377        arguments = [args],
378        inputs = inputs,
379        outputs = [validation_output],
380        mnemonic = "ValidateAAR",
381        progress_message = "Validating aar_import %s" % str(ctx.label),
382    )
383    return validation_output
384
385def _process_lint_rules(
386        ctx,
387        aar,
388        unzip_tool):
389    providers = []
390
391    if ctx.attr.has_lint_jar:
392        lint_jar = _create_aar_artifact(ctx, LINT_JAR)
393        _extract_single_file(
394            ctx,
395            lint_jar,
396            aar,
397            LINT_JAR,
398            unzip_tool,
399        )
400        providers.append(AndroidLintRulesInfo(
401            lint_jar = lint_jar,
402        ))
403
404    providers.extend(_utils.collect_providers(
405        AndroidLintRulesInfo,
406        ctx.attr.exports,
407    ))
408    return providers
409
410def impl(ctx):
411    """The rule implementation.
412
413    Args:
414      ctx: The context.
415
416    Returns:
417      A list of providers.
418    """
419    providers = []
420    validation_outputs = []
421
422    aar = _utils.only(ctx.files.aar)
423    unzip_tool = _get_android_toolchain(ctx).unzip_tool.files_to_run
424
425    # Extract the AndroidManifest.xml from the AAR.
426    android_manifest = _create_aar_artifact(ctx, ANDROID_MANIFEST)
427    _extract_single_file(
428        ctx,
429        android_manifest,
430        aar,
431        ANDROID_MANIFEST,
432        unzip_tool,
433    )
434
435    resources_ctx = _process_resources(
436        ctx,
437        aar = aar,
438        manifest = android_manifest,
439        deps = ctx.attr.deps,
440        aar_resources_extractor_tool =
441            _get_android_toolchain(ctx).aar_resources_extractor.files_to_run,
442        unzip_tool = unzip_tool,
443    )
444    providers.extend(resources_ctx.providers)
445
446    merged_jar = _create_aar_artifact(ctx, "classes_and_libs_merged.jar")
447    jvm_ctx = _process_jars(
448        ctx,
449        out_jar = merged_jar,
450        aar = aar,
451        source_jar = ctx.file.srcjar,
452        deps = _utils.collect_providers(JavaInfo, ctx.attr.deps),
453        r_java = resources_ctx.r_java,
454        exports = _utils.collect_providers(JavaInfo, ctx.attr.exports),
455        enable_desugar_java8 = ctx.fragments.android.desugar_java8,
456        enable_imports_deps_check =
457            _acls.in_aar_import_deps_checker(str(ctx.label)),
458        aar_embedded_jars_extractor_tool =
459            _get_android_toolchain(ctx).aar_embedded_jars_extractor.files_to_run,
460        bootclasspath =
461            ctx.attr._java_toolchain[java_common.JavaToolchainInfo].bootclasspath,
462        desugar_java8_extra_bootclasspath =
463            _get_android_toolchain(ctx).desugar_java8_extra_bootclasspath.files,
464        import_deps_checker_tool =
465            _get_android_toolchain(ctx).import_deps_checker.files_to_run,
466        single_jar_tool =
467            ctx.attr._java_toolchain[java_common.JavaToolchainInfo].single_jar,
468        java_toolchain =
469            ctx.attr._java_toolchain[java_common.JavaToolchainInfo],
470        host_javabase = ctx.attr._host_javabase,
471    )
472    providers.extend(jvm_ctx.providers)
473    validation_outputs.extend(jvm_ctx.validation_results)
474
475    native_libs = _create_aar_artifact(ctx, "native_libs.zip")
476    _extract_native_libs(
477        ctx,
478        native_libs,
479        aar = aar,
480        android_cpu = ctx.fragments.android.android_cpu,
481        aar_native_libs_zip_creator_tool =
482            _get_android_toolchain(ctx).aar_native_libs_zip_creator.files_to_run,
483    )
484    native_libs_infos = _utils.collect_providers(
485        AndroidNativeLibsInfo,
486        ctx.attr.deps,
487        ctx.attr.exports,
488    )
489    providers.append(
490        AndroidNativeLibsInfo(
491            depset(
492                [native_libs],
493                transitive = [info.native_libs for info in native_libs_infos],
494            ),
495        ),
496    )
497
498    lint_providers = _process_lint_rules(
499        ctx,
500        aar = aar,
501        unzip_tool = unzip_tool,
502    )
503    providers.extend(lint_providers)
504
505    validation_outputs.append(_validate_rule(
506        ctx,
507        aar = aar,
508        manifest = android_manifest,
509        checks = _get_android_toolchain(ctx).aar_import_checks.files_to_run,
510    ))
511
512    providers.append(
513        intellij.make_android_ide_info(
514            ctx,
515            java_package = _java.resolve_package_from_label(ctx.label, ctx.attr.package),
516            manifest = resources_ctx.merged_manifest,
517            defines_resources = resources_ctx.defines_resources,
518            merged_manifest = resources_ctx.merged_manifest,
519            resources_apk = resources_ctx.resources_apk,
520            r_jar = _utils.only(resources_ctx.r_java.outputs.jars) if resources_ctx.r_java else None,
521            java_info = jvm_ctx.java_info,
522            signed_apk = None,  # signed_apk, always empty for aar_import
523            apks_under_test = [],  # apks_under_test, always empty for aar_import
524            native_libs = dict(),  # nativelibs, always empty for aar_import
525            idlclass = _get_android_toolchain(ctx).idlclass.files_to_run,
526            host_javabase = _common.get_host_javabase(ctx),
527        ),
528    )
529
530    providers.append(OutputGroupInfo(_validation = depset(validation_outputs)))
531
532    # There isn't really any use case for building an aar_import target on its own, so the files to
533    # build could be empty. The R class JAR and merged JARs are added here as a sanity check for
534    # Bazel developers so that `bazel build java/com/my_aar_import` will fail if the resource
535    # processing or JAR merging steps fail.
536    files_to_build = []
537    files_to_build.extend(resources_ctx.validation_results)  # TODO(djwhang): This should be validation.
538    files_to_build.append(merged_jar)
539
540    providers.append(
541        DefaultInfo(
542            files = depset(files_to_build),
543            runfiles = ctx.runfiles(),
544        ),
545    )
546
547    return providers
548