blob: 7cba8f5c6c84d3d77cbc0e37215d61ef7c83c44e [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.
"""Rules for importing and registering a local JDK."""
load("//java:defs.bzl", "java_runtime")
load(":default_java_toolchain.bzl", "default_java_toolchain")
def _detect_java_version(repository_ctx, java_bin):
properties_out = repository_ctx.execute([java_bin, "-XshowSettings:properties"]).stderr
# This returns an indented list of properties separated with newlines:
# " java.vendor.url.bug = ... \n"
# " java.version = 11.0.8\n"
# " java.version.date = 2020-11-05\"
strip_properties = [property.strip() for property in properties_out.splitlines()]
version_property = [property for property in strip_properties if property.startswith("java.version = ")]
if len(version_property) != 1:
return None
version_value = version_property[0][len("java.version = "):]
parts = version_value.split(".")
major = parts[0]
if len(parts) == 1:
return major
elif major == "1": # handles versions below 1.8
minor = parts[1]
return minor
return major
def local_java_runtime(name, java_home, version, runtime_name = None, visibility = ["//visibility:public"], exec_compatible_with = [], target_compatible_with = []):
"""Defines a java_runtime target together with Java runtime and compile toolchain definitions.
Java runtime toolchain is constrained by flag --java_runtime_version having
value set to either name or version argument.
Java compile toolchains are created for --java_language_version flags values
between 8 and version (inclusive). Java compile toolchains use the same
(local) JDK for compilation. This requires a different configuration for JDK8
than the newer versions.
Args:
name: name of the target.
java_home: Path to the JDK.
version: Version of the JDK.
runtime_name: name of java_runtime target if it already exists.
visibility: Visibility that will be applied to the java runtime target
exec_compatible_with: A list of constraint values that must be
satisfied for the exec platform.
target_compatible_with: A list of constraint values that must be
satisfied for the target platform.
"""
if runtime_name == None:
runtime_name = name
java_runtime(
name = runtime_name,
java_home = java_home,
visibility = visibility,
version = int(version) if version.isdigit() else 0,
)
native.config_setting(
name = name + "_name_setting",
values = {"java_runtime_version": name},
visibility = ["//visibility:private"],
)
native.config_setting(
name = name + "_version_setting",
values = {"java_runtime_version": version},
visibility = ["//visibility:private"],
)
native.config_setting(
name = name + "_name_version_setting",
values = {"java_runtime_version": name + "_" + version},
visibility = ["//visibility:private"],
)
native.alias(
name = name + "_settings_alias",
actual = select({
name + "_name_setting": name + "_name_setting",
name + "_version_setting": name + "_version_setting",
"//conditions:default": name + "_name_version_setting",
}),
visibility = ["//visibility:private"],
)
native.toolchain(
name = "runtime_toolchain_definition",
target_settings = [":%s_settings_alias" % name],
toolchain_type = Label("@bazel_tools//tools/jdk:runtime_toolchain_type"),
toolchain = runtime_name,
)
if type(version) == type("") and version.isdigit() and int(version) > 8:
for version in range(8, int(version) + 1):
default_java_toolchain(
name = name + "_toolchain_java" + str(version),
source_version = str(version),
target_version = str(version),
java_runtime = runtime_name,
exec_compatible_with = exec_compatible_with,
target_compatible_with = target_compatible_with,
)
# else version is not recognized and no compilation toolchains are predefined
def _is_macos(repository_ctx):
return repository_ctx.os.name.lower().find("mac os x") != -1
def _is_windows(repository_ctx):
return repository_ctx.os.name.lower().find("windows") != -1
def _with_os_extension(repository_ctx, binary):
return binary + (".exe" if _is_windows(repository_ctx) else "")
def _determine_java_home(repository_ctx):
"""Determine the java home path.
If the `java_home` attribute is specified, then use the given path,
otherwise, try to detect the java home path on the system.
Args:
repository_ctx: repository context
"""
java_home = repository_ctx.attr.java_home
if java_home:
java_home_path = repository_ctx.path(java_home)
if not java_home_path.exists:
fail('The path indicated by the "java_home" attribute "%s" (absolute: "%s") does not exist.' % (java_home, str(java_home_path)))
return java_home_path
if "JAVA_HOME" in repository_ctx.os.environ:
return repository_ctx.path(repository_ctx.os.environ["JAVA_HOME"])
if _is_macos(repository_ctx):
# Replicate GetSystemJavabase() in src/main/cpp/blaze_util_darwin.cc
result = repository_ctx.execute(["/usr/libexec/java_home", "-v", "1.11+"])
if result.return_code == 0:
return repository_ctx.path(result.stdout.strip())
else:
# Calculate java home by locating the javac binary
# javac should exists at ${JAVA_HOME}/bin/javac
# Replicate GetSystemJavabase() in src/main/cpp/blaze_util_linux.cc
# This logic should also work on Windows.
javac_path = repository_ctx.which(_with_os_extension(repository_ctx, "javac"))
if javac_path:
return javac_path.realpath.dirname.dirname
return repository_ctx.path("./nosystemjdk")
def _local_java_repository_impl(repository_ctx):
"""Repository rule local_java_repository implementation.
Args:
repository_ctx: repository context
"""
java_home = _determine_java_home(repository_ctx)
# When Bzlmod is enabled, the Java runtime name should be the last segment of the repository name.
local_java_runtime_name = repository_ctx.name.split("~")[-1]
repository_ctx.file(
"WORKSPACE",
"# DO NOT EDIT: automatically generated WORKSPACE file for local_java_repository\n" +
"workspace(name = \"{name}\")\n".format(name = local_java_runtime_name),
)
java_bin = java_home.get_child("bin").get_child(_with_os_extension(repository_ctx, "java"))
if not java_bin.exists:
# Java binary does not exist
repository_ctx.file(
"BUILD.bazel",
_NOJDK_BUILD_TPL.format(
local_jdk = local_java_runtime_name,
java_binary = _with_os_extension(repository_ctx, "bin/java"),
java_home = java_home,
),
False,
)
return
# Detect version
version = repository_ctx.attr.version if repository_ctx.attr.version != "" else _detect_java_version(repository_ctx, java_bin)
# Prepare BUILD file using "local_java_runtime" macro
if repository_ctx.attr.build_file_content and repository_ctx.attr.build_file:
fail("build_file and build_file_content are exclusive")
if repository_ctx.attr.build_file_content:
build_file = repository_ctx.attr.build_file_content
elif repository_ctx.attr.build_file:
build_file = repository_ctx.read(repository_ctx.path(repository_ctx.attr.build_file))
else:
build_file = ""
build_file = build_file.format(RUNTIME_VERSION = version if version.isdigit() else "0")
runtime_name = '"jdk"' if build_file else None
local_java_runtime_macro = """
local_java_runtime(
name = "%s",
runtime_name = %s,
java_home = "%s",
version = "%s",
)
""" % (local_java_runtime_name, runtime_name, java_home, version)
repository_ctx.file(
"BUILD.bazel",
'load("@rules_java//toolchains:local_java_repository.bzl", "local_java_runtime")\n' +
build_file +
local_java_runtime_macro,
)
# Symlink all files
for file in repository_ctx.path(java_home).readdir():
repository_ctx.symlink(file, file.basename)
# Build file template, when JDK does not exist
_NOJDK_BUILD_TPL = '''load("@rules_java//toolchains:fail_rule.bzl", "fail_rule")
fail_rule(
name = "jdk",
header = "Auto-Configuration Error:",
message = ("Cannot find Java binary {java_binary} in {java_home}; either correct your JAVA_HOME, " +
"PATH or specify Java from remote repository (e.g. " +
"--java_runtime_version=remotejdk_11)")
)
config_setting(
name = "localjdk_setting",
values = {{"java_runtime_version": "{local_jdk}"}},
visibility = ["//visibility:private"],
)
toolchain(
name = "runtime_toolchain_definition",
target_settings = [":localjdk_setting"],
toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type",
toolchain = ":jdk",
)
'''
_local_java_repository_rule = repository_rule(
implementation = _local_java_repository_impl,
local = True,
configure = True,
environ = ["JAVA_HOME"],
attrs = {
"build_file": attr.label(),
"build_file_content": attr.string(),
"java_home": attr.string(),
"version": attr.string(),
},
)
def local_java_repository(name, java_home = "", version = "", build_file = None, build_file_content = None):
"""Registers a runtime toolchain for local JDK and creates an unregistered compile toolchain.
Toolchain resolution is constrained with --java_runtime_version flag
having value of the "name" or "version" parameter.
Java compile toolchains are created for --java_language_version flags values
between 8 and version (inclusive). Java compile toolchains use the same
(local) JDK for compilation.
If there is no JDK "virtual" targets are created, which fail only when actually needed.
Args:
name: A unique name for this rule.
java_home: Location of the JDK imported.
build_file: optionally BUILD file template
build_file_content: optional BUILD file template as a string
version: optionally java version
"""
_local_java_repository_rule(name = name, java_home = java_home, version = version, build_file = build_file, build_file_content = build_file_content)