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