• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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