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