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/toolchains:java_runtime.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 local_java_runtime_name = repository_ctx.attr.runtime_name 182 183 repository_ctx.file( 184 "WORKSPACE", 185 "# DO NOT EDIT: automatically generated WORKSPACE file for local_java_repository\n" + 186 "workspace(name = \"{name}\")\n".format(name = local_java_runtime_name), 187 ) 188 189 java_bin = java_home.get_child("bin").get_child(_with_os_extension(repository_ctx, "java")) 190 191 if not java_bin.exists: 192 # Java binary does not exist 193 _create_auto_config_error_build_file( 194 repository_ctx, 195 local_java_runtime_name = local_java_runtime_name, 196 java_home = java_home, 197 message = "Cannot find Java binary {java_binary} in {java_home}; " + 198 "either correct your JAVA_HOME, PATH or specify Java from " + 199 "remote repository (e.g. --java_runtime_version=remotejdk_11)", 200 ) 201 return 202 203 # Detect version 204 version = repository_ctx.attr.version if repository_ctx.attr.version != "" else _detect_java_version(repository_ctx, java_bin) 205 if version == None: 206 # Java version could not be detected 207 _create_auto_config_error_build_file( 208 repository_ctx, 209 local_java_runtime_name = local_java_runtime_name, 210 java_home = java_home, 211 message = "Cannot detect Java version of {java_binary} in {java_home}; " + 212 "make sure it points to a valid Java executable", 213 ) 214 return 215 216 # Prepare BUILD file using "local_java_runtime" macro 217 if repository_ctx.attr.build_file_content and repository_ctx.attr.build_file: 218 fail("build_file and build_file_content are exclusive") 219 if repository_ctx.attr.build_file_content: 220 build_file = repository_ctx.attr.build_file_content 221 elif repository_ctx.attr.build_file: 222 build_file = repository_ctx.read(repository_ctx.path(repository_ctx.attr.build_file)) 223 else: 224 build_file = "" 225 build_file = build_file.format(RUNTIME_VERSION = version if version.isdigit() else "0") 226 227 runtime_name = '"jdk"' if build_file else None 228 local_java_runtime_macro = """ 229local_java_runtime( 230 name = "%s", 231 runtime_name = %s, 232 java_home = "%s", 233 version = "%s", 234 exec_compatible_with = HOST_CONSTRAINTS, 235) 236""" % (local_java_runtime_name, runtime_name, java_home, version) 237 238 repository_ctx.file( 239 "BUILD.bazel", 240 'load("@rules_java//toolchains:local_java_repository.bzl", "local_java_runtime")\n' + 241 'load("@local_config_platform//:constraints.bzl", "HOST_CONSTRAINTS")\n' + 242 build_file + 243 local_java_runtime_macro, 244 ) 245 246 # Symlink all files 247 for file in repository_ctx.path(java_home).readdir(): 248 repository_ctx.symlink(file, file.basename) 249 250# Build file template, when JDK could not be detected 251_AUTO_CONFIG_ERROR_BUILD_TPL = '''load("@rules_java//toolchains:fail_rule.bzl", "fail_rule") 252fail_rule( 253 name = "jdk", 254 header = "Auto-Configuration Error:", 255 message = {message}, 256) 257config_setting( 258 name = "localjdk_setting", 259 values = {{"java_runtime_version": "{local_jdk}"}}, 260 visibility = ["//visibility:private"], 261) 262toolchain( 263 name = "runtime_toolchain_definition", 264 target_settings = [":localjdk_setting"], 265 toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type", 266 toolchain = ":jdk", 267) 268toolchain( 269 name = "bootstrap_runtime_toolchain_definition", 270 target_settings = [":localjdk_setting"], 271 toolchain_type = "@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type", 272 toolchain = ":jdk", 273) 274''' 275 276def _create_auto_config_error_build_file(repository_ctx, *, local_java_runtime_name, java_home, message): 277 repository_ctx.file( 278 "BUILD.bazel", 279 _AUTO_CONFIG_ERROR_BUILD_TPL.format( 280 local_jdk = local_java_runtime_name, 281 message = repr(message.format( 282 java_binary = _with_os_extension(repository_ctx, "bin/java"), 283 java_home = java_home, 284 )), 285 ), 286 False, 287 ) 288 289_local_java_repository_rule = repository_rule( 290 implementation = _local_java_repository_impl, 291 local = True, 292 configure = True, 293 environ = ["JAVA_HOME"], 294 attrs = { 295 "runtime_name": attr.string(), 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, runtime_name = name, java_home = java_home, version = version, build_file = build_file, build_file_content = build_file_content, **kwargs) 336