| # Copyright 2018 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Bazel Java APIs for the Android rules.""" | |
| load(":path.bzl", _path = "path") | |
| load(":utils.bzl", "log") | |
| _ANDROID_CONSTRAINT_MISSING_ERROR = ( | |
| "A list of constraints provided without the 'android' constraint." | |
| ) | |
| # TODO(b/283499746): Reduce singlejar memory if possible. | |
| _SINGLEJAR_MEMORY_FOR_DEPLOY_JAR_MB = 1600 | |
| def _segment_idx(path_segments): | |
| """Finds the index of the segment in the path that preceeds the source root. | |
| Args: | |
| path_segments: A list of strings, where each string is the segment of a | |
| filesystem path. | |
| Returns: | |
| An index to the path segment that represents the Java segment or -1 if | |
| none found. | |
| """ | |
| if _path.is_absolute(path_segments[0]): | |
| log.error("path must not be absolute: %s" % _path.join(path_segments)) | |
| root_idx = -1 | |
| for idx, segment in enumerate(path_segments): | |
| if segment in ["java", "javatests", "src", "testsrc"]: | |
| root_idx = idx | |
| break | |
| if root_idx < 0: | |
| return root_idx | |
| is_src = path_segments[root_idx] == "src" | |
| check_maven_idx = root_idx if is_src else -1 | |
| if root_idx == 0 or is_src: | |
| # Check for a nested root directory. | |
| for idx in range(root_idx + 1, len(path_segments) - 2): | |
| segment = path_segments[idx] | |
| if segment == "src" or (is_src and segment in ["java", "javatests"]): | |
| next_segment = path_segments[idx + 1] | |
| if next_segment in ["com", "org", "net"]: | |
| root_idx = idx | |
| elif segment == "src": | |
| check_maven_idx = idx | |
| break | |
| if check_maven_idx >= 0 and check_maven_idx + 2 < len(path_segments): | |
| next_segment = path_segments[check_maven_idx + 1] | |
| if next_segment in ["main", "test"]: | |
| next_segment = path_segments[check_maven_idx + 2] | |
| if next_segment in ["java", "resources"]: | |
| root_idx = check_maven_idx + 2 | |
| return root_idx | |
| def _resolve_package(path): | |
| """Determines the Java package name from the given path. | |
| Examples: | |
| "{workspace}/java/foo/bar/wiz" -> "foo.bar.wiz" | |
| "{workspace}/javatests/foo/bar/wiz" -> "foo.bar.wiz" | |
| Args: | |
| path: A string, representing a file path. | |
| Returns: | |
| A string representing a Java package name or None if could not be | |
| determined. | |
| """ | |
| path_segments = _path.split(path.partition(":")[0]) | |
| java_idx = _segment_idx(path_segments) | |
| if java_idx < 0: | |
| return None | |
| else: | |
| return ".".join(path_segments[java_idx + 1:]) | |
| def _resolve_package_from_label( | |
| label, | |
| custom_package = None): | |
| """Resolves the Java package from a Label. | |
| When no legal Java package can be resolved from the label, None will be | |
| returned unless fallback is specified. | |
| When a fallback is requested, a not safe for Java compilation package will | |
| be returned. The fallback value will be derrived by taking the label.package | |
| and replacing all path separators with ".". | |
| """ | |
| if custom_package: | |
| return custom_package | |
| # For backwards compatibility, also include directories | |
| # from the label's name | |
| # Ex: "//foo/bar:java/com/google/baz" is a legal one and | |
| # results in "com.google" | |
| label_path = _path.join( | |
| [label.package] + | |
| _path.split(label.name)[:-1], | |
| ) | |
| return _resolve_package(label_path) | |
| def _root(path): | |
| """Determines the Java root from the given path. | |
| Examples: | |
| "{workspace}/java/foo/bar/wiz" -> "{workspace}/java" | |
| "{workspace}/javatests/foo/bar/wiz" -> "{workspace}/javatests" | |
| "java/foo/bar/wiz" -> "java" | |
| "javatests/foo/bar/wiz" -> "javatests" | |
| Args: | |
| path: A string, representing a file path. | |
| Returns: | |
| A string representing the Java root path or None if could not be | |
| determined. | |
| """ | |
| path_segments = _path.split(path.partition(":")[0]) | |
| java_idx = _segment_idx(path_segments) | |
| if java_idx < 0: | |
| return None | |
| else: | |
| return _path.join(path_segments[0:java_idx + 1]) | |
| def _check_for_invalid_java_package(java_package): | |
| return "-" in java_package or len(java_package.split(".")) < 2 | |
| def _invalid_java_package(custom_package, java_package): | |
| """Checks if the given java package is invalid. | |
| Only checks if either custom_package or java_package contains the | |
| illegal character "-" or if they are composed of only one word. | |
| Only checks java_package if custom_package is an empty string or None. | |
| Args: | |
| custom_package: string. Java package given as an attribute to a rule to override | |
| the java_package. | |
| java_package: string. Java package inferred from the directory where the BUILD | |
| containing the rule is. | |
| Returns: | |
| A boolean. True if custom_package or java_package contains "-" or is only one word. | |
| Only checks java_package if custom_package is an empty string or None. | |
| """ | |
| return ( | |
| (custom_package and _check_for_invalid_java_package(custom_package)) or | |
| (not custom_package and _check_for_invalid_java_package(java_package)) | |
| ) | |
| # The Android specific Java compile. | |
| def _compile_android( | |
| ctx, | |
| output_jar, | |
| output_srcjar = None, | |
| srcs = [], | |
| resources = [], | |
| javac_opts = [], | |
| r_java = None, | |
| deps = [], | |
| exports = [], | |
| plugins = [], | |
| exported_plugins = [], | |
| annotation_processor_additional_outputs = [], | |
| annotation_processor_additional_inputs = [], | |
| enable_deps_without_srcs = False, | |
| neverlink = False, | |
| constraints = ["android"], | |
| strict_deps = "Error", | |
| java_toolchain = None): | |
| """Compiles the Java and IDL sources for Android. | |
| Args: | |
| ctx: The context. | |
| output_jar: File. The artifact to place the compilation unit. | |
| output_srcjar: File. The artifact to place the sources of the compilation | |
| unit. Optional. | |
| srcs: sequence of Files. A list of files and jars to be compiled. | |
| resources: sequence of Files. Will be added to the output jar - see | |
| java_library.resources. Optional. | |
| javac_opts: sequence of strings. A list of the desired javac options. | |
| Optional. | |
| r_java: JavaInfo. The R.jar dependency. Optional. | |
| deps: sequence of JavaInfo providers. A list of dependencies. Optional. | |
| exports: sequence of JavaInfo providers. A list of exports. Optional. | |
| plugins: sequence of JavaPluginInfo providers. A list of plugins. Optional. | |
| exported_plugins: sequence of JavaPluginInfo providers. A list of exported | |
| plugins. Optional. | |
| annotation_processor_additional_outputs: sequence of Files. A list of | |
| files produced by an annotation processor. | |
| annotation_processor_additional_inputs: sequence of Files. A list of | |
| files consumed by an annotation processor. | |
| enable_deps_without_srcs: Enables the behavior from b/14473160. | |
| neverlink: Bool. Makes the compiled unit a compile-time only dependency. | |
| constraints: sequence of Strings. A list of constraints, to constrain the | |
| target. Optional. By default []. | |
| strict_deps: string. A string that specifies how to handle strict deps. | |
| Possible values: 'OFF', 'ERROR','WARN' and 'DEFAULT'. For more details | |
| see https://docs.bazel.build/versions/master/user-manual.html#flag--strict_java_deps. | |
| By default 'ERROR'. | |
| java_toolchain: The java_toolchain Target. | |
| Returns: | |
| A JavaInfo provider representing the Java compilation. | |
| """ | |
| if "android" not in constraints: | |
| log.error(_ANDROID_CONSTRAINT_MISSING_ERROR) | |
| if not srcs: | |
| if deps and enable_deps_without_srcs: | |
| # TODO(b/122039567): Produces a JavaInfo that exports the deps, but | |
| # not the plugins. To reproduce the "deps without srcs" bug, | |
| # b/14473160, behavior in Starlark. | |
| exports = exports + [ | |
| android_common.enable_implicit_sourceless_deps_exports_compatibility(dep) | |
| for dep in deps | |
| ] | |
| if not exports: | |
| # Add a "no-op JavaInfo" to propagate the exported_plugins when | |
| # deps or exports have not been specified by the target and | |
| # additionally forces java_common.compile method to create the | |
| # empty output jar and srcjar when srcs have not been specified. | |
| noop_java_info = java_common.merge([]) | |
| exports = exports + [noop_java_info] | |
| r_java_info = [r_java] if r_java else [] | |
| java_info = _compile( | |
| ctx, | |
| output_jar, | |
| output_srcjar = output_srcjar, | |
| srcs = srcs, | |
| resources = resources, | |
| javac_opts = javac_opts, | |
| deps = r_java_info + deps, | |
| # In native, the JavaInfo exposes two Jars as compile-time deps, the | |
| # compiled sources and the Android R.java jars. To simulate this | |
| # behavior, the JavaInfo of the R.jar is also exported. | |
| exports = r_java_info + exports, | |
| plugins = plugins, | |
| exported_plugins = exported_plugins, | |
| annotation_processor_additional_outputs = ( | |
| annotation_processor_additional_outputs | |
| ), | |
| annotation_processor_additional_inputs = ( | |
| annotation_processor_additional_inputs | |
| ), | |
| neverlink = neverlink, | |
| constraints = constraints, | |
| strict_deps = strict_deps, | |
| java_toolchain = java_toolchain, | |
| ) | |
| return java_info | |
| def _compile( | |
| ctx, | |
| output_jar, | |
| output_srcjar = None, | |
| srcs = [], | |
| resources = [], | |
| javac_opts = [], | |
| deps = [], | |
| exports = [], | |
| plugins = [], | |
| exported_plugins = [], | |
| annotation_processor_additional_outputs = [], | |
| annotation_processor_additional_inputs = [], | |
| neverlink = False, | |
| constraints = [], | |
| strict_deps = "Error", | |
| java_toolchain = None): | |
| """Compiles the Java and IDL sources for Android. | |
| Args: | |
| ctx: The context. | |
| output_jar: File. The artifact to place the compilation unit. | |
| output_srcjar: File. The artifact to place the sources of the compilation | |
| unit. Optional. | |
| srcs: sequence of Files. A list of files and jars to be compiled. | |
| resources: sequence of Files. Will be added to the output jar - see | |
| java_library.resources. Optional. | |
| javac_opts: sequence of strings. A list of the desired javac options. | |
| Optional. | |
| deps: sequence of JavaInfo providers. A list of dependencies. Optional. | |
| exports: sequence of JavaInfo providers. A list of exports. Optional. | |
| plugins: sequence of JavaPluginInfo providers. A list of plugins. Optional. | |
| exported_plugins: sequence of JavaPluginInfo providers. A list of exported | |
| plugins. Optional. | |
| annotation_processor_additional_outputs: sequence of Files. A list of | |
| files produced by an annotation processor. | |
| annotation_processor_additional_inputs: sequence of Files. A list of | |
| files consumed by an annotation processor. | |
| resources: sequence of Files. Will be added to the output jar - see | |
| java_library.resources. Optional. | |
| neverlink: Bool. Makes the compiled unit a compile-time only dependency. | |
| constraints: sequence of Strings. A list of constraints, to constrain the | |
| target. Optional. By default []. | |
| strict_deps: string. A string that specifies how to handle strict deps. | |
| Possible values: 'OFF', 'ERROR','WARN' and 'DEFAULT'. For more details | |
| see https://docs.bazel.build/versions/master/user-manual.html#flag--strict_java_deps. | |
| By default 'ERROR'. | |
| java_toolchain: The java_toolchain Target. | |
| Returns: | |
| A JavaInfo provider representing the Java compilation. | |
| """ | |
| # Split javac opts. | |
| opts = [] | |
| for opt in javac_opts: | |
| opts.extend(opt.split(" ")) | |
| # Separate the sources *.java from *.srcjar. | |
| source_files = [] | |
| source_jars = [] | |
| for src in srcs: | |
| if src.path.endswith(".srcjar"): | |
| source_jars.append(src) | |
| else: | |
| source_files.append(src) | |
| return java_common.compile( | |
| ctx, | |
| output = output_jar, | |
| output_source_jar = output_srcjar, | |
| source_files = source_files, | |
| source_jars = source_jars, | |
| resources = resources, | |
| javac_opts = opts, | |
| deps = deps, | |
| exports = exports, | |
| plugins = plugins, | |
| exported_plugins = exported_plugins, | |
| annotation_processor_additional_outputs = ( | |
| annotation_processor_additional_outputs | |
| ), | |
| annotation_processor_additional_inputs = ( | |
| annotation_processor_additional_inputs | |
| ), | |
| neverlink = neverlink, | |
| strict_deps = strict_deps, | |
| java_toolchain = java_toolchain[java_common.JavaToolchainInfo], | |
| ) | |
| def _singlejar( | |
| ctx, | |
| inputs, | |
| output, | |
| mnemonic = "SingleJar", | |
| progress_message = "Merge into a single jar.", | |
| include_build_data = False, | |
| java_toolchain = None, | |
| extra_args = [], | |
| resource_set = None): | |
| args = ctx.actions.args() | |
| args.add("--output") | |
| args.add(output) | |
| args.add("--compression") | |
| args.add("--normalize") | |
| if not include_build_data: | |
| args.add("--exclude_build_data") | |
| args.add("--warn_duplicate_resources") | |
| if inputs: | |
| args.add("--sources") | |
| args.add_all(inputs) | |
| args.use_param_file("@%s") | |
| args.set_param_file_format("multiline") | |
| ctx.actions.run( | |
| executable = java_toolchain[java_common.JavaToolchainInfo].single_jar, | |
| arguments = [args] + extra_args, | |
| inputs = inputs, | |
| outputs = [output], | |
| mnemonic = mnemonic, | |
| progress_message = progress_message, | |
| resource_set = resource_set, | |
| ) | |
| def _run( | |
| ctx, | |
| host_javabase, | |
| jvm_flags = [], | |
| **args): | |
| """Run a java binary | |
| Args: | |
| ctx: The context. | |
| host_javabase: Target. The host_javabase. | |
| jvm_flags: Additional arguments to the JVM itself. | |
| **args: Additional arguments to pass to ctx.actions.run(). Some will get modified. | |
| """ | |
| if type(ctx) != "ctx": | |
| fail("Expected type ctx for argument ctx, got %s" % type(ctx)) | |
| if type(host_javabase) != "Target": | |
| fail("Expected type Target for argument host_javabase, got %s" % type(host_javabase)) | |
| # Set reasonable max heap default. Required to prevent runaway memory usage. | |
| # Can still be overridden by callers of this method. | |
| jvm_flags = ["-Xms4G", "-Xmx4G", "-XX:+ExitOnOutOfMemoryError"] + jvm_flags | |
| # executable should be a File or a FilesToRunProvider | |
| jar = args.get("executable") | |
| if type(jar) == "FilesToRunProvider": | |
| jar = jar.executable | |
| elif type(jar) != "File": | |
| fail("Expected type File or FilesToRunProvider for argument executable, got %s" % type(jar)) | |
| java_runtime = host_javabase[java_common.JavaRuntimeInfo] | |
| args["executable"] = java_runtime.java_executable_exec_path | |
| # inputs can be a list or a depset of File | |
| inputs = args.get("inputs", default = []) | |
| if type(inputs) == type([]): | |
| args["inputs"] = depset(direct = inputs + [jar], transitive = [java_runtime.files]) | |
| else: # inputs is a depset | |
| args["inputs"] = depset(direct = [jar], transitive = [inputs, java_runtime.files]) | |
| jar_args = ctx.actions.args() | |
| jar_args.add("-jar", jar) | |
| args["arguments"] = jvm_flags + [jar_args] + args.get("arguments", default = []) | |
| ctx.actions.run(**args) | |
| def _create_deploy_jar( | |
| ctx, | |
| output = None, | |
| runtime_jars = depset(), | |
| java_toolchain = None, | |
| target_name = "", | |
| build_info_files = depset(), | |
| deploy_manifest_lines = [], | |
| extra_build_info = ""): | |
| inputs = depset(transitive = [runtime_jars, build_info_files]) | |
| args = ctx.actions.args() | |
| args.add("--build_target", target_name) | |
| args.add("--check_desugar_deps") | |
| if build_info_files: | |
| args.add_all(build_info_files, before_each = "--build_info_file") | |
| args.add_all("--deploy_manifest_lines", deploy_manifest_lines) | |
| args.add("--extra_build_info", extra_build_info) | |
| _singlejar( | |
| ctx, | |
| inputs = inputs, | |
| output = output, | |
| mnemonic = "JavaDeployJar", | |
| progress_message = "Building deploy jar %s" % output.short_path, | |
| java_toolchain = java_toolchain, | |
| extra_args = [args], | |
| resource_set = _resource_set_for_deploy_jar, | |
| ) | |
| return output | |
| def _resource_set_for_deploy_jar(_os, _inputs_size): | |
| # parameters are unused but required by the resource_set API | |
| return {"memory": _SINGLEJAR_MEMORY_FOR_DEPLOY_JAR_MB, "cpu": 1} | |
| java = struct( | |
| compile = _compile, | |
| compile_android = _compile_android, | |
| resolve_package = _resolve_package, | |
| resolve_package_from_label = _resolve_package_from_label, | |
| root = _root, | |
| invalid_java_package = _invalid_java_package, | |
| run = _run, | |
| singlejar = _singlejar, | |
| create_deploy_jar = _create_deploy_jar, | |
| ) |