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