# Copyright (C) 2019 The Dagger Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Skylark rules to collect Maven artifacts information. """ load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") # TODO(b/142057516): Unfork this file once we've settled on a more general API. MavenInfo = provider( fields = { "artifact": """ The Maven coordinate for the artifact that is exported by this target, if one exists. """, "has_srcs": """ True if this library contains srcs.. """, "all_transitive_deps": """ All transitive deps of the target with srcs. """, "maven_nearest_artifacts": """ The nearest maven deps of the target. """, "maven_transitive_deps": """ All transitive deps that are included in some maven dependency. """, }, ) _EMPTY_MAVEN_INFO = MavenInfo( artifact = None, has_srcs = False, maven_nearest_artifacts = depset(), maven_transitive_deps = depset(), all_transitive_deps = depset(), ) _MAVEN_COORDINATES_PREFIX = "maven_coordinates=" def _collect_maven_info_impl(target, ctx): tags = getattr(ctx.rule.attr, "tags", []) srcs = getattr(ctx.rule.attr, "srcs", []) deps = getattr(ctx.rule.attr, "deps", []) exports = getattr(ctx.rule.attr, "exports", []) artifact = None for tag in tags: if tag in ("maven:compile_only", "maven:shaded"): return [_EMPTY_MAVEN_INFO] if tag.startswith(_MAVEN_COORDINATES_PREFIX): artifact = tag[len(_MAVEN_COORDINATES_PREFIX):] all_deps = [dep.label for dep in (deps + exports) if dep[MavenInfo].has_srcs] all_transitive_deps = [dep[MavenInfo].all_transitive_deps for dep in (deps + exports)] maven_artifacts = [] maven_nearest_artifacts = [] maven_deps = [] maven_transitive_deps = [] for dep in (deps + exports): # If the dep is itself a maven artifact, add it and all of its transitive deps. # Otherwise, just propagate its transitive maven deps. if dep[MavenInfo].artifact or dep[MavenInfo] == _EMPTY_MAVEN_INFO: if (dep[MavenInfo].artifact): maven_artifacts.append(dep[MavenInfo].artifact) maven_deps.append(dep.label) maven_transitive_deps.append(dep[MavenInfo].all_transitive_deps) else: maven_nearest_artifacts.append(dep[MavenInfo].maven_nearest_artifacts) maven_transitive_deps.append(dep[MavenInfo].maven_transitive_deps) return [MavenInfo( artifact = artifact, has_srcs = len(srcs) > 0, maven_nearest_artifacts = depset(maven_artifacts, transitive = maven_nearest_artifacts), maven_transitive_deps = depset(maven_deps, transitive = maven_transitive_deps), all_transitive_deps = depset(all_deps, transitive = all_transitive_deps), )] collect_maven_info = aspect( attr_aspects = [ "deps", "exports", ], doc = """ Collects the Maven information for targets, their dependencies, and their transitive exports. """, implementation = _collect_maven_info_impl, ) def _fake_java_library(name, deps = None, exports = None, is_artifact = True): src_file = ["%s.java" % name] native.genrule( name = "%s_source_file" % name, outs = src_file, cmd = "echo 'package pkg; class %s {}' > $@" % name, ) native.java_library( name = name, srcs = src_file, tags = ["maven_coordinates=%s:_:_" % name] if is_artifact else [], deps = deps or [], exports = exports or [], ) def _maven_info_test_impl(ctx): env = unittest.begin(ctx) asserts.equals( env, expected = ctx.attr.artifact if ctx.attr.artifact else None, actual = ctx.attr.target[MavenInfo].artifact, msg = "MavenInfo.artifact", ) asserts.equals( env, expected = sorted([ctx.label.relative(dep) for dep in ctx.attr.maven_transitive_deps]), actual = sorted(ctx.attr.target[MavenInfo].maven_transitive_deps.to_list()), msg = "MavenInfo.maven_transitive_deps", ) asserts.equals( env, expected = sorted([ctx.label.relative(dep) for dep in ctx.attr.all_transitive_deps]), actual = sorted(ctx.attr.target[MavenInfo].all_transitive_deps.to_list()), msg = "MavenInfo.all_transitive_deps", ) return unittest.end(env) _maven_info_test = unittest.make( _maven_info_test_impl, attrs = { "target": attr.label(aspects = [collect_maven_info]), "artifact": attr.string(), "maven_transitive_deps": attr.string_list(), "all_transitive_deps": attr.string_list(), }, ) def maven_info_tests(): """Tests for `pom_file` and `MavenInfo`. """ _fake_java_library(name = "A") _fake_java_library( name = "DepOnA", deps = [":A"], ) _maven_info_test( name = "a_test", target = ":A", artifact = "A:_:_", maven_transitive_deps = [], all_transitive_deps = [], ) _maven_info_test( name = "dependencies_test", target = ":DepOnA", artifact = "DepOnA:_:_", maven_transitive_deps = [":A"], all_transitive_deps = [":A"], ) _fake_java_library( name = "ExportsA", exports = [":A"], ) _maven_info_test( name = "exports_test", target = ":ExportsA", artifact = "ExportsA:_:_", maven_transitive_deps = [":A"], all_transitive_deps = [":A"], ) _fake_java_library( name = "TransitiveExports", exports = [":ExportsA"], ) _maven_info_test( name = "transitive_exports_test", target = ":TransitiveExports", artifact = "TransitiveExports:_:_", maven_transitive_deps = [":ExportsA", ":A"], all_transitive_deps = [":ExportsA", ":A"], ) _fake_java_library( name = "TransitiveDeps", deps = [":ExportsA"], ) _maven_info_test( name = "transitive_deps_test", target = ":TransitiveDeps", artifact = "TransitiveDeps:_:_", maven_transitive_deps = [":ExportsA", ":A"], all_transitive_deps = [":ExportsA", ":A"], ) _fake_java_library(name = "Node1", is_artifact = False) _maven_info_test( name = "test_node1", target = ":Node1", maven_transitive_deps = [], all_transitive_deps = [], ) _fake_java_library(name = "Node2_Artifact", deps = [":Node1"]) _maven_info_test( name = "test_node2", target = ":Node2_Artifact", artifact = "Node2_Artifact:_:_", maven_transitive_deps = [], all_transitive_deps = [":Node1"], ) _fake_java_library(name = "Node3", deps = [":Node2_Artifact"], is_artifact = False) _maven_info_test( name = "test_node3", target = ":Node3", maven_transitive_deps = [":Node1", ":Node2_Artifact"], all_transitive_deps = [":Node1", ":Node2_Artifact"], ) _fake_java_library(name = "Node4", deps = [":Node3"], is_artifact = False) _maven_info_test( name = "test_node4", target = ":Node4", maven_transitive_deps = [":Node1", ":Node2_Artifact"], all_transitive_deps = [":Node1", ":Node2_Artifact", ":Node3"], )