blob: 883e943cad5446a1bb548f2c4a0722f86fab43cc [file] [log] [blame]
load(":functions.bzl", "create_option_file", "explicit_target")
load(":coverage.bzl", "coverage_baseline")
load(":kotlin.bzl", "kotlin_library")
load(":merge_archives.bzl", "run_singlejar")
load(":utils.bzl", "is_release")
load("@bazel_tools//tools/jdk:toolchain_utils.bzl", "find_java_toolchain")
def generate_pom(
ctx,
group,
artifact,
version,
output_pom,
deps = [],
exports = [],
description = None,
pom_name = None,
source = None,
export = False,
properties = None,
properties_files = None,
version_property = None,
exclusions = None):
inputs = []
args = []
# Input file to take as base
if source:
args += ["-i", source.path]
inputs += [source]
# Output file
args += ["-o", output_pom.path]
args += ["-x"] if export else []
# Overrides
if group:
args += ["--group", group]
if artifact:
args += ["--artifact", artifact]
if version:
args += ["--version", version]
if description:
args += ["--description", description]
if pom_name:
args += ["--pom_name", pom_name]
if properties:
args += ["--properties", properties.path]
inputs += [properties]
if properties_files:
args += ["--properties", ":".join([file.path for file in properties_files])]
inputs += properties_files
if version_property:
args += ["--version_property", version_property]
# Exclusions
if exclusions:
for (dependency, exclusions) in exclusions.items():
args += ["--exclusion", dependency, ",".join([e for e in exclusions])]
args += ["--deps", ":".join([dep.path for dep in deps])]
args += ["--exports", ":".join([dep.path for dep in exports])]
inputs += deps + exports
ctx.actions.run(
mnemonic = "GenPom",
inputs = inputs,
outputs = [output_pom],
arguments = args,
executable = ctx.executable._pom,
)
def _zipper(actions, zipper, desc, map_file, files, out):
zipper_args = ["c", out.path]
zipper_args += ["@" + map_file.path]
actions.run(
inputs = files + [map_file],
outputs = [out],
executable = zipper,
arguments = zipper_args,
progress_message = desc,
mnemonic = "zipper",
)
def _local_maven_repository_impl(repository_ctx):
workspace = repository_ctx.path(repository_ctx.attr._this_file).dirname.dirname.dirname.dirname
local_repo = str(workspace) + "/" + repository_ctx.attr.path
# Create a symlink at external/$repo_rule_name/repo and make it point to
# to the actual location of the checked-in local maven repository so that
# the BUILD file generated under the external repository does not have to
# refer back to files in the internal repository.
# Example:
# ~/studio-main/bazel-studio-master-dev/external/maven/repo
# -> ~/studio-main/prebuilts/tools/common/m2/repository
#
# This is needed to avoid the following Bazel bug:
# https://github.com/bazelbuild/bazel/issues/6752
repo_path = "repo"
repository_ctx.symlink(local_repo, repo_path)
jdk11 = workspace.get_child("prebuilts").get_child("studio").get_child("jdk").get_child("jdk11")
os_name = repository_ctx.os.name.lower()
if os_name.startswith("mac os"):
# Ignore mac-arm64, it's OK to use x86 version of java.
jdk11_os = jdk11.get_child("mac").get_child("Contents").get_child("Home")
elif os_name.find("windows") != -1:
jdk11_os = jdk11.get_child("win")
else:
jdk11_os = jdk11.get_child("linux")
java_exe = "java.exe" if os_name.find("windows") != -1 else "java"
java = jdk11_os.get_child("bin").get_child(java_exe)
if not java.exists:
fail("Failed to find java binary at: " + str(java))
# Invoke build file generator tool.
arguments = (
[java, "-jar", repository_ctx.path(repository_ctx.attr._generator)] +
repository_ctx.attr.artifacts +
["+" + coordinate for coordinate in repository_ctx.attr.data] +
["-o", "BUILD"] +
["--repo-path", repo_path] +
[
remote_repo
for k, v in repository_ctx.attr.remote_repositories.items()
for remote_repo in ("--remote-repo", k + "=" + v)
]
)
result = repository_ctx.execute(
arguments,
)
if result.return_code:
fail("Failed to generate BUILD file using command:\n" +
str(arguments) + "\n" +
"Stdout: " + result.stdout + "\n" +
"Stderr: " + result.stderr + "\n")
_local_maven_repository = repository_rule(
attrs = {
"path": attr.string(),
"_this_file": attr.label(default = "@//tools/base/bazel:maven.bzl"),
"artifacts": attr.string_list(),
"data": attr.string_list(),
"remote_repositories": attr.string_dict(),
"_generator": attr.label(default = "@//tools/base/bazel:maven/generator.jar"),
"_pom": attr.label(
executable = True,
cfg = "host",
default = Label("//tools/base/bazel:pom_generator"),
allow_files = True,
),
},
environ = ["MAVEN_FETCH"],
local = True,
implementation = _local_maven_repository_impl,
)
# Resolves artifacts transitively from a local Maven repository,
# and generates a BUILD file that contains rules for all the resolved
# Maven artifacts.
#
# Consumers can depend on the listed Maven artifacts using the name
# of the repository rule, and the Maven coordinates of the artifact
# they need, without the need to express the version.
#
# For instance, to depend on Guava, add the following to deps:
# "@maven//:com.google.guava.guava"
#
# And the repository will provide the version of Guava that is obtained
# from Maven dependency resolution.
#
# Args:
# path: Local path to a Maven repository relative to the workspace root
# artifacts: Coordinates of the Maven artifacts to resolve. These artifacts
# will generate version-less maven_library rules to be used
# for compilation.
# data: Coordinates of the Maven artifacts to include as data. These
# artifacts versions will not be resolved and only maven_artifacts will be
# generated for these.
# fetch: If True, artifacts will be downloaded from remote_repositories
# to the local repository
# remote_repositories: A dictionary that maps remote repository names to urls
def local_maven_repository(name, path, artifacts, remote_repositories, data = []):
_local_maven_repository(
name = name,
path = path,
artifacts = artifacts,
data = data,
remote_repositories = remote_repositories,
)
# Rule set that supports both Java and Maven providers, still under development
MavenInfo = provider(fields = {
"pom": "A File referencing the pom",
"repo_path": "A String with the repo relative path",
"files": "The files of this artifact",
"transitive": "The transitive files of this artifact",
})
def _maven_artifact_impl(ctx):
files = [(ctx.attr.repo_path + "/" + file.basename, file) for file in ctx.files.files]
return [
DefaultInfo(files = depset(ctx.files.files)),
MavenInfo(
pom = ctx.file.pom,
files = files,
transitive = depset(direct = files, transitive = [d[MavenInfo].transitive for d in ctx.attr.deps]),
),
]
_maven_artifact = rule(
implementation = _maven_artifact_impl,
attrs = {
"files": attr.label_list(allow_files = True),
"deps": attr.label_list(),
"pom": attr.label(allow_single_file = True),
"repo_path": attr.string(),
"repo_root_path": attr.string(),
},
)
# Files that should be excluded from glob() expressions
# when collecting files from repository.
_REPO_GLOB_EXCLUDES = [
"resolver-status.properties",
"_remote.repositories",
"maven-metadata-*.xml",
"maven-metadata-*.xml.sha1",
]
def _get_artifact_dir(repo_root_path, repo_path):
if repo_root_path and repo_path:
return repo_root_path + "/" + repo_path + "/"
elif repo_path:
return repo_path + "/"
else:
return ""
def maven_artifact(
name,
pom,
repo_path = "",
repo_root_path = "",
parent = None,
deps = [],
**kwargs):
artifact_dir = _get_artifact_dir(repo_root_path, repo_path)
_maven_artifact(
name = name,
pom = pom,
repo_path = repo_path,
repo_root_path = repo_root_path,
files = native.glob(
include = [artifact_dir + "**"],
exclude = [artifact_dir + "**/" + exclude for exclude in _REPO_GLOB_EXCLUDES],
),
deps = ([parent] if parent else []) + deps,
**kwargs
)
def _maven_import_impl(ctx):
infos = []
for jar in ctx.files.jars:
ijar = java_common.run_ijar(
actions = ctx.actions,
jar = jar,
java_toolchain = find_java_toolchain(ctx, ctx.attr._java_toolchain),
)
infos.append(JavaInfo(
output_jar = jar,
compile_jar = ijar,
deps = [dep[JavaInfo] for dep in ctx.attr.deps + ctx.attr.exports],
))
infos += [dep[JavaInfo] for dep in ctx.attr.exports]
data_deps = []
data_deps += ctx.attr.deps if ctx.attr.deps else []
data_deps += ctx.attr.exports if ctx.attr.exports else []
data_deps += ctx.attr.original_deps if ctx.attr.original_deps else []
data_deps += [ctx.attr.parent] if ctx.attr.parent else []
mavens = [dep[MavenInfo] for dep in data_deps]
files = [(ctx.attr.repo_path + "/" + file.basename, file) for file in ctx.files.files]
names = []
for jar in ctx.files.jars:
name = jar.basename
if jar.extension:
name = jar.basename[:-len(jar.extension) - 1]
names.append(name)
return struct(
providers = [
DefaultInfo(files = depset(ctx.files.jars)),
MavenInfo(
pom = ctx.file.pom,
files = files,
transitive = depset(direct = files, transitive = [info.transitive for info in mavens]),
),
java_common.merge(infos),
],
notice = struct(
file = ctx.attr.notice,
name = ",".join(names),
),
)
_maven_import = rule(
implementation = _maven_import_impl,
attrs = {
"jars": attr.label_list(allow_files = True),
"files": attr.label_list(allow_files = True),
"deps": attr.label_list(providers = [MavenInfo]),
"exports": attr.label_list(providers = [MavenInfo]),
"repo_path": attr.string(),
"repo_root_path": attr.string(),
"parent": attr.label(),
"pom": attr.label(allow_single_file = True),
"notice": attr.label(allow_single_file = True),
"_java_toolchain": attr.label(default = Label("@bazel_tools//tools/jdk:current_java_toolchain")),
"original_deps": attr.label_list(),
"srcjar": attr.label(allow_files = True),
},
)
# A java_import rule extended with pom and parent attributes for maven libraries.
def maven_import(
name,
pom,
classifiers = [],
visibility = None,
jars = [],
repo_path = "",
repo_root_path = "",
parent = None,
classified_only = False,
**kwargs):
artifact_dir = _get_artifact_dir(repo_root_path, repo_path)
_maven_import(
name = name,
visibility = visibility,
jars = jars,
pom = pom,
repo_path = repo_path,
repo_root_path = repo_root_path,
parent = parent,
files = native.glob(
include = [artifact_dir + "**"],
exclude = [artifact_dir + "**/" + exclude for exclude in _REPO_GLOB_EXCLUDES],
),
notice = artifact_dir + "NOTICE",
tags = ["require_license"],
**kwargs
)
MavenRepoInfo = provider(fields = {
"artifacts": "The list of files in the repo",
"manifest": "The repo's manifest file with short paths for test rules",
"build_manifest": "The repo's manifest file with full paths for build rules",
})
def _maven_repository_impl(ctx):
rel_paths = []
files = []
artifacts = depset(
direct = [f for artifact in ctx.attr.artifacts for f in artifact[MavenInfo].files],
transitive = [artifact[MavenInfo].transitive for artifact in ctx.attr.artifacts] if ctx.attr.include_transitive_deps else [],
)
# Redundancy check:
if not ctx.attr.allow_duplicates:
has_duplicates = False
rem = {a: True for a in ctx.attr.artifacts}
for b in ctx.attr.artifacts:
b_items = {e: None for e in b[MavenInfo].transitive.to_list()}
for a in ctx.attr.artifacts:
if a != b:
included = True
for e in a[MavenInfo].files:
if e not in b_items:
included = False
break
if included:
rem[a] = False
print("%s is redundant as it's a dependency of %s" % (a.label, b.label))
has_duplicates = True
if has_duplicates:
print("The minimum set of dependencies is:\n" + ",\n".join(["\"%s\"" % str(a.label) for a, v in rem.items() if v]))
fail("Duplicated/Redundant dependencies found.")
for r, f in artifacts.to_list():
files.append(f)
rel_paths.append((r, f))
build_manifest_content = "".join([k + "=" + v.path + "\n" for k, v in rel_paths])
manifest_content = "".join([k + "=" + v.short_path + "\n" for k, v in rel_paths])
ctx.actions.write(ctx.outputs.manifest, manifest_content)
build_manifest = create_option_file(ctx, ctx.label.name + ".build.manifest", build_manifest_content)
_zipper(ctx.actions, ctx.executable._zipper, "Creating repo zip", build_manifest, files, ctx.outputs.zip)
runfiles = ctx.runfiles(files = [ctx.outputs.manifest] + files)
return [
DefaultInfo(
# Do not include the zip as a default output (like _deploy.jar)
files = depset([ctx.outputs.manifest]),
runfiles = runfiles,
),
MavenRepoInfo(
artifacts = files,
manifest = ctx.outputs.manifest,
build_manifest = build_manifest,
),
]
# Creates a maven repo with the given artifacts and all their transitive dependencies.
#
# The rule exposes a MavenRepoInfo provider and outputs a manifest file. The manifest file contains
# relative runfile paths, which are available only during bazel run/test (see
# https://docs.bazel.build/versions/master/skylark/rules.html#runfiles-location).
# If the repo is used as part of a Bazel rule, the provider should be used instead to obtain the
# full paths to each of the artifacts.
#
# Usage:
# maven_repository(
# name = The name of the rule. The output of the rule will be ${name}.manifest.
# artifacts = A list of all maven_library artifacts to add to the repo.
# include_transitive_deps = Also include the transitive dependencies of artifacts in the repo.
# )
maven_repository = rule(
attrs = {
"artifacts": attr.label_list(providers = [MavenInfo]),
"include_transitive_deps": attr.bool(default = True),
"allow_duplicates": attr.bool(default = True),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "exec",
executable = True,
),
},
outputs = {
"manifest": "%{name}.manifest",
"zip": "%{name}.zip",
},
implementation = _maven_repository_impl,
)
def split_coordinates(coordinates):
parts = coordinates.split(":")
if len(parts) != 3:
print("Unsupported coordinates")
segments = parts[0].split(".") + [parts[1], parts[2]]
return struct(
group_id = parts[0],
artifact_id = parts[1],
version = parts[2],
repo_path = "/".join(segments),
)
def _maven_library_impl(ctx):
infos_deps = [dep[MavenInfo] for dep in ctx.attr.deps]
infos_exports = [dep[MavenInfo] for dep in ctx.attr.exports]
pom_deps = [info.pom for info in infos_deps]
pom_exports = [info.pom for info in infos_exports]
coordinates = split_coordinates(ctx.attr.coordinates)
basename = coordinates.artifact_id + "-" + coordinates.version
pom_name = ctx.attr.pom_name if ctx.attr.pom_name else coordinates.group_id + "." + coordinates.artifact_id
generate_pom(
ctx,
source = ctx.file.template_pom,
output_pom = ctx.outputs.pom,
group = coordinates.group_id,
artifact = coordinates.artifact_id,
version = coordinates.version,
description = ctx.attr.description,
pom_name = pom_name,
deps = pom_deps,
exports = pom_exports,
)
repo_files = [(coordinates.repo_path + "/" + n, f.files.to_list()[0]) for f, n in ctx.attr.files.items()]
repo_files.append((coordinates.repo_path + "/" + basename + ".pom", ctx.outputs.pom))
if ctx.attr.library:
repo_files.append((coordinates.repo_path + "/" + basename + ".jar", ctx.file.library))
source_jars = []
source_jars += ctx.attr.library[JavaInfo].source_jars
if ctx.attr.notice:
notice_jar = ctx.actions.declare_file(ctx.label.name + ".notice.jar")
ctx.actions.run(
inputs = [ctx.file.notice],
outputs = [notice_jar],
executable = ctx.executable._zipper,
arguments = ["c", notice_jar.path, ctx.file.notice.basename + "=" + ctx.file.notice.path],
progress_message = "Creating notice jar",
mnemonic = "zipper",
)
source_jars += [notice_jar]
if source_jars:
source_jar = ctx.actions.declare_file(ctx.label.name + ".src.jar")
run_singlejar(
ctx = ctx,
jars = source_jars,
out = source_jar,
)
repo_files.append((coordinates.repo_path + "/" + basename + "-sources.jar", source_jar))
transitive = depset(direct = repo_files, transitive = [info.transitive for info in infos_deps + infos_exports])
default_files = [ctx.attr.library[DefaultInfo].files] if ctx.attr.library else []
providers = [
DefaultInfo(files = depset(direct = [ctx.outputs.pom], transitive = default_files)),
MavenInfo(pom = ctx.outputs.pom, files = repo_files, transitive = transitive),
]
if ctx.attr.library:
providers += [ctx.attr.library[JavaInfo]]
return providers
_maven_library = rule(
attrs = {
"notice": attr.label(allow_single_file = True),
"library": attr.label(providers = [JavaInfo], allow_single_file = True),
"files": attr.label_keyed_string_dict(allow_files = True),
"coordinates": attr.string(),
"description": attr.string(),
"pom_name": attr.string(),
"template_pom": attr.label(
default = Label("//tools/base/bazel:maven/android.pom"),
allow_single_file = True,
),
"deps": attr.label_list(providers = [MavenInfo]),
"exports": attr.label_list(providers = [MavenInfo]),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
),
"_singlejar": attr.label(
default = Label("@bazel_tools//tools/jdk:singlejar"),
cfg = "host",
executable = True,
),
"_pom": attr.label(
executable = True,
cfg = "host",
default = Label("//tools/base/bazel:pom_generator"),
allow_files = True,
),
},
outputs = {
"pom": "%{name}.pom",
},
fragments = ["java"],
implementation = _maven_library_impl,
)
def maven_library(
name,
srcs,
javacopts = [],
resources = [],
resource_strip_prefix = None,
data = [],
deps = [],
exports = [],
runtime_deps = [],
bundled_deps = [],
friends = [],
notice = None,
coordinates = None,
jar_name = None,
description = None,
pom_name = None,
exclusions = None,
lint_baseline = None,
lint_classpath = [],
lint_is_test_sources = False,
lint_timeout = None,
module_name = None,
plugins = [],
manifest_lines = None,
**kwargs):
"""Compiles a library jar from Java and Kotlin sources
Args:
srcs: The sources of the library.
javacopts: Additional javac options.
resources: Resources to add to the jar.
resources_strip_prefix: The prefix to strip from the resources path.
deps: The dependencies of this library.
exports: The exported dependencies of this library.
runtime_deps: The runtime dependencies.
bundled_deps: The dependencies that are bundled inside the output jar and not treated as a maven dependency
friends: The list of kotlin-friends.
notice: An optional notice file to be included in the jar.
coordinates: The maven coordinates of this artifact.
exclusions: Files to exclude from the generated pom file.
lint_*: Lint configuration arguments
module_name: The kotlin module name.
"""
kotlins = [src for src in srcs if src.endswith(".kt")]
neverlink_deps = [dep for dep in bundled_deps if dep.endswith("_neverlink")]
bundled_deps = [dep for dep in bundled_deps if dep not in neverlink_deps]
kotlin_library(
name = name + ".lib",
jar_name = jar_name if jar_name else name + ".jar",
srcs = srcs,
compress_resources = is_release(),
data = data,
deps = deps + neverlink_deps,
exports = exports,
bundled_deps = bundled_deps,
friends = friends,
notice = notice,
module_name = module_name,
resources = resources,
resource_strip_prefix = resource_strip_prefix,
runtime_deps = runtime_deps,
stdlib = None, # Maven libraries use the stdlib in different scopes and versions.
plugins = plugins,
manifest_lines = manifest_lines,
**kwargs
)
_maven_library(
name = name,
notice = notice,
deps = deps,
exports = exports,
coordinates = coordinates,
description = description,
pom_name = pom_name,
library = ":" + name + ".lib",
**kwargs
)
def custom_maven_library(
name,
files,
**kwargs):
"""A rule to create a custom maven library with provided files.
Args:
name: the name of the rule
files: a map of <file> -> <string> for all files to have the given name in maven.
"""
_maven_library(
name = name,
files = files,
**kwargs
)