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