1# Copyright 2018 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"""Bazel Java APIs for the Android rules.""" 16 17load(":path.bzl", _path = "path") 18load(":utils.bzl", "log") 19 20_ANDROID_CONSTRAINT_MISSING_ERROR = ( 21 "A list of constraints provided without the 'android' constraint." 22) 23 24# TODO(b/283499746): Reduce singlejar memory if possible. 25_SINGLEJAR_MEMORY_FOR_DEPLOY_JAR_MB = 1600 26 27def _segment_idx(path_segments): 28 """Finds the index of the segment in the path that preceeds the source root. 29 30 Args: 31 path_segments: A list of strings, where each string is the segment of a 32 filesystem path. 33 34 Returns: 35 An index to the path segment that represents the Java segment or -1 if 36 none found. 37 """ 38 if _path.is_absolute(path_segments[0]): 39 log.error("path must not be absolute: %s" % _path.join(path_segments)) 40 41 root_idx = -1 42 for idx, segment in enumerate(path_segments): 43 if segment in ["java", "javatests", "src", "testsrc"]: 44 root_idx = idx 45 break 46 if root_idx < 0: 47 return root_idx 48 49 is_src = path_segments[root_idx] == "src" 50 check_maven_idx = root_idx if is_src else -1 51 if root_idx == 0 or is_src: 52 # Check for a nested root directory. 53 for idx in range(root_idx + 1, len(path_segments) - 2): 54 segment = path_segments[idx] 55 if segment == "src" or (is_src and segment in ["java", "javatests"]): 56 next_segment = path_segments[idx + 1] 57 if next_segment in ["com", "org", "net"]: 58 root_idx = idx 59 elif segment == "src": 60 check_maven_idx = idx 61 break 62 63 if check_maven_idx >= 0 and check_maven_idx + 2 < len(path_segments): 64 next_segment = path_segments[check_maven_idx + 1] 65 if next_segment in ["main", "test"]: 66 next_segment = path_segments[check_maven_idx + 2] 67 if next_segment in ["java", "resources"]: 68 root_idx = check_maven_idx + 2 69 return root_idx 70 71def _resolve_package(path): 72 """Determines the Java package name from the given path. 73 74 Examples: 75 "{workspace}/java/foo/bar/wiz" -> "foo.bar.wiz" 76 "{workspace}/javatests/foo/bar/wiz" -> "foo.bar.wiz" 77 78 Args: 79 path: A string, representing a file path. 80 81 Returns: 82 A string representing a Java package name or None if could not be 83 determined. 84 """ 85 path_segments = _path.split(path.partition(":")[0]) 86 java_idx = _segment_idx(path_segments) 87 if java_idx < 0: 88 return None 89 else: 90 return ".".join(path_segments[java_idx + 1:]) 91 92def _resolve_package_from_label( 93 label, 94 custom_package = None): 95 """Resolves the Java package from a Label. 96 97 When no legal Java package can be resolved from the label, None will be 98 returned unless fallback is specified. 99 100 When a fallback is requested, a not safe for Java compilation package will 101 be returned. The fallback value will be derrived by taking the label.package 102 and replacing all path separators with ".". 103 """ 104 if custom_package: 105 return custom_package 106 107 # For backwards compatibility, also include directories 108 # from the label's name 109 # Ex: "//foo/bar:java/com/google/baz" is a legal one and 110 # results in "com.google" 111 label_path = _path.join( 112 [label.package] + 113 _path.split(label.name)[:-1], 114 ) 115 return _resolve_package(label_path) 116 117def _root(path): 118 """Determines the Java root from the given path. 119 120 Examples: 121 "{workspace}/java/foo/bar/wiz" -> "{workspace}/java" 122 "{workspace}/javatests/foo/bar/wiz" -> "{workspace}/javatests" 123 "java/foo/bar/wiz" -> "java" 124 "javatests/foo/bar/wiz" -> "javatests" 125 126 Args: 127 path: A string, representing a file path. 128 129 Returns: 130 A string representing the Java root path or None if could not be 131 determined. 132 """ 133 path_segments = _path.split(path.partition(":")[0]) 134 java_idx = _segment_idx(path_segments) 135 if java_idx < 0: 136 return None 137 else: 138 return _path.join(path_segments[0:java_idx + 1]) 139 140def _check_for_invalid_java_package(java_package): 141 return "-" in java_package or len(java_package.split(".")) < 2 142 143def _invalid_java_package(custom_package, java_package): 144 """Checks if the given java package is invalid. 145 146 Only checks if either custom_package or java_package contains the 147 illegal character "-" or if they are composed of only one word. 148 Only checks java_package if custom_package is an empty string or None. 149 150 Args: 151 custom_package: string. Java package given as an attribute to a rule to override 152 the java_package. 153 java_package: string. Java package inferred from the directory where the BUILD 154 containing the rule is. 155 156 Returns: 157 A boolean. True if custom_package or java_package contains "-" or is only one word. 158 Only checks java_package if custom_package is an empty string or None. 159 """ 160 return ( 161 (custom_package and _check_for_invalid_java_package(custom_package)) or 162 (not custom_package and _check_for_invalid_java_package(java_package)) 163 ) 164 165# The Android specific Java compile. 166def _compile_android( 167 ctx, 168 output_jar, 169 output_srcjar = None, 170 srcs = [], 171 resources = [], 172 javac_opts = [], 173 r_java = None, 174 deps = [], 175 exports = [], 176 plugins = [], 177 exported_plugins = [], 178 annotation_processor_additional_outputs = [], 179 annotation_processor_additional_inputs = [], 180 enable_deps_without_srcs = False, 181 neverlink = False, 182 constraints = ["android"], 183 strict_deps = "Error", 184 java_toolchain = None): 185 """Compiles the Java and IDL sources for Android. 186 187 Args: 188 ctx: The context. 189 output_jar: File. The artifact to place the compilation unit. 190 output_srcjar: File. The artifact to place the sources of the compilation 191 unit. Optional. 192 srcs: sequence of Files. A list of files and jars to be compiled. 193 resources: sequence of Files. Will be added to the output jar - see 194 java_library.resources. Optional. 195 javac_opts: sequence of strings. A list of the desired javac options. 196 Optional. 197 r_java: JavaInfo. The R.jar dependency. Optional. 198 deps: sequence of JavaInfo providers. A list of dependencies. Optional. 199 exports: sequence of JavaInfo providers. A list of exports. Optional. 200 plugins: sequence of JavaPluginInfo providers. A list of plugins. Optional. 201 exported_plugins: sequence of JavaPluginInfo providers. A list of exported 202 plugins. Optional. 203 annotation_processor_additional_outputs: sequence of Files. A list of 204 files produced by an annotation processor. 205 annotation_processor_additional_inputs: sequence of Files. A list of 206 files consumed by an annotation processor. 207 enable_deps_without_srcs: Enables the behavior from b/14473160. 208 neverlink: Bool. Makes the compiled unit a compile-time only dependency. 209 constraints: sequence of Strings. A list of constraints, to constrain the 210 target. Optional. By default []. 211 strict_deps: string. A string that specifies how to handle strict deps. 212 Possible values: 'OFF', 'ERROR','WARN' and 'DEFAULT'. For more details 213 see https://docs.bazel.build/versions/master/user-manual.html#flag--strict_java_deps. 214 By default 'ERROR'. 215 java_toolchain: The java_toolchain Target. 216 217 Returns: 218 A JavaInfo provider representing the Java compilation. 219 """ 220 if "android" not in constraints: 221 log.error(_ANDROID_CONSTRAINT_MISSING_ERROR) 222 223 if not srcs: 224 if deps and enable_deps_without_srcs: 225 # TODO(b/122039567): Produces a JavaInfo that exports the deps, but 226 # not the plugins. To reproduce the "deps without srcs" bug, 227 # b/14473160, behavior in Starlark. 228 exports = exports + [ 229 android_common.enable_implicit_sourceless_deps_exports_compatibility(dep) 230 for dep in deps 231 ] 232 if not exports: 233 # Add a "no-op JavaInfo" to propagate the exported_plugins when 234 # deps or exports have not been specified by the target and 235 # additionally forces java_common.compile method to create the 236 # empty output jar and srcjar when srcs have not been specified. 237 noop_java_info = java_common.merge([]) 238 exports = exports + [noop_java_info] 239 240 r_java_info = [r_java] if r_java else [] 241 242 java_info = _compile( 243 ctx, 244 output_jar, 245 output_srcjar = output_srcjar, 246 srcs = srcs, 247 resources = resources, 248 javac_opts = javac_opts, 249 deps = r_java_info + deps, 250 # In native, the JavaInfo exposes two Jars as compile-time deps, the 251 # compiled sources and the Android R.java jars. To simulate this 252 # behavior, the JavaInfo of the R.jar is also exported. 253 exports = r_java_info + exports, 254 plugins = plugins, 255 exported_plugins = exported_plugins, 256 annotation_processor_additional_outputs = ( 257 annotation_processor_additional_outputs 258 ), 259 annotation_processor_additional_inputs = ( 260 annotation_processor_additional_inputs 261 ), 262 neverlink = neverlink, 263 constraints = constraints, 264 strict_deps = strict_deps, 265 java_toolchain = java_toolchain, 266 ) 267 return java_info 268 269def _compile( 270 ctx, 271 output_jar, 272 output_srcjar = None, 273 srcs = [], 274 resources = [], 275 javac_opts = [], 276 deps = [], 277 exports = [], 278 plugins = [], 279 exported_plugins = [], 280 annotation_processor_additional_outputs = [], 281 annotation_processor_additional_inputs = [], 282 neverlink = False, 283 constraints = [], 284 strict_deps = "Error", 285 java_toolchain = None): 286 """Compiles the Java and IDL sources for Android. 287 288 Args: 289 ctx: The context. 290 output_jar: File. The artifact to place the compilation unit. 291 output_srcjar: File. The artifact to place the sources of the compilation 292 unit. Optional. 293 srcs: sequence of Files. A list of files and jars to be compiled. 294 resources: sequence of Files. Will be added to the output jar - see 295 java_library.resources. Optional. 296 javac_opts: sequence of strings. A list of the desired javac options. 297 Optional. 298 deps: sequence of JavaInfo providers. A list of dependencies. Optional. 299 exports: sequence of JavaInfo providers. A list of exports. Optional. 300 plugins: sequence of JavaPluginInfo providers. A list of plugins. Optional. 301 exported_plugins: sequence of JavaPluginInfo providers. A list of exported 302 plugins. Optional. 303 annotation_processor_additional_outputs: sequence of Files. A list of 304 files produced by an annotation processor. 305 annotation_processor_additional_inputs: sequence of Files. A list of 306 files consumed by an annotation processor. 307 resources: sequence of Files. Will be added to the output jar - see 308 java_library.resources. Optional. 309 neverlink: Bool. Makes the compiled unit a compile-time only dependency. 310 constraints: sequence of Strings. A list of constraints, to constrain the 311 target. Optional. By default []. 312 strict_deps: string. A string that specifies how to handle strict deps. 313 Possible values: 'OFF', 'ERROR','WARN' and 'DEFAULT'. For more details 314 see https://docs.bazel.build/versions/master/user-manual.html#flag--strict_java_deps. 315 By default 'ERROR'. 316 java_toolchain: The java_toolchain Target. 317 318 Returns: 319 A JavaInfo provider representing the Java compilation. 320 """ 321 322 # Split javac opts. 323 opts = [] 324 for opt in javac_opts: 325 opts.extend(opt.split(" ")) 326 327 # Separate the sources *.java from *.srcjar. 328 source_files = [] 329 source_jars = [] 330 for src in srcs: 331 if src.path.endswith(".srcjar"): 332 source_jars.append(src) 333 else: 334 source_files.append(src) 335 336 return java_common.compile( 337 ctx, 338 output = output_jar, 339 output_source_jar = output_srcjar, 340 source_files = source_files, 341 source_jars = source_jars, 342 resources = resources, 343 javac_opts = opts, 344 deps = deps, 345 exports = exports, 346 plugins = plugins, 347 exported_plugins = exported_plugins, 348 annotation_processor_additional_outputs = ( 349 annotation_processor_additional_outputs 350 ), 351 annotation_processor_additional_inputs = ( 352 annotation_processor_additional_inputs 353 ), 354 neverlink = neverlink, 355 strict_deps = strict_deps, 356 java_toolchain = java_toolchain[java_common.JavaToolchainInfo], 357 ) 358 359def _singlejar( 360 ctx, 361 inputs, 362 output, 363 mnemonic = "SingleJar", 364 progress_message = "Merge into a single jar.", 365 build_target = "", 366 check_desugar_deps = False, 367 compression = True, 368 deploy_manifest_lines = [], 369 include_build_data = False, 370 include_prefixes = [], 371 java_toolchain = None, 372 resource_set = None): 373 args = ctx.actions.args() 374 args.add("--output") 375 args.add(output) 376 if compression: 377 args.add("--compression") 378 args.add("--normalize") 379 if not include_build_data: 380 args.add("--exclude_build_data") 381 args.add("--warn_duplicate_resources") 382 if inputs: 383 args.add("--sources") 384 args.add_all(inputs) 385 386 if build_target: 387 args.add("--build_target", build_target) 388 if check_desugar_deps: 389 args.add("--check_desugar_deps") 390 if deploy_manifest_lines: 391 args.add_all("--deploy_manifest_lines", deploy_manifest_lines) 392 if include_prefixes: 393 args.add_all("--include_prefixes", include_prefixes) 394 395 args.use_param_file("@%s") 396 args.set_param_file_format("multiline") 397 398 ctx.actions.run( 399 executable = java_toolchain[java_common.JavaToolchainInfo].single_jar, 400 toolchain = "@bazel_tools//tools/jdk:toolchain_type", 401 arguments = [args], 402 inputs = inputs, 403 outputs = [output], 404 mnemonic = mnemonic, 405 progress_message = progress_message, 406 resource_set = resource_set, 407 ) 408 409def _run( 410 ctx, 411 host_javabase, 412 jvm_flags = [], 413 **args): 414 """Run a java binary 415 416 Args: 417 ctx: The context. 418 host_javabase: Target. The host_javabase. 419 jvm_flags: Additional arguments to the JVM itself. 420 **args: Additional arguments to pass to ctx.actions.run(). Some will get modified. 421 """ 422 423 if type(ctx) != "ctx": 424 fail("Expected type ctx for argument ctx, got %s" % type(ctx)) 425 426 if type(host_javabase) != "Target": 427 fail("Expected type Target for argument host_javabase, got %s" % type(host_javabase)) 428 429 # Set reasonable max heap default. Required to prevent runaway memory usage. 430 # Can still be overridden by callers of this method. 431 jvm_flags = ["-Xms4G", "-Xmx4G", "-XX:+ExitOnOutOfMemoryError"] + jvm_flags 432 433 # executable should be a File or a FilesToRunProvider 434 jar = args.get("executable") 435 if type(jar) == "FilesToRunProvider": 436 jar = jar.executable 437 elif type(jar) != "File": 438 fail("Expected type File or FilesToRunProvider for argument executable, got %s" % type(jar)) 439 440 java_runtime = host_javabase[java_common.JavaRuntimeInfo] 441 args["executable"] = java_runtime.java_executable_exec_path 442 args["toolchain"] = "@bazel_tools//tools/jdk:toolchain_type" 443 444 # inputs can be a list or a depset of File 445 inputs = args.get("inputs", default = []) 446 if type(inputs) == type([]): 447 args["inputs"] = depset(direct = inputs + [jar], transitive = [java_runtime.files]) 448 else: # inputs is a depset 449 args["inputs"] = depset(direct = [jar], transitive = [inputs, java_runtime.files]) 450 451 jar_args = ctx.actions.args() 452 jar_args.add("-jar", jar) 453 454 args["arguments"] = jvm_flags + [jar_args] + args.get("arguments", default = []) 455 456 ctx.actions.run(**args) 457 458def _create_deploy_jar( 459 ctx, 460 output = None, 461 runtime_jars = depset(), 462 java_toolchain = None, 463 build_target = "", 464 deploy_manifest_lines = []): 465 _singlejar( 466 ctx, 467 inputs = runtime_jars, 468 output = output, 469 mnemonic = "JavaDeployJar", 470 progress_message = "Building deploy jar %s" % output.short_path, 471 java_toolchain = java_toolchain, 472 build_target = build_target, 473 check_desugar_deps = True, 474 compression = False, 475 deploy_manifest_lines = deploy_manifest_lines, 476 resource_set = _resource_set_for_deploy_jar, 477 ) 478 return output 479 480def _resource_set_for_deploy_jar(_os, _inputs_size): 481 # parameters are unused but required by the resource_set API 482 return {"memory": _SINGLEJAR_MEMORY_FOR_DEPLOY_JAR_MB, "cpu": 1} 483 484java = struct( 485 compile = _compile, 486 compile_android = _compile_android, 487 resolve_package = _resolve_package, 488 resolve_package_from_label = _resolve_package_from_label, 489 root = _root, 490 invalid_java_package = _invalid_java_package, 491 run = _run, 492 singlejar = _singlejar, 493 create_deploy_jar = _create_deploy_jar, 494) 495