blob: 7ba8065c396bf4d6be6a56c891cab336d6fdce3b [file] [log] [blame]
# Copyright 2021 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
# limitations under the License.
"""
Definition of java_import rule.
"""
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("//java/common:java_common.bzl", "java_common")
load("//java/common:java_info.bzl", "JavaInfo")
load("//java/common:java_semantics.bzl", "semantics")
load(":basic_java_library.bzl", "construct_defaultinfo")
load(":import_deps_check.bzl", "import_deps_check")
load(":proguard_validation.bzl", "validate_proguard_specs")
visibility(["//java/..."])
_java_common_internal = java_common.internal_DO_NOT_USE()
_run_ijar_private_for_builtins = _java_common_internal.run_ijar_private_for_builtins
def _filter_provider(provider, *attrs):
return [dep[provider] for attr in attrs for dep in attr if provider in dep]
def _collect_jars(ctx, jars):
jars_dict = {}
for info in jars:
if JavaInfo in info:
fail("'jars' attribute cannot contain labels of Java targets")
for jar in info.files.to_list():
jar_path = jar.dirname + jar.basename
if jars_dict.get(jar_path) != None:
fail("in jars attribute of java_import rule //" + ctx.label.package + ":" + ctx.attr.name + ": " + jar.basename + " is a duplicate")
jars_dict[jar_path] = jar
return [jar_tuple[1] for jar_tuple in jars_dict.items()] if len(jars_dict.items()) > 0 else []
def _process_with_ijars_if_needed(jars, ctx):
file_dict = {}
use_ijars = ctx.fragments.java.use_ijars()
for jar in jars:
interface_jar = jar
if use_ijars:
ijar_basename = jar.short_path.removeprefix("../").removesuffix("." + jar.extension) + "-ijar.jar"
interface_jar_directory = "_ijar/" + ctx.label.name + "/" + ijar_basename
interface_jar = ctx.actions.declare_file(interface_jar_directory)
_run_ijar_private_for_builtins(
ctx.actions,
target_label = ctx.label,
jar = jar,
output = interface_jar,
java_toolchain = semantics.find_java_toolchain(ctx),
)
file_dict[jar] = interface_jar
return file_dict
def _check_export_error(ctx, exports):
not_in_allowlist = hasattr(ctx.attr, "_allowlist_java_import_exports") and not getattr(ctx.attr, "_allowlist_java_import_exports")[PackageSpecificationInfo].contains(ctx.label)
disallow_java_import_exports = ctx.fragments.java.disallow_java_import_exports()
if len(exports) != 0 and (disallow_java_import_exports or not_in_allowlist):
fail("java_import.exports is no longer supported; use java_import.deps instead")
def _check_empty_jars_error(ctx, jars):
# TODO(kotlaja): Remove temporary incompatible flag [disallow_java_import_empty_jars] once migration is done.
not_in_allowlist = hasattr(ctx.attr, "_allowlist_java_import_empty_jars") and not getattr(ctx.attr, "_allowlist_java_import_empty_jars")[PackageSpecificationInfo].contains(ctx.label)
disallow_java_import_empty_jars = ctx.fragments.java.disallow_java_import_empty_jars()
if len(jars) == 0 and disallow_java_import_empty_jars and not_in_allowlist:
fail("empty java_import.jars is no longer supported " + ctx.label.package)
def _create_java_info_with_dummy_output_file(ctx, srcjar, all_deps, exports, runtime_deps_list, neverlink, cc_info_list, add_exports, add_opens):
dummy_jar = ctx.actions.declare_file(ctx.label.name + "_dummy.jar")
dummy_src_jar = srcjar
if dummy_src_jar == None:
dummy_src_jar = ctx.actions.declare_file(ctx.label.name + "_src_dummy.java")
ctx.actions.write(dummy_src_jar, "")
return java_common.compile(
ctx,
output = dummy_jar,
java_toolchain = semantics.find_java_toolchain(ctx),
source_files = [dummy_src_jar],
deps = all_deps,
runtime_deps = runtime_deps_list,
neverlink = neverlink,
exports = [export[JavaInfo] for export in exports if JavaInfo in export], # Watchout, maybe you need to add them there manually.
native_libraries = cc_info_list,
add_exports = add_exports,
add_opens = add_opens,
)
def bazel_java_import_rule(
ctx,
jars = [],
srcjar = None,
deps = [],
runtime_deps = [],
exports = [],
neverlink = False,
proguard_specs = [],
add_exports = [],
add_opens = []):
"""Implements java_import.
This rule allows the use of precompiled .jar files as libraries in other Java rules.
Args:
ctx: (RuleContext) Used to register the actions.
jars: (list[Artifact]) List of output jars.
srcjar: (Artifact) The jar containing the sources.
deps: (list[Target]) The list of dependent libraries.
runtime_deps: (list[Target]) Runtime dependencies to attach to the rule.
exports: (list[Target]) The list of exported libraries.
neverlink: (bool) Whether this rule should only be used for compilation and not at runtime.
proguard_specs: (list[File]) Files to be used as Proguard specification.
add_exports: (list[str]) Allow this library to access the given <module>/<package>.
add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
Returns:
(list[provider]) A list containing DefaultInfo, JavaInfo,
OutputGroupsInfo, ProguardSpecProvider providers.
"""
_check_empty_jars_error(ctx, jars)
_check_export_error(ctx, exports)
collected_jars = _collect_jars(ctx, jars)
all_deps = _filter_provider(JavaInfo, deps, exports)
jdeps_artifact = None
merged_java_info = java_common.merge(all_deps)
not_in_allowlist = hasattr(ctx.attr, "_allowlist_java_import_deps_checking") and not ctx.attr._allowlist_java_import_deps_checking[PackageSpecificationInfo].contains(ctx.label)
if len(collected_jars) > 0 and not_in_allowlist and "incomplete-deps" not in ctx.attr.tags:
jdeps_artifact = import_deps_check(
ctx,
collected_jars,
merged_java_info.compile_jars,
merged_java_info.transitive_compile_time_jars,
"java_import",
)
compilation_to_runtime_jar_map = _process_with_ijars_if_needed(collected_jars, ctx)
runtime_deps_list = [runtime_dep[JavaInfo] for runtime_dep in runtime_deps if JavaInfo in runtime_dep]
cc_info_list = [dep[CcInfo] for dep in deps if CcInfo in dep]
java_info = None
if len(collected_jars) > 0:
java_infos = []
for jar in collected_jars:
java_infos.append(JavaInfo(
output_jar = jar,
compile_jar = compilation_to_runtime_jar_map[jar],
deps = all_deps,
runtime_deps = runtime_deps_list,
neverlink = neverlink,
source_jar = srcjar,
exports = [export[JavaInfo] for export in exports if JavaInfo in export], # Watchout, maybe you need to add them there manually.
native_libraries = cc_info_list,
add_exports = add_exports,
add_opens = add_opens,
))
java_info = java_common.merge(java_infos)
else:
# TODO(kotlaja): Remove next line once all java_import targets with empty jars attribute are cleaned from depot (b/246559727).
java_info = _create_java_info_with_dummy_output_file(ctx, srcjar, all_deps, exports, runtime_deps_list, neverlink, cc_info_list, add_exports, add_opens)
target = {"JavaInfo": java_info}
target["ProguardSpecProvider"] = validate_proguard_specs(
ctx,
proguard_specs,
[deps, runtime_deps, exports],
)
# TODO(kotlaja): Revise if collected_runtimes can be added into construct_defaultinfo directly.
collected_runtimes = []
for runtime_dep in ctx.attr.runtime_deps:
collected_runtimes.extend(runtime_dep.files.to_list())
target["DefaultInfo"] = construct_defaultinfo(
ctx,
collected_jars,
collected_jars + collected_runtimes,
neverlink,
exports,
)
output_group_src_jars = depset() if srcjar == None else depset([srcjar])
target["OutputGroupInfo"] = OutputGroupInfo(
**{
"_source_jars": output_group_src_jars,
"_direct_source_jars": output_group_src_jars,
"_validation": depset() if jdeps_artifact == None else depset([jdeps_artifact]),
"_hidden_top_level_INTERNAL_": target["ProguardSpecProvider"].specs,
}
)
return target
def _proxy(ctx):
return bazel_java_import_rule(
ctx,
ctx.attr.jars,
ctx.file.srcjar,
ctx.attr.deps,
ctx.attr.runtime_deps,
ctx.attr.exports,
ctx.attr.neverlink,
ctx.files.proguard_specs,
ctx.attr.add_exports,
ctx.attr.add_opens,
).values()
_ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT = [
"java_library",
"java_import",
"cc_library",
"cc_binary",
]
# buildifier: disable=attr-licenses
JAVA_IMPORT_ATTRS = {
"data": attr.label_list(
allow_files = True,
flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
doc = """
The list of files needed by this rule at runtime.
""",
),
"deps": attr.label_list(
providers = [JavaInfo],
allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT,
doc = """
The list of other libraries to be linked in to the target.
See <a href="${link java_library.deps}">java_library.deps</a>.
""",
),
"exports": attr.label_list(
providers = [JavaInfo],
allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT,
doc = """
Targets to make available to users of this rule.
See <a href="${link java_library.exports}">java_library.exports</a>.
""",
),
"runtime_deps": attr.label_list(
allow_files = [".jar"],
allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT,
providers = [[CcInfo], [JavaInfo]],
flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"],
doc = """
Libraries to make available to the final binary or test at runtime only.
See <a href="${link java_library.runtime_deps}">java_library.runtime_deps</a>.
""",
),
# JavaImportBazeRule attr
"jars": attr.label_list(
allow_files = [".jar"],
mandatory = True,
doc = """
The list of JAR files provided to Java targets that depend on this target.
""",
),
"srcjar": attr.label(
allow_single_file = [".srcjar", ".jar"],
flags = ["DIRECT_COMPILE_TIME_INPUT"],
doc = """
A JAR file that contains source code for the compiled JAR files.
""",
),
"neverlink": attr.bool(
default = False,
doc = """
Only use this library for compilation and not at runtime.
Useful if the library will be provided by the runtime environment
during execution. Examples of libraries like this are IDE APIs
for IDE plug-ins or <code>tools.jar</code> for anything running on
a standard JDK.
""",
),
"constraints": attr.string_list(
doc = """
Extra constraints imposed on this rule as a Java library.
""",
),
# ProguardLibraryRule attr
"proguard_specs": attr.label_list(
allow_files = True,
doc = """
Files to be used as Proguard specification.
These will describe the set of specifications to be used by Proguard. If specified,
they will be added to any <code>android_binary</code> target depending on this library.
The files included here must only have idempotent rules, namely -dontnote, -dontwarn,
assumenosideeffects, and rules that start with -keep. Other options can only appear in
<code>android_binary</code>'s proguard_specs, to ensure non-tautological merges.
""",
),
# Additional attrs
"add_exports": attr.string_list(
doc = """
Allow this library to access the given <code>module</code> or <code>package</code>.
<p>
This corresponds to the javac and JVM --add-exports= flags.
""",
),
"add_opens": attr.string_list(
doc = """
Allow this library to reflectively access the given <code>module</code> or
<code>package</code>.
<p>
This corresponds to the javac and JVM --add-opens= flags.
""",
),
"licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
"_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE),
}
java_import = rule(
_proxy,
doc = """
<p>
This rule allows the use of precompiled <code>.jar</code> files as
libraries for <code><a href="${link java_library}">java_library</a></code> and
<code>java_binary</code> rules.
</p>
<h4 id="java_import_examples">Examples</h4>
<pre class="code">
<code class="lang-starlark">
java_import(
name = "maven_model",
jars = [
"maven_model/maven-aether-provider-3.2.3.jar",
"maven_model/maven-model-3.2.3.jar",
"maven_model/maven-model-builder-3.2.3.jar",
],
)
</code>
</pre>
""",
attrs = JAVA_IMPORT_ATTRS,
provides = [JavaInfo],
fragments = ["java", "cpp"],
toolchains = [semantics.JAVA_TOOLCHAIN],
)