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