blob: 8eea7575a5e483c536ceaf238d9fb7411d82a4b6 [file]
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# 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
"""A parser of the dependency tree from Coursier or maven_install.json.
This file contains parsing functions to turn a JSON-like dependency tree
into target declarations (jvm_import) for the final @maven//:BUILD file.
"""
load(
"//private:coursier_utilities.bzl",
"PLATFORM_CLASSIFIER",
"escape",
"get_classifier",
"get_packaging",
"is_maven_local_path",
"match_group_and_artifact",
"strip_packaging_and_classifier",
"strip_packaging_and_classifier_and_version",
)
JETIFY_INCLUDE_LIST_JETIFY_ALL = ["*"]
def _genrule_copy_artifact_from_http_file(artifact, visibilities):
# skip artifacts without any urls (ie: maven local artifacts)
if not len(artifact["urls"]):
return ""
http_file_repository = escape(artifact["coordinates"])
return "\n".join([
"genrule(",
" name = \"%s_extension\"," % http_file_repository,
" srcs = [\"@%s//file\"]," % http_file_repository,
" outs = [\"%s\"]," % artifact["file"],
" cmd = \"cp $< $@\",",
" visibility = [%s]" % (",".join(["\"%s\"" % v for v in visibilities])),
")",
])
def _deduplicate_list(items):
seen_items = {}
unique_items = []
for item in items:
if item not in seen_items:
seen_items[item] = True
unique_items.append(item)
return unique_items
# Generate BUILD file with jvm_import and aar_import for each artifact in
# the transitive closure, with their respective deps mapped to the resolved
# tree.
#
# Made function public for testing.
def _generate_imports(repository_ctx, dependencies, explicit_artifacts, neverlink_artifacts, testonly_artifacts, override_targets, skip_maven_local_dependencies):
# The list of java_import/aar_import declaration strings to be joined at the end
all_imports = []
# A dictionary (set) of coordinates. This is to ensure we don't generate
# duplicate labels
#
# seen_imports :: string -> bool
seen_imports = {}
# A list of versionless target labels for jar artifacts. This is used for
# generating a compatibility layer for repositories. For example, if we generate
# @maven//:junit_junit, we also generate @junit_junit//jar as an alias to it.
jar_versionless_target_labels = []
labels_to_override = {}
for coord in override_targets:
labels_to_override.update({escape(coord): override_targets.get(coord)})
default_visibilities = repository_ctx.attr.strict_visibility_value if repository_ctx.attr.strict_visibility else ["//visibility:public"]
# First collect a map of target_label to their srcjar relative paths, and symlink the srcjars if needed.
# We will use this map later while generating target declaration strings with the "srcjar" attr.
srcjar_paths = None
if repository_ctx.attr.fetch_sources:
srcjar_paths = {}
for artifact in dependencies:
if get_classifier(artifact["coordinates"]) == "sources":
artifact_path = artifact["file"]
# Skip the maven local dependencies if requested
if skip_maven_local_dependencies and is_maven_local_path(artifact_path):
continue
if artifact_path != None and artifact_path not in seen_imports:
seen_imports[artifact_path] = True
target_label = escape(strip_packaging_and_classifier_and_version(artifact["coordinates"]))
srcjar_paths[target_label] = artifact_path
if repository_ctx.attr.maven_install_json:
all_imports.append(_genrule_copy_artifact_from_http_file(artifact, default_visibilities))
jetify_all = repository_ctx.attr.jetify and repository_ctx.attr.jetify_include_list == JETIFY_INCLUDE_LIST_JETIFY_ALL
# Write artifacts to dict to achieve O(1) lookup instead of O(n).
jetify_include_dict = {}
for jetify_include_artifact in repository_ctx.attr.jetify_include_list:
jetify_include_dict[jetify_include_artifact] = None
# Iterate through the list of artifacts, and generate the target declaration strings.
for artifact in dependencies:
artifact_path = artifact["file"]
# Skip the maven local dependencies if requested
if skip_maven_local_dependencies and is_maven_local_path(artifact_path):
continue
simple_coord = strip_packaging_and_classifier_and_version(artifact["coordinates"])
target_label = escape(simple_coord)
alias_visibility = ""
if target_label in seen_imports:
# Skip if we've seen this target label before. Every versioned artifact is uniquely mapped to a target label.
pass
elif repository_ctx.attr.fetch_sources and get_classifier(artifact["coordinates"]) == "sources":
# We already processed the sources above, so skip them here.
pass
elif repository_ctx.attr.fetch_javadoc and get_classifier(artifact["coordinates"]) == "javadoc":
seen_imports[target_label] = True
all_imports.append(
"filegroup(\n\tname = \"%s\",\n\tsrcs = [\"%s\"],\n\ttags = [\"javadoc\"],\n)" % (target_label, artifact_path),
)
elif get_packaging(artifact["coordinates"]) == "json":
seen_imports[target_label] = True
versioned_target_alias_label = "%s_extension" % escape(artifact["coordinates"])
all_imports.append(
"alias(\n\tname = \"%s\",\n\tactual = \"%s\",\n\tvisibility = [\"//visibility:public\"],\n)" % (target_label, versioned_target_alias_label),
)
if repository_ctx.attr.maven_install_json:
all_imports.append(_genrule_copy_artifact_from_http_file(artifact, default_visibilities))
elif target_label in labels_to_override:
# Override target labels with the user provided mapping, instead of generating
# a jvm_import/aar_import based on information in dep_tree.
seen_imports[target_label] = True
all_imports.append(
"alias(\n\tname = \"%s\",\n\tactual = \"%s\",\n\tvisibility = [\"//visibility:public\"],)" % (target_label, labels_to_override.get(target_label)),
)
if repository_ctx.attr.maven_install_json:
# Provide the downloaded artifact as a file target.
all_imports.append(_genrule_copy_artifact_from_http_file(artifact, default_visibilities))
elif artifact_path != None:
seen_imports[target_label] = True
# 1. Generate the rule class.
#
# (jetify_)(jvm|aar)_import(
#
packaging = artifact_path.split(".").pop()
if packaging == "jar":
# Regular `java_import` invokes ijar on all JARs, causing some Scala and
# Kotlin compile interface JARs to be incorrect. We replace java_import
# with a simple jvm_import Starlark rule that skips ijar.
import_rule = "jvm_import"
jar_versionless_target_labels.append(target_label)
elif packaging == "aar":
import_rule = "aar_import"
jar_versionless_target_labels.append(target_label)
else:
fail("Unsupported packaging type: " + packaging)
jetify = jetify_all or (repository_ctx.attr.jetify and simple_coord in jetify_include_dict)
if jetify:
import_rule = "jetify_" + import_rule
target_import_string = [import_rule + "("]
# 2. Generate the target label.
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
#
target_import_string.append("\tname = \"%s\"," % target_label)
# 3. Generate the jars/aar attribute to the relative path of the artifact.
# Optionally generate srcjar attr too.
#
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
#
if packaging == "jar":
target_import_string.append("\tjars = [\"%s\"]," % artifact_path)
if srcjar_paths != None and target_label in srcjar_paths:
target_import_string.append("\tsrcjar = \"%s\"," % srcjar_paths[target_label])
elif packaging == "aar":
target_import_string.append("\taar = \"%s\"," % artifact_path)
if srcjar_paths != None and target_label in srcjar_paths:
target_import_string.append("\tsrcjar = \"%s\"," % srcjar_paths[target_label])
if jetify and repository_ctx.attr.use_starlark_android_rules:
# Because jetifier.bzl cannot conditionally import the starlark rules
# (it's not a generated file), inject the aar_import rule from
# the load statement in the generated file.
target_import_string.append("\t_aar_import = aar_import,")
# 4. Generate the deps attribute with references to other target labels.
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
# deps = [
# ":org_hamcrest_hamcrest_core",
# ],
#
target_import_string.append("\tdeps = [")
# Dedupe dependencies here. Sometimes coursier will return "x.y:z:aar:version" and "x.y:z:version" in the
# same list of dependencies.
target_import_labels = []
for dep in artifact["deps"]:
if get_packaging(dep) == "json":
continue
stripped_dep = strip_packaging_and_classifier_and_version(dep)
dep_target_label = escape(stripped_dep)
# If we have matching artifacts with platform classifiers, skip adding this dependency.
# See https://github.com/bazelbuild/rules_jvm_external/issues/686
if match_group_and_artifact(artifact["coordinates"], dep) and \
get_classifier(artifact["coordinates"]) in PLATFORM_CLASSIFIER and \
get_classifier(dep) in PLATFORM_CLASSIFIER:
continue
# Coursier returns cyclic dependencies sometimes. Handle it here.
# See https://github.com/bazelbuild/rules_jvm_external/issues/172
if dep_target_label != target_label:
if dep_target_label in labels_to_override:
dep_target_label = labels_to_override.get(dep_target_label)
else:
dep_target_label = ":" + dep_target_label
target_import_labels.append("\t\t\"%s\",\n" % dep_target_label)
target_import_labels = _deduplicate_list(target_import_labels)
target_import_string.append("".join(target_import_labels) + "\t],")
# 5. Add a tag with the original maven coordinates for use generating pom files
# For use with this rule https://github.com/google/bazel-common/blob/f1115e0f777f08c3cdb115526c4e663005bec69b/tools/maven/pom_file.bzl#L177
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
# deps = [
# ":org_hamcrest_hamcrest_core",
# ],
# tags = [
# "maven_coordinates=org.hamcrest:hamcrest.library:1.3"],
# "maven_url=https://repo1.maven.org/maven/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
# ],
target_import_string.append("\ttags = [")
target_import_string.append("\t\t\"maven_coordinates=%s\"," % artifact["coordinates"])
if len(artifact["urls"]):
target_import_string.append("\t\t\"maven_url=%s\"," % artifact["urls"][0])
else:
target_import_string.append("\t\t\"maven_url=None\",")
target_import_string.append("\t],")
# 6. If `neverlink` is True in the artifact spec, add the neverlink attribute to make this artifact
# available only as a compile time dependency.
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
# deps = [
# ":org_hamcrest_hamcrest_core",
# ],
# tags = [
# "maven_coordinates=org.hamcrest:hamcrest.library:1.3"],
# "maven_url=https://repo1.maven.org/maven/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
# ],
# neverlink = True,
if neverlink_artifacts.get(simple_coord):
target_import_string.append("\tneverlink = True,")
# 7. If `testonly` is True in the artifact spec, add the testonly attribute to make this artifact
# available only as a test dependency.
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
# deps = [
# ":org_hamcrest_hamcrest_core",
# ],
# tags = [
# "maven_coordinates=org.hamcrest:hamcrest.library:1.3"],
# "maven_url=https://repo1.maven.org/maven/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
# ],
# neverlink = True,
# testonly = True,
if testonly_artifacts.get(simple_coord):
target_import_string.append("\ttestonly = True,")
# 8. If `strict_visibility` is True in the artifact spec, define public
# visibility only for non-transitive dependencies.
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
# deps = [
# ":org_hamcrest_hamcrest_core",
# ],
# tags = [
# "maven_coordinates=org.hamcrest:hamcrest.library:1.3"],
# "maven_url=https://repo1.maven.org/maven/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
# ],
# neverlink = True,
# testonly = True,
# visibility = ["//visibility:public"],
target_visibilities = []
if not repository_ctx.attr.strict_visibility or explicit_artifacts.get(simple_coord):
target_visibilities.append("//visibility:public")
elif repository_ctx.attr.generate_compat_repositories:
target_visibilities.append("@%s//:__subpackages__" % target_label)
else:
target_visibilities.extend(repository_ctx.attr.strict_visibility_value)
target_import_string.append("\tvisibility = [%s]," % (",".join(["\"%s\"" % t for t in target_visibilities])))
alias_visibility = "\tvisibility = [%s],\n" % (",".join(["\"%s\"" % t for t in target_visibilities]))
# 9. Finish the java_import rule.
#
# java_import(
# name = "org_hamcrest_hamcrest_library",
# jars = ["https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# srcjar = "https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar",
# deps = [
# ":org_hamcrest_hamcrest_core",
# ],
# tags = [
# "maven_coordinates=org.hamcrest:hamcrest.library:1.3"],
# "maven_url=https://repo1.maven.org/maven/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
# ],
# neverlink = True,
# testonly = True,
# )
target_import_string.append(")")
all_imports.append("\n".join(target_import_string))
# 10. Create a versionless alias target
#
# alias(
# name = "org_hamcrest_hamcrest_library_1_3",
# actual = "org_hamcrest_hamcrest_library",
# )
versioned_target_alias_label = escape(strip_packaging_and_classifier(artifact["coordinates"]))
all_imports.append("alias(\n\tname = \"%s\",\n\tactual = \"%s\",\n%s)" %
(versioned_target_alias_label, target_label, alias_visibility))
# 11. If using maven_install.json, use a genrule to copy the file from the http_file
# repository into this repository.
#
# genrule(
# name = "org_hamcrest_hamcrest_library_1_3_extension",
# srcs = ["@org_hamcrest_hamcrest_library_1_3//file"],
# outs = ["@maven//:v1/https/repo1.maven.org/maven2/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar"],
# cmd = "cp $< $@",
# )
if repository_ctx.attr.maven_install_json:
all_imports.append(_genrule_copy_artifact_from_http_file(artifact, default_visibilities))
else: # artifact_path == None:
# Special case for certain artifacts that only come with a POM file.
# Such artifacts "aggregate" their dependencies, so they don't have
# a JAR for download.
#
# Note that there are other possible reasons that the artifact_path is None:
#
# https://github.com/bazelbuild/rules_jvm_external/issues/70
# https://github.com/bazelbuild/rules_jvm_external/issues/74
#
#
# This can be due to the artifact being of a type that's unknown to
# Coursier. This is increasingly rare as we add more types to
# SUPPORTED_PACKAGING_TYPES. It's also increasingly
# uncommon relatively to POM-only / parent artifacts. So when we
# encounter an artifact without a filepath, we assume that it's a
# parent artifact that just exports its dependencies, instead of
# failing.
seen_imports[target_label] = True
target_import_string = ["java_library("]
target_import_string.append("\tname = \"%s\"," % target_label)
target_import_string.append("\texports = [")
target_import_labels = []
for dep in artifact.get("deps", []):
dep_target_label = escape(strip_packaging_and_classifier_and_version(dep))
# Coursier returns cyclic dependencies sometimes. Handle it here.
# See https://github.com/bazelbuild/rules_jvm_external/issues/172
if dep_target_label != target_label:
target_import_labels.append("\t\t\":%s\",\n" % dep_target_label)
target_import_labels = _deduplicate_list(target_import_labels)
target_import_string.append("".join(target_import_labels) + "\t],")
target_import_string.append("\ttags = [\"maven_coordinates=%s\"]," % artifact["coordinates"])
if repository_ctx.attr.strict_visibility and explicit_artifacts.get(simple_coord):
target_import_string.append("\tvisibility = [\"//visibility:public\"],")
alias_visibility = "\tvisibility = [\"//visibility:public\"],\n"
else:
target_import_string.append("\tvisibility = [%s]," % (",".join(["\"%s\"" % v for v in default_visibilities])))
alias_visibility = "\tvisibility = [%s],\n" % (",".join(["\"%s\"" % v for v in default_visibilities]))
target_import_string.append(")")
all_imports.append("\n".join(target_import_string))
versioned_target_alias_label = escape(strip_packaging_and_classifier(artifact["coordinates"]))
all_imports.append("alias(\n\tname = \"%s\",\n\tactual = \"%s\",\n%s)" %
(versioned_target_alias_label, target_label, alias_visibility))
return ("\n".join(all_imports), jar_versionless_target_labels)
parser = struct(
generate_imports = _generate_imports,
)