1# Copyright (C) 2019 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"""Skylark rules to collect Maven artifacts information.
15"""
16
17load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
18
19# TODO(b/142057516): Unfork this file once we've settled on a more general API.
20MavenInfo = provider(
21    fields = {
22        "artifact": """
23        The Maven coordinate for the artifact that is exported by this target, if one exists.
24        """,
25        "has_srcs": """
26        True if this library contains srcs..
27        """,
28        "all_transitive_deps": """
29        All transitive deps of the target with srcs.
30        """,
31        "maven_nearest_artifacts": """
32        The nearest maven deps of the target.
33        """,
34        "maven_transitive_deps": """
35        All transitive deps that are included in some maven dependency.
36        """,
37    },
38)
39
40_EMPTY_MAVEN_INFO = MavenInfo(
41    artifact = None,
42    has_srcs = False,
43    maven_nearest_artifacts = depset(),
44    maven_transitive_deps = depset(),
45    all_transitive_deps = depset(),
46)
47
48_MAVEN_COORDINATES_PREFIX = "maven_coordinates="
49
50def _collect_maven_info_impl(target, ctx):
51    tags = getattr(ctx.rule.attr, "tags", [])
52    srcs = getattr(ctx.rule.attr, "srcs", [])
53    deps = getattr(ctx.rule.attr, "deps", [])
54    exports = getattr(ctx.rule.attr, "exports", [])
55
56    artifact = None
57    for tag in tags:
58        if tag in ("maven:compile_only", "maven:shaded"):
59            return [_EMPTY_MAVEN_INFO]
60        if tag.startswith(_MAVEN_COORDINATES_PREFIX):
61            artifact = tag[len(_MAVEN_COORDINATES_PREFIX):]
62
63    all_deps = [dep.label for dep in (deps + exports) if dep[MavenInfo].has_srcs]
64    all_transitive_deps = [dep[MavenInfo].all_transitive_deps for dep in (deps + exports)]
65
66    maven_artifacts = []
67    maven_nearest_artifacts = []
68    maven_deps = []
69    maven_transitive_deps = []
70    for dep in (deps + exports):
71        # If the dep is itself a maven artifact, add it and all of its transitive deps.
72        # Otherwise, just propagate its transitive maven deps.
73        if dep[MavenInfo].artifact or dep[MavenInfo] == _EMPTY_MAVEN_INFO:
74            if (dep[MavenInfo].artifact):
75                maven_artifacts.append(dep[MavenInfo].artifact)
76            maven_deps.append(dep.label)
77            maven_transitive_deps.append(dep[MavenInfo].all_transitive_deps)
78        else:
79            maven_nearest_artifacts.append(dep[MavenInfo].maven_nearest_artifacts)
80            maven_transitive_deps.append(dep[MavenInfo].maven_transitive_deps)
81
82    return [MavenInfo(
83        artifact = artifact,
84        has_srcs = len(srcs) > 0,
85        maven_nearest_artifacts = depset(maven_artifacts, transitive = maven_nearest_artifacts),
86        maven_transitive_deps = depset(maven_deps, transitive = maven_transitive_deps),
87        all_transitive_deps = depset(all_deps, transitive = all_transitive_deps),
88    )]
89
90collect_maven_info = aspect(
91    attr_aspects = [
92        "deps",
93        "exports",
94    ],
95    doc = """
96    Collects the Maven information for targets, their dependencies, and their transitive exports.
97    """,
98    implementation = _collect_maven_info_impl,
99)
100
101def _fake_java_library(name, deps = None, exports = None, is_artifact = True):
102    src_file = ["%s.java" % name]
103    native.genrule(
104        name = "%s_source_file" % name,
105        outs = src_file,
106        cmd = "echo 'package pkg; class %s {}' > $@" % name,
107    )
108    native.java_library(
109        name = name,
110        srcs = src_file,
111        tags = ["maven_coordinates=%s:_:_" % name] if is_artifact else [],
112        deps = deps or [],
113        exports = exports or [],
114    )
115
116def _maven_info_test_impl(ctx):
117    env = unittest.begin(ctx)
118    asserts.equals(
119        env,
120        expected = ctx.attr.artifact if ctx.attr.artifact else None,
121        actual = ctx.attr.target[MavenInfo].artifact,
122        msg = "MavenInfo.artifact",
123    )
124    asserts.equals(
125        env,
126        expected = sorted([ctx.label.relative(dep) for dep in ctx.attr.maven_transitive_deps]),
127        actual = sorted(ctx.attr.target[MavenInfo].maven_transitive_deps.to_list()),
128        msg = "MavenInfo.maven_transitive_deps",
129    )
130    asserts.equals(
131        env,
132        expected = sorted([ctx.label.relative(dep) for dep in ctx.attr.all_transitive_deps]),
133        actual = sorted(ctx.attr.target[MavenInfo].all_transitive_deps.to_list()),
134        msg = "MavenInfo.all_transitive_deps",
135    )
136    return unittest.end(env)
137
138_maven_info_test = unittest.make(
139    _maven_info_test_impl,
140    attrs = {
141        "target": attr.label(aspects = [collect_maven_info]),
142        "artifact": attr.string(),
143        "maven_transitive_deps": attr.string_list(),
144        "all_transitive_deps": attr.string_list(),
145    },
146)
147
148def maven_info_tests():
149    """Tests for `pom_file` and `MavenInfo`.
150    """
151    _fake_java_library(name = "A")
152    _fake_java_library(
153        name = "DepOnA",
154        deps = [":A"],
155    )
156
157    _maven_info_test(
158        name = "a_test",
159        target = ":A",
160        artifact = "A:_:_",
161        maven_transitive_deps = [],
162        all_transitive_deps = [],
163    )
164
165    _maven_info_test(
166        name = "dependencies_test",
167        target = ":DepOnA",
168        artifact = "DepOnA:_:_",
169        maven_transitive_deps = [":A"],
170        all_transitive_deps = [":A"],
171    )
172
173    _fake_java_library(
174        name = "ExportsA",
175        exports = [":A"],
176    )
177
178    _maven_info_test(
179        name = "exports_test",
180        target = ":ExportsA",
181        artifact = "ExportsA:_:_",
182        maven_transitive_deps = [":A"],
183        all_transitive_deps = [":A"],
184    )
185
186    _fake_java_library(
187        name = "TransitiveExports",
188        exports = [":ExportsA"],
189    )
190
191    _maven_info_test(
192        name = "transitive_exports_test",
193        target = ":TransitiveExports",
194        artifact = "TransitiveExports:_:_",
195        maven_transitive_deps = [":ExportsA", ":A"],
196        all_transitive_deps = [":ExportsA", ":A"],
197    )
198
199    _fake_java_library(
200        name = "TransitiveDeps",
201        deps = [":ExportsA"],
202    )
203
204    _maven_info_test(
205        name = "transitive_deps_test",
206        target = ":TransitiveDeps",
207        artifact = "TransitiveDeps:_:_",
208        maven_transitive_deps = [":ExportsA", ":A"],
209        all_transitive_deps = [":ExportsA", ":A"],
210    )
211
212    _fake_java_library(name = "Node1", is_artifact = False)
213    _maven_info_test(
214        name = "test_node1",
215        target = ":Node1",
216        maven_transitive_deps = [],
217        all_transitive_deps = [],
218    )
219
220    _fake_java_library(name = "Node2_Artifact", deps = [":Node1"])
221    _maven_info_test(
222        name = "test_node2",
223        target = ":Node2_Artifact",
224        artifact = "Node2_Artifact:_:_",
225        maven_transitive_deps = [],
226        all_transitive_deps = [":Node1"],
227    )
228
229    _fake_java_library(name = "Node3", deps = [":Node2_Artifact"], is_artifact = False)
230    _maven_info_test(
231        name = "test_node3",
232        target = ":Node3",
233        maven_transitive_deps = [":Node1", ":Node2_Artifact"],
234        all_transitive_deps = [":Node1", ":Node2_Artifact"],
235    )
236
237    _fake_java_library(name = "Node4", deps = [":Node3"], is_artifact = False)
238    _maven_info_test(
239        name = "test_node4",
240        target = ":Node4",
241        maven_transitive_deps = [":Node1", ":Node2_Artifact"],
242        all_transitive_deps = [":Node1", ":Node2_Artifact", ":Node3"],
243    )
244