1# Copyright (C) 2018 The Dagger Authors. 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"""Macros to simplify generating maven files. 16""" 17 18load("@google_bazel_common//tools/jarjar:jarjar.bzl", "jarjar_library") 19load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") 20load("@google_bazel_common//tools/maven:pom_file.bzl", default_pom_file = "pom_file") 21load(":maven_info.bzl", "MavenInfo", "collect_maven_info") 22 23SHADED_MAVEN_DEPS = [ 24 "com.google.auto:auto-common", 25 "org.jetbrains.kotlinx:kotlinx-metadata-jvm", 26] 27 28def pom_file(name, targets, artifact_name, artifact_id, packaging = None, **kwargs): 29 default_pom_file( 30 name = name, 31 targets = targets, 32 preferred_group_ids = [ 33 "com.google.dagger", 34 "com.google", 35 ], 36 template_file = "//tools:pom-template.xml", 37 substitutions = { 38 "{artifact_name}": artifact_name, 39 "{artifact_id}": artifact_id, 40 "{packaging}": packaging or "jar", 41 }, 42 # NOTE: The shaded maven dependencies are excluded from every Dagger pom file. 43 # Thus, if a Dagger artifact needs the dependencies it must jarjar the dependency 44 # into the artifact itself using the gen_maven_artifact.shaded_deps or get it from 45 # a transitive Dagger artifact as a dependency. In addition, the artifact must add 46 # the shade rules in the deploy scripts, e.g. deploy-dagger.sh. 47 excluded_artifacts = SHADED_MAVEN_DEPS, 48 **kwargs 49 ) 50 51def gen_maven_artifact( 52 name, 53 artifact_name, 54 artifact_coordinates, 55 artifact_target, 56 artifact_target_libs = None, 57 artifact_target_maven_deps = None, 58 artifact_target_maven_deps_banned = None, 59 testonly = 0, 60 pom_name = "pom", 61 packaging = None, 62 javadoc_srcs = None, 63 javadoc_root_packages = None, 64 javadoc_exclude_packages = None, 65 javadoc_android_api_level = None, 66 shaded_deps = None, 67 manifest = None, 68 lint_deps = None, 69 proguard_and_r8_specs = None, 70 r8_specs = None, 71 proguard_specs = None): 72 _gen_maven_artifact( 73 name, 74 artifact_name, 75 artifact_coordinates, 76 artifact_target, 77 artifact_target_libs, 78 artifact_target_maven_deps, 79 artifact_target_maven_deps_banned, 80 testonly, 81 pom_name, 82 packaging, 83 javadoc_srcs, 84 javadoc_root_packages, 85 javadoc_exclude_packages, 86 javadoc_android_api_level, 87 shaded_deps, 88 manifest, 89 lint_deps, 90 proguard_and_r8_specs, 91 r8_specs, 92 proguard_specs 93 ) 94 95def _gen_maven_artifact( 96 name, 97 artifact_name, 98 artifact_coordinates, 99 artifact_target, 100 artifact_target_libs, 101 artifact_target_maven_deps, 102 artifact_target_maven_deps_banned, 103 testonly, 104 pom_name, 105 packaging, 106 javadoc_srcs, 107 javadoc_root_packages, 108 javadoc_exclude_packages, 109 javadoc_android_api_level, 110 shaded_deps, 111 manifest, 112 lint_deps, 113 proguard_and_r8_specs, 114 r8_specs, 115 proguard_specs): 116 """Generates the files required for a maven artifact. 117 118 This macro generates the following targets: 119 * ":pom": The pom file for the given target and deps 120 * ":<NAME>": The artifact file for the given target and deps 121 * ":<NAME>-src": The sources jar file for the given target and deps 122 * ":<NAME>-javadoc": The javadocs jar file for the given target and deps 123 124 This macro also validates a few things. First, it validates that the 125 given "target" is a maven artifact (i.e. the "tags" attribute contains 126 "maven_coordinates=..."). Second, it calculates the list of transitive 127 dependencies of the target that are not owned by another maven artifact, 128 and validates that the given "deps" matches exactly. 129 130 Args: 131 name: The name associated with the various output targets. 132 artifact_target: The target containing the maven_coordinates. 133 artifact_name: The name of the maven artifact. 134 artifact_coordinates: The coordinates of the maven artifact in the 135 form: "<group_id>:<artifact_id>:<version>" 136 artifact_target_libs: The set of transitive libraries of the target. 137 artifact_target_maven_deps: The required maven deps of the target. 138 artifact_target_maven_deps_banned: The banned maven deps of the target. 139 testonly: True if the jar should be testonly. 140 packaging: The packaging of the maven artifact. E.g. "aar" 141 pom_name: The name of the pom file (or "pom" if absent). 142 javadoc_srcs: The srcs for the javadocs. 143 javadoc_root_packages: The root packages for the javadocs. 144 javadoc_exclude_packages: The packages to exclude from the javadocs. 145 javadoc_android_api_level: The android api level for the javadocs. 146 shaded_deps: The shaded deps for the jarjar. 147 manifest: The AndroidManifest.xml to bundle in when packaing an 'aar'. 148 lint_deps: The lint targets to be bundled in when packaging an 'aar'. 149 proguard_and_r8_specs: The proguard spec files to be bundled in when 150 packaging an 'aar', which will be applied in 151 both r8 and proguard. 152 r8_specs: The proguard spec files to be used only for r8 when packaging an 'jar'. 153 proguard_specs: The proguard spec files to be used only for proguard not r8 when 154 packaging an 'jar'. 155 """ 156 157 _validate_maven_deps( 158 name = name + "-validation", 159 testonly = 1, 160 target = artifact_target, 161 expected_artifact = artifact_coordinates, 162 expected_libs = artifact_target_libs, 163 expected_maven_deps = artifact_target_maven_deps, 164 banned_maven_deps = artifact_target_maven_deps_banned, 165 ) 166 167 shaded_deps = shaded_deps or [] 168 artifact_targets = [artifact_target] + (artifact_target_libs or []) 169 lint_deps = lint_deps or [] 170 171 # META-INF resources files that can be combined by appending lines. 172 merge_meta_inf_files = [ 173 "gradle/incremental.annotation.processors", 174 ] 175 176 artifact_id = artifact_coordinates.split(":")[1] 177 pom_file( 178 name = pom_name, 179 testonly = testonly, 180 artifact_id = artifact_id, 181 artifact_name = artifact_name, 182 packaging = packaging, 183 targets = artifact_targets, 184 ) 185 186 if (packaging == "aar"): 187 jarjar_library( 188 name = name + "-classes", 189 testonly = testonly, 190 jars = artifact_targets + shaded_deps, 191 merge_meta_inf_files = merge_meta_inf_files, 192 ) 193 if lint_deps: 194 # jarjar all lint artifacts since an aar only contains a single lint.jar. 195 jarjar_library( 196 name = name + "-lint", 197 jars = lint_deps, 198 ) 199 lint_jar_name = name + "-lint.jar" 200 else: 201 lint_jar_name = None 202 203 if proguard_and_r8_specs: 204 # Concatenate all proguard rules since an aar only contains a single proguard.txt 205 native.genrule( 206 name = name + "-proguard", 207 srcs = proguard_and_r8_specs, 208 outs = [name + "-proguard.txt"], 209 cmd = "cat $(SRCS) > $@", 210 ) 211 proguard_file = name + "-proguard.txt" 212 else: 213 proguard_file = None 214 215 _package_android_library( 216 name = name + "-android-lib", 217 manifest = manifest, 218 classesJar = name + "-classes.jar", 219 lintJar = lint_jar_name, 220 proguardSpec = proguard_file, 221 ) 222 223 # Copy intermediate outputs to final one. 224 native.genrule( 225 name = name, 226 srcs = [name + "-android-lib"], 227 outs = [name + ".aar"], 228 cmd = "cp $< $@", 229 ) 230 else: 231 # (TODO/322873492) add support for passing in general proguard rule. 232 if r8_specs: 233 # Concatenate all r8 rules. 234 native.genrule( 235 name = name + "-r8", 236 srcs = r8_specs, 237 outs = [name + "-r8.txt"], 238 cmd = "cat $(SRCS) > $@", 239 ) 240 r8_file = name + "-r8.txt" 241 else: 242 r8_file = None 243 244 if proguard_specs: 245 # Concatenate all proguard only rules. 246 native.genrule( 247 name = name + "-proguard-only", 248 srcs = proguard_specs, 249 outs = [name + "-proguard-only.txt"], 250 cmd = "cat $(SRCS) > $@", 251 ) 252 proguard_only_file = name + "-proguard-only.txt" 253 else: 254 proguard_only_file = None 255 256 jarjar_library( 257 name = name + "-classes", 258 testonly = testonly, 259 jars = artifact_targets + shaded_deps, 260 merge_meta_inf_files = merge_meta_inf_files, 261 ) 262 jar_name = name + "-classes.jar" 263 264 # Include r8 and proguard rules to dagger jar if there is one. 265 _package_r8_and_proguard_rule( 266 name = name, 267 artifactJar = jar_name, 268 r8Spec = r8_file, 269 proguardSpec = proguard_only_file, 270 ) 271 272 jarjar_library( 273 name = name + "-src", 274 testonly = testonly, 275 jars = [_src_jar(dep) for dep in artifact_targets], 276 merge_meta_inf_files = merge_meta_inf_files, 277 ) 278 279 if javadoc_srcs != None: 280 javadoc_library( 281 name = name + "-javadoc", 282 srcs = javadoc_srcs, 283 testonly = testonly, 284 root_packages = javadoc_root_packages, 285 exclude_packages = javadoc_exclude_packages, 286 android_api_level = javadoc_android_api_level, 287 deps = artifact_targets, 288 ) 289 else: 290 # Build an empty javadoc because Sonatype requires javadocs 291 # even if the jar is empty. 292 # https://central.sonatype.org/pages/requirements.html#supply-javadoc-and-sources 293 native.java_binary( 294 name = name + "-javadoc", 295 ) 296 297def _src_jar(target): 298 if target.startswith(":"): 299 target = Label("//" + native.package_name() + target) 300 else: 301 target = Label(target) 302 return "//%s:lib%s-src.jar" % (target.package, target.name) 303 304def _validate_maven_deps_impl(ctx): 305 """Validates the given Maven target and deps 306 307 Validates that the given "target" is a maven artifact (i.e. the "tags" 308 attribute contains "maven_coordinates=..."). Second, it calculates the 309 list of transitive dependencies of the target that are not owned by 310 another maven artifact, and validates that the given "deps" matches 311 exactly. 312 """ 313 target = ctx.attr.target 314 artifact = target[MavenInfo].artifact 315 if not artifact: 316 fail("\t[Error]: %s is not a maven artifact" % target.label) 317 318 if artifact != ctx.attr.expected_artifact: 319 fail( 320 "\t[Error]: %s expected artifact, %s, but was: %s" % ( 321 target.label, 322 ctx.attr.expected_artifact, 323 artifact, 324 ), 325 ) 326 327 all_transitive_deps = target[MavenInfo].all_transitive_deps.to_list() 328 maven_nearest_artifacts = target[MavenInfo].maven_nearest_artifacts.to_list() 329 maven_transitive_deps = target[MavenInfo].maven_transitive_deps.to_list() 330 331 expected_libs = [dep.label for dep in getattr(ctx.attr, "expected_libs", [])] 332 actual_libs = [dep for dep in all_transitive_deps if dep not in maven_transitive_deps] 333 _validate_list("artifact_target_libs", actual_libs, expected_libs) 334 335 expected_maven_deps = [dep for dep in getattr(ctx.attr, "expected_maven_deps", [])] 336 actual_maven_deps = [_strip_artifact_version(artifact) for artifact in maven_nearest_artifacts] 337 _validate_list( 338 "artifact_target_maven_deps", 339 # Exclude shaded maven deps from this list since they're not actual dependencies. 340 [dep for dep in actual_maven_deps if dep not in SHADED_MAVEN_DEPS], 341 expected_maven_deps, 342 ctx.attr.banned_maven_deps, 343 ) 344 345def _validate_list(name, actual_list, expected_list, banned_list = []): 346 missing = sorted(['"{}",'.format(x) for x in actual_list if x not in expected_list]) 347 if missing: 348 fail("\t[Error]: Found missing {}: \n\t\t".format(name) + "\n\t\t".join(missing)) 349 350 extra = sorted(['"{}",'.format(x) for x in expected_list if x not in actual_list]) 351 if extra: 352 fail("\t[Error]: Found extra {}: \n\t\t".format(name) + "\n\t\t".join(extra)) 353 354 banned = sorted(['"{}",'.format(x) for x in actual_list if x in banned_list]) 355 if banned: 356 fail("\t[Error]: Found banned {}: \n\t\t".format(name) + "\n\t\t".join(banned)) 357 358def _strip_artifact_version(artifact): 359 return artifact.rsplit(":", 1)[0] 360 361_validate_maven_deps = rule( 362 implementation = _validate_maven_deps_impl, 363 attrs = { 364 "target": attr.label( 365 doc = "The target to generate a maven artifact for.", 366 aspects = [collect_maven_info], 367 mandatory = True, 368 ), 369 "expected_artifact": attr.string( 370 doc = "The artifact name of the target.", 371 mandatory = True, 372 ), 373 "expected_libs": attr.label_list( 374 doc = "The set of transitive libraries of the target, if any.", 375 ), 376 "expected_maven_deps": attr.string_list( 377 doc = "The required maven dependencies of the target, if any.", 378 ), 379 "banned_maven_deps": attr.string_list( 380 doc = "The required maven dependencies of the target, if any.", 381 ), 382 }, 383) 384 385def _package_android_library_impl(ctx): 386 """A very, very simple Android Library (aar) packaging rule. 387 388 This rule only support packaging simple android libraries. No resources 389 support, assets, extra libs, nor jni. This rule is needed because 390 there is no 'JarJar equivalent' for AARs and some of our artifacts are 391 composed of sources spread across multiple android_library targets. 392 393 See: https://developer.android.com/studio/projects/android-library.html#aar-contents 394 """ 395 inputs = [ctx.file.manifest, ctx.file.classesJar] 396 if ctx.file.lintJar: 397 inputs.append(ctx.file.lintJar) 398 if ctx.file.proguardSpec: 399 inputs.append(ctx.file.proguardSpec) 400 401 ctx.actions.run_shell( 402 inputs = inputs, 403 outputs = [ctx.outputs.aar], 404 command = """ 405 TMPDIR="$(mktemp -d)" 406 cp {manifest} $TMPDIR/AndroidManifest.xml 407 cp {classesJar} $TMPDIR/classes.jar 408 if [[ -a {lintJar} ]]; then 409 cp {lintJar} $TMPDIR/lint.jar 410 fi 411 if [[ -a {proguardSpec} ]]; then 412 cp {proguardSpec} $TMPDIR/proguard.txt 413 fi 414 touch $TMPDIR/R.txt 415 zip -j {outputFile} $TMPDIR/* 416 """.format( 417 manifest = ctx.file.manifest.path, 418 classesJar = ctx.file.classesJar.path, 419 lintJar = ctx.file.lintJar.path if ctx.file.lintJar else "none", 420 proguardSpec = ctx.file.proguardSpec.path if ctx.file.proguardSpec else "none", 421 outputFile = ctx.outputs.aar.path, 422 ), 423 ) 424 425_package_android_library = rule( 426 implementation = _package_android_library_impl, 427 attrs = { 428 "manifest": attr.label( 429 doc = "The AndroidManifest.xml file.", 430 allow_single_file = True, 431 mandatory = True, 432 ), 433 "classesJar": attr.label( 434 doc = "The classes.jar file.", 435 allow_single_file = True, 436 mandatory = True, 437 ), 438 "lintJar": attr.label( 439 doc = "The lint.jar file.", 440 allow_single_file = True, 441 mandatory = False, 442 ), 443 "proguardSpec": attr.label( 444 doc = "The proguard.txt file.", 445 allow_single_file = True, 446 mandatory = False, 447 ), 448 }, 449 outputs = { 450 "aar": "%{name}.aar", 451 }, 452) 453 454def _package_r8_and_proguard_rule_impl(ctx): 455 inputs = [ctx.file.artifactJar] 456 if ctx.file.r8Spec: 457 inputs.append(ctx.file.r8Spec) 458 if ctx.file.proguardSpec: 459 inputs.append(ctx.file.proguardSpec) 460 ctx.actions.run_shell( 461 inputs = inputs, 462 outputs = [ctx.outputs.jar], 463 command = """ 464 TMPDIR="$(mktemp -d)" 465 cp {artifactJar} $TMPDIR/artifact.jar 466 if [[ -a {r8Spec} ]]; then 467 mkdir -p META-INF/com.android.tools/r8 468 cp {r8Spec} META-INF/com.android.tools/r8/r8.pro 469 jar uf $TMPDIR/artifact.jar META-INF/ 470 fi 471 if [[ -a {proguardSpec} ]]; then 472 mkdir -p META-INF/com.android.tools/proguard 473 cp {proguardSpec} META-INF/com.android.tools/proguard/proguard.pro 474 jar uf $TMPDIR/artifact.jar META-INF/ 475 fi 476 cp $TMPDIR/artifact.jar {outputFile} 477 """.format( 478 artifactJar = ctx.file.artifactJar.path, 479 r8Spec = ctx.file.r8Spec.path if ctx.file.r8Spec else "none", 480 proguardSpec = ctx.file.proguardSpec.path if ctx.file.proguardSpec else "none", 481 outputFile = ctx.outputs.jar.path, 482 ), 483 ) 484 485_package_r8_and_proguard_rule = rule( 486 implementation = _package_r8_and_proguard_rule_impl, 487 attrs = { 488 "artifactJar": attr.label( 489 doc = "The library artifact jar to be updated.", 490 allow_single_file = True, 491 mandatory = True, 492 ), 493 "r8Spec": attr.label( 494 doc = "The r8.txt file to be merged with the artifact jar", 495 allow_single_file = True, 496 mandatory = False, 497 ), 498 "proguardSpec": attr.label( 499 doc = "The proguard-only.txt file to be merged with the artifact jar", 500 allow_single_file = True, 501 mandatory = False, 502 ), 503 }, 504 outputs = { 505 "jar": "%{name}.jar", 506 }, 507) 508