• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Rules for importing and registering a local JDK."""
16
17load("//java:defs.bzl", "java_runtime")
18load(":default_java_toolchain.bzl", "default_java_toolchain")
19
20def _detect_java_version(repository_ctx, java_bin):
21    properties_out = repository_ctx.execute([java_bin, "-XshowSettings:properties"]).stderr
22    # This returns an indented list of properties separated with newlines:
23    # "  java.vendor.url.bug = ... \n"
24    # "  java.version = 11.0.8\n"
25    # "  java.version.date = 2020-11-05\"
26
27    strip_properties = [property.strip() for property in properties_out.splitlines()]
28    version_property = [property for property in strip_properties if property.startswith("java.version = ")]
29    if len(version_property) != 1:
30        return None
31
32    version_value = version_property[0][len("java.version = "):]
33    parts = version_value.split(".")
34    major = parts[0]
35    if len(parts) == 1:
36        return major
37    elif major == "1":  # handles versions below 1.8
38        minor = parts[1]
39        return minor
40    return major
41
42def local_java_runtime(name, java_home, version, runtime_name = None, visibility = ["//visibility:public"], exec_compatible_with = [], target_compatible_with = []):
43    """Defines a java_runtime target together with Java runtime and compile toolchain definitions.
44
45    Java runtime toolchain is constrained by flag --java_runtime_version having
46    value set to either name or version argument.
47
48    Java compile toolchains are created for --java_language_version flags values
49    between 8 and version (inclusive). Java compile toolchains use the same
50    (local) JDK for compilation. This requires a different configuration for JDK8
51    than the newer versions.
52
53    Args:
54      name: name of the target.
55      java_home: Path to the JDK.
56      version: Version of the JDK.
57      runtime_name: name of java_runtime target if it already exists.
58      visibility: Visibility that will be applied to the java runtime target
59      exec_compatible_with: A list of constraint values that must be
60                            satisfied for the exec platform.
61      target_compatible_with: A list of constraint values that must be
62                              satisfied for the target platform.
63    """
64
65    if runtime_name == None:
66        runtime_name = name
67        java_runtime(
68            name = runtime_name,
69            java_home = java_home,
70            visibility = visibility,
71            version = int(version) if version.isdigit() else 0,
72        )
73
74    native.config_setting(
75        name = name + "_name_setting",
76        values = {"java_runtime_version": name},
77        visibility = ["//visibility:private"],
78    )
79    native.config_setting(
80        name = name + "_version_setting",
81        values = {"java_runtime_version": version},
82        visibility = ["//visibility:private"],
83    )
84    native.config_setting(
85        name = name + "_name_version_setting",
86        values = {"java_runtime_version": name + "_" + version},
87        visibility = ["//visibility:private"],
88    )
89    native.alias(
90        name = name + "_settings_alias",
91        actual = select({
92            name + "_name_setting": name + "_name_setting",
93            name + "_version_setting": name + "_version_setting",
94            "//conditions:default": name + "_name_version_setting",
95        }),
96        visibility = ["//visibility:private"],
97    )
98    native.toolchain(
99        name = "runtime_toolchain_definition",
100        target_settings = [":%s_settings_alias" % name],
101        toolchain_type = Label("@bazel_tools//tools/jdk:runtime_toolchain_type"),
102        toolchain = runtime_name,
103    )
104
105    if type(version) == type("") and version.isdigit() and int(version) > 8:
106        for version in range(8, int(version) + 1):
107            default_java_toolchain(
108                name = name + "_toolchain_java" + str(version),
109                source_version = str(version),
110                target_version = str(version),
111                java_runtime = runtime_name,
112                exec_compatible_with = exec_compatible_with,
113                target_compatible_with = target_compatible_with,
114            )
115
116    # else version is not recognized and no compilation toolchains are predefined
117
118def _is_macos(repository_ctx):
119    return repository_ctx.os.name.lower().find("mac os x") != -1
120
121def _is_windows(repository_ctx):
122    return repository_ctx.os.name.lower().find("windows") != -1
123
124def _with_os_extension(repository_ctx, binary):
125    return binary + (".exe" if _is_windows(repository_ctx) else "")
126
127def _determine_java_home(repository_ctx):
128    """Determine the java home path.
129
130    If the `java_home` attribute is specified, then use the given path,
131    otherwise, try to detect the java home path on the system.
132
133    Args:
134      repository_ctx: repository context
135    """
136    java_home = repository_ctx.attr.java_home
137    if java_home:
138        java_home_path = repository_ctx.path(java_home)
139        if not java_home_path.exists:
140            fail('The path indicated by the "java_home" attribute "%s" (absolute: "%s") does not exist.' % (java_home, str(java_home_path)))
141        return java_home_path
142    if "JAVA_HOME" in repository_ctx.os.environ:
143        return repository_ctx.path(repository_ctx.os.environ["JAVA_HOME"])
144
145    if _is_macos(repository_ctx):
146        # Replicate GetSystemJavabase() in src/main/cpp/blaze_util_darwin.cc
147        result = repository_ctx.execute(["/usr/libexec/java_home", "-v", "1.11+"])
148        if result.return_code == 0:
149            return repository_ctx.path(result.stdout.strip())
150    else:
151        # Calculate java home by locating the javac binary
152        # javac should exists at ${JAVA_HOME}/bin/javac
153        # Replicate GetSystemJavabase() in src/main/cpp/blaze_util_linux.cc
154        # This logic should also work on Windows.
155        javac_path = repository_ctx.which(_with_os_extension(repository_ctx, "javac"))
156        if javac_path:
157            return javac_path.realpath.dirname.dirname
158    return repository_ctx.path("./nosystemjdk")
159
160def _local_java_repository_impl(repository_ctx):
161    """Repository rule local_java_repository implementation.
162
163    Args:
164      repository_ctx: repository context
165    """
166
167    java_home = _determine_java_home(repository_ctx)
168
169    # When Bzlmod is enabled, the Java runtime name should be the last segment of the repository name.
170    local_java_runtime_name = repository_ctx.name.split("~")[-1]
171
172    repository_ctx.file(
173        "WORKSPACE",
174        "# DO NOT EDIT: automatically generated WORKSPACE file for local_java_repository\n" +
175        "workspace(name = \"{name}\")\n".format(name = local_java_runtime_name),
176    )
177
178    java_bin = java_home.get_child("bin").get_child(_with_os_extension(repository_ctx, "java"))
179
180    if not java_bin.exists:
181        # Java binary does not exist
182        repository_ctx.file(
183            "BUILD.bazel",
184            _NOJDK_BUILD_TPL.format(
185                local_jdk = local_java_runtime_name,
186                java_binary = _with_os_extension(repository_ctx, "bin/java"),
187                java_home = java_home,
188            ),
189            False,
190        )
191        return
192
193    # Detect version
194    version = repository_ctx.attr.version if repository_ctx.attr.version != "" else _detect_java_version(repository_ctx, java_bin)
195
196    # Prepare BUILD file using "local_java_runtime" macro
197    if repository_ctx.attr.build_file_content and repository_ctx.attr.build_file:
198        fail("build_file and build_file_content are exclusive")
199    if repository_ctx.attr.build_file_content:
200        build_file = repository_ctx.attr.build_file_content
201    elif repository_ctx.attr.build_file:
202        build_file = repository_ctx.read(repository_ctx.path(repository_ctx.attr.build_file))
203    else:
204        build_file = ""
205    build_file = build_file.format(RUNTIME_VERSION = version if version.isdigit() else "0")
206
207    runtime_name = '"jdk"' if build_file else None
208    local_java_runtime_macro = """
209local_java_runtime(
210    name = "%s",
211    runtime_name = %s,
212    java_home = "%s",
213    version = "%s",
214)
215""" % (local_java_runtime_name, runtime_name, java_home, version)
216
217    repository_ctx.file(
218        "BUILD.bazel",
219        'load("@rules_java//toolchains:local_java_repository.bzl", "local_java_runtime")\n' +
220        build_file +
221        local_java_runtime_macro,
222    )
223
224    # Symlink all files
225    for file in repository_ctx.path(java_home).readdir():
226        repository_ctx.symlink(file, file.basename)
227
228# Build file template, when JDK does not exist
229_NOJDK_BUILD_TPL = '''load("@rules_java//toolchains:fail_rule.bzl", "fail_rule")
230fail_rule(
231   name = "jdk",
232   header = "Auto-Configuration Error:",
233   message = ("Cannot find Java binary {java_binary} in {java_home}; either correct your JAVA_HOME, " +
234          "PATH or specify Java from remote repository (e.g. " +
235          "--java_runtime_version=remotejdk_11)")
236)
237config_setting(
238   name = "localjdk_setting",
239   values = {{"java_runtime_version": "{local_jdk}"}},
240   visibility = ["//visibility:private"],
241)
242toolchain(
243   name = "runtime_toolchain_definition",
244   target_settings = [":localjdk_setting"],
245   toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type",
246   toolchain = ":jdk",
247)
248'''
249
250_local_java_repository_rule = repository_rule(
251    implementation = _local_java_repository_impl,
252    local = True,
253    configure = True,
254    environ = ["JAVA_HOME"],
255    attrs = {
256        "build_file": attr.label(),
257        "build_file_content": attr.string(),
258        "java_home": attr.string(),
259        "version": attr.string(),
260    },
261)
262
263def local_java_repository(name, java_home = "", version = "", build_file = None, build_file_content = None, **kwargs):
264    """Registers a runtime toolchain for local JDK and creates an unregistered compile toolchain.
265
266    Toolchain resolution is constrained with --java_runtime_version flag
267    having value of the "name" or "version" parameter.
268
269    Java compile toolchains are created for --java_language_version flags values
270    between 8 and version (inclusive). Java compile toolchains use the same
271    (local) JDK for compilation.
272
273    If there is no JDK "virtual" targets are created, which fail only when actually needed.
274
275    Args:
276      name: A unique name for this rule.
277      java_home: Location of the JDK imported.
278      build_file: optionally BUILD file template
279      build_file_content: optional BUILD file template as a string
280      version: optionally java version
281      **kwargs: additional arguments for repository rule
282    """
283    _local_java_repository_rule(name = name, java_home = java_home, version = version, build_file = build_file, build_file_content = build_file_content, **kwargs)
284