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 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 by the exec platform for the Java compile 61 toolchain to be selected. They must be satisfied by 62 the target platform for the Java runtime toolchain 63 to be selected. 64 target_compatible_with: A list of constraint values that must be 65 satisfied by the target platform for the Java 66 compile toolchain to be selected. 67 """ 68 69 if runtime_name == None: 70 runtime_name = name 71 java_runtime( 72 name = runtime_name, 73 java_home = java_home, 74 visibility = visibility, 75 version = int(version) if version.isdigit() else 0, 76 ) 77 78 native.config_setting( 79 name = name + "_name_setting", 80 values = {"java_runtime_version": name}, 81 visibility = ["//visibility:private"], 82 ) 83 native.config_setting( 84 name = name + "_version_setting", 85 values = {"java_runtime_version": version}, 86 visibility = ["//visibility:private"], 87 ) 88 native.config_setting( 89 name = name + "_name_version_setting", 90 values = {"java_runtime_version": name + "_" + version}, 91 visibility = ["//visibility:private"], 92 ) 93 native.alias( 94 name = name + "_settings_alias", 95 actual = select({ 96 name + "_name_setting": name + "_name_setting", 97 name + "_version_setting": name + "_version_setting", 98 "//conditions:default": name + "_name_version_setting", 99 }), 100 visibility = ["//visibility:private"], 101 ) 102 native.toolchain( 103 name = "runtime_toolchain_definition", 104 # A JDK can be used as a runtime *for* the platforms it can be used to compile *on*. 105 target_compatible_with = exec_compatible_with, 106 target_settings = [":%s_settings_alias" % name], 107 toolchain_type = Label("@bazel_tools//tools/jdk:runtime_toolchain_type"), 108 toolchain = runtime_name, 109 ) 110 native.toolchain( 111 name = "bootstrap_runtime_toolchain_definition", 112 target_settings = [":%s_settings_alias" % name], 113 toolchain_type = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type"), 114 toolchain = runtime_name, 115 ) 116 117 if type(version) == type("") and version.isdigit() and int(version) > 8: 118 for version in range(8, int(version) + 1): 119 default_java_toolchain( 120 name = name + "_toolchain_java" + str(version), 121 source_version = str(version), 122 target_version = str(version), 123 java_runtime = runtime_name, 124 exec_compatible_with = exec_compatible_with, 125 target_compatible_with = target_compatible_with, 126 ) 127 128 # else version is not recognized and no compilation toolchains are predefined 129 130def _is_macos(repository_ctx): 131 return repository_ctx.os.name.lower().find("mac os x") != -1 132 133def _is_windows(repository_ctx): 134 return repository_ctx.os.name.lower().find("windows") != -1 135 136def _with_os_extension(repository_ctx, binary): 137 return binary + (".exe" if _is_windows(repository_ctx) else "") 138 139def _determine_java_home(repository_ctx): 140 """Determine the java home path. 141 142 If the `java_home` attribute is specified, then use the given path, 143 otherwise, try to detect the java home path on the system. 144 145 Args: 146 repository_ctx: repository context 147 """ 148 java_home = repository_ctx.attr.java_home 149 if java_home: 150 java_home_path = repository_ctx.path(java_home) 151 if not java_home_path.exists: 152 fail('The path indicated by the "java_home" attribute "%s" (absolute: "%s") does not exist.' % (java_home, str(java_home_path))) 153 return java_home_path 154 if "JAVA_HOME" in repository_ctx.os.environ: 155 return repository_ctx.path(repository_ctx.os.environ["JAVA_HOME"]) 156 157 if _is_macos(repository_ctx): 158 # Replicate GetSystemJavabase() in src/main/cpp/blaze_util_darwin.cc 159 result = repository_ctx.execute(["/usr/libexec/java_home", "-v", "1.11+"]) 160 if result.return_code == 0: 161 return repository_ctx.path(result.stdout.strip()) 162 else: 163 # Calculate java home by locating the javac binary 164 # javac should exists at ${JAVA_HOME}/bin/javac 165 # Replicate GetSystemJavabase() in src/main/cpp/blaze_util_linux.cc 166 # This logic should also work on Windows. 167 javac_path = repository_ctx.which(_with_os_extension(repository_ctx, "javac")) 168 if javac_path: 169 return javac_path.realpath.dirname.dirname 170 return repository_ctx.path("./nosystemjdk") 171 172def _local_java_repository_impl(repository_ctx): 173 """Repository rule local_java_repository implementation. 174 175 Args: 176 repository_ctx: repository context 177 """ 178 179 java_home = _determine_java_home(repository_ctx) 180 181 # When Bzlmod is enabled, the Java runtime name should be the last segment of the repository name. 182 local_java_runtime_name = repository_ctx.name.split("~")[-1] 183 184 repository_ctx.file( 185 "WORKSPACE", 186 "# DO NOT EDIT: automatically generated WORKSPACE file for local_java_repository\n" + 187 "workspace(name = \"{name}\")\n".format(name = local_java_runtime_name), 188 ) 189 190 java_bin = java_home.get_child("bin").get_child(_with_os_extension(repository_ctx, "java")) 191 192 if not java_bin.exists: 193 # Java binary does not exist 194 _create_auto_config_error_build_file( 195 repository_ctx, 196 local_java_runtime_name = local_java_runtime_name, 197 java_home = java_home, 198 message = "Cannot find Java binary {java_binary} in {java_home}; " + 199 "either correct your JAVA_HOME, PATH or specify Java from " + 200 "remote repository (e.g. --java_runtime_version=remotejdk_11)", 201 ) 202 return 203 204 # Detect version 205 version = repository_ctx.attr.version if repository_ctx.attr.version != "" else _detect_java_version(repository_ctx, java_bin) 206 if version == None: 207 # Java version could not be detected 208 _create_auto_config_error_build_file( 209 repository_ctx, 210 local_java_runtime_name = local_java_runtime_name, 211 java_home = java_home, 212 message = "Cannot detect Java version of {java_binary} in {java_home}; " + 213 "make sure it points to a valid Java executable", 214 ) 215 return 216 217 # Prepare BUILD file using "local_java_runtime" macro 218 if repository_ctx.attr.build_file_content and repository_ctx.attr.build_file: 219 fail("build_file and build_file_content are exclusive") 220 if repository_ctx.attr.build_file_content: 221 build_file = repository_ctx.attr.build_file_content 222 elif repository_ctx.attr.build_file: 223 build_file = repository_ctx.read(repository_ctx.path(repository_ctx.attr.build_file)) 224 else: 225 build_file = "" 226 build_file = build_file.format(RUNTIME_VERSION = version if version.isdigit() else "0") 227 228 runtime_name = '"jdk"' if build_file else None 229 local_java_runtime_macro = """ 230local_java_runtime( 231 name = "%s", 232 runtime_name = %s, 233 java_home = "%s", 234 version = "%s", 235 exec_compatible_with = HOST_CONSTRAINTS, 236) 237""" % (local_java_runtime_name, runtime_name, java_home, version) 238 239 repository_ctx.file( 240 "BUILD.bazel", 241 'load("@rules_java//toolchains:local_java_repository.bzl", "local_java_runtime")\n' + 242 'load("@local_config_platform//:constraints.bzl", "HOST_CONSTRAINTS")\n' + 243 build_file + 244 local_java_runtime_macro, 245 ) 246 247 # Symlink all files 248 for file in repository_ctx.path(java_home).readdir(): 249 repository_ctx.symlink(file, file.basename) 250 251# Build file template, when JDK could not be detected 252_AUTO_CONFIG_ERROR_BUILD_TPL = '''load("@rules_java//toolchains:fail_rule.bzl", "fail_rule") 253fail_rule( 254 name = "jdk", 255 header = "Auto-Configuration Error:", 256 message = {message}, 257) 258config_setting( 259 name = "localjdk_setting", 260 values = {{"java_runtime_version": "{local_jdk}"}}, 261 visibility = ["//visibility:private"], 262) 263toolchain( 264 name = "runtime_toolchain_definition", 265 target_settings = [":localjdk_setting"], 266 toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type", 267 toolchain = ":jdk", 268) 269toolchain( 270 name = "bootstrap_runtime_toolchain_definition", 271 target_settings = [":localjdk_setting"], 272 toolchain_type = "@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type", 273 toolchain = ":jdk", 274) 275''' 276 277def _create_auto_config_error_build_file(repository_ctx, *, local_java_runtime_name, java_home, message): 278 repository_ctx.file( 279 "BUILD.bazel", 280 _AUTO_CONFIG_ERROR_BUILD_TPL.format( 281 local_jdk = local_java_runtime_name, 282 message = repr(message.format( 283 java_binary = _with_os_extension(repository_ctx, "bin/java"), 284 java_home = java_home, 285 )), 286 ), 287 False, 288 ) 289 290_local_java_repository_rule = repository_rule( 291 implementation = _local_java_repository_impl, 292 local = True, 293 configure = True, 294 environ = ["JAVA_HOME"], 295 attrs = { 296 "build_file": attr.label(), 297 "build_file_content": attr.string(), 298 "java_home": attr.string(), 299 "version": attr.string(), 300 }, 301) 302 303def local_java_repository(name, java_home = "", version = "", build_file = None, build_file_content = None, **kwargs): 304 """Defines runtime and compile toolchains for a local JDK. 305 306 Register the toolchains defined by this macro as follows (where `<name>` is the value of the 307 `name` parameter): 308 * Runtime toolchains only (recommended) 309 ``` 310 register_toolchains("@<name>//:runtime_toolchain_definition") 311 register_toolchains("@<name>//:bootstrap_runtime_toolchain_definition") 312 ``` 313 * Runtime and compilation toolchains: 314 ``` 315 register_toolchains("@<name>//:all") 316 ``` 317 318 Toolchain resolution is constrained with --java_runtime_version flag 319 having value of the "name" or "version" parameter. 320 321 Java compile toolchains are created for --java_language_version flags values 322 between 8 and version (inclusive). Java compile toolchains use the same 323 (local) JDK for compilation. 324 325 If there is no JDK "virtual" targets are created, which fail only when actually needed. 326 327 Args: 328 name: A unique name for this rule. 329 java_home: Location of the JDK imported. 330 build_file: optionally BUILD file template 331 build_file_content: optional BUILD file template as a string 332 version: optionally java version 333 **kwargs: additional arguments for repository rule 334 """ 335 _local_java_repository_rule(name = name, java_home = java_home, version = version, build_file = build_file, build_file_content = build_file_content, **kwargs) 336