xref: /aosp_15_r20/external/dagger2/tools/maven.bzl (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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