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