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