| # Copyright 2022 Google LLC. 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. |
| |
| """Common Kotlin definitions.""" |
| |
| load("@bazel_skylib//lib:sets.bzl", "sets") |
| load("//bazel:stubs.bzl", "BASE_JVMOPTS") |
| load("//bazel:stubs.bzl", "DEFAULT_BUILTIN_PROCESSORS") |
| load(":file_factory.bzl", "FileFactory") |
| |
| # TODO: Remove the _ALLOWED_*_RULES lists to determine which rules |
| # are accepted dependencies to Kotlin rules as the approach does not scale |
| # because it will require a cl + release for every new rule. |
| |
| _ALLOWED_ANDROID_RULES = [ |
| "aar_import", |
| "android_library", |
| "kt_android_library_helper", |
| ] |
| |
| _ALLOWED_JVM_RULES = [ |
| "_java_grpc_library", |
| "_java_lite_grpc_library", |
| "af_internal_guice_module", # b/142743220 |
| "af_internal_jbcsrc_library", # added with b/143872075 |
| "af_internal_soyinfo_generator", # b/143872075 |
| "java_import", |
| "java_library", |
| "java_lite_proto_library", |
| "java_mutable_proto_library", |
| "java_proto_library", |
| "java_wrap_cc", # b/152799927 |
| "jvm_import", |
| "_kmp_library", |
| "kt_grpc_library_helper", |
| "kt_jvm_library_helper", |
| "kt_jvm_import", |
| "kt_proto_library_helper", |
| "_j2kt_jvm_library_rule", # b/233055549 |
| ] |
| |
| _EXT = struct( |
| KT = ".kt", |
| JAVA = ".java", |
| JAR = ".jar", |
| SRCJAR = ".srcjar", |
| ) |
| |
| _KT_FILE_TYPES = [_EXT.KT] |
| |
| _KT_JVM_FILE_TYPES = [ |
| _EXT.JAVA, |
| _EXT.KT, |
| _EXT.SRCJAR, |
| ] |
| |
| _JAR_FILE_TYPE = [_EXT.JAR] |
| |
| _SRCJAR_FILE_TYPES = [_EXT.JAR, _EXT.SRCJAR] |
| |
| _RULE_FAMILY = struct( |
| UNKNOWN = 0, |
| JVM_LIBRARY = 1, |
| ANDROID_LIBRARY = 2, |
| ) |
| |
| def _is_dir(file, basename): |
| return file.is_directory and file.basename == basename |
| |
| def _is_kt_src(src): |
| """Decides if `src` Kotlin code. |
| |
| Either: |
| - a Kotlin source file |
| - a tree-artifact expected to contain only Kotlin source files |
| """ |
| |
| return src.path.endswith(_EXT.KT) or _is_dir(src, "kotlin") |
| |
| # Compute module name based on target (b/139403883), similar to Swift |
| def _derive_module_name(ctx): |
| label = _get_original_kt_target_label(ctx) |
| package_part = label.package.replace("/", ".") # .package has no leading '//' |
| name_part = label.name |
| if package_part: |
| return package_part + "_" + name_part |
| return name_part |
| |
| def _common_kapt_and_kotlinc_args(ctx, toolchain): |
| return toolchain.kotlinc_cli_flags + [ |
| # Set module name so module-level metadata is preserved when merging Jars (b/139403883) |
| "-module-name", |
| _derive_module_name(ctx), |
| ] |
| |
| # Runs KAPT in two separate actions so annotation processors only rerun when Kotlin stubs changed. |
| def _kapt( |
| ctx, |
| file_factory, |
| kt_srcs = [], |
| common_srcs = [], |
| java_srcs = [], |
| kotlincopts = [], |
| plugin_processors = [], |
| plugin_classpaths = None, |
| plugin_data = None, |
| javacopts = [], |
| toolchain = None, |
| classpath = []): |
| """Runs annotation processors, returns directory containing generated sources.""" |
| if not plugin_processors: # shouldn't get here |
| fail("Kapt cannot work without processors") |
| |
| # Kapt fails with "no source files" if only given Java sources (b/110473479), so skip ahead to |
| # just run turbine if there are no .kt sources. |
| stub_srcjars = [] |
| if kt_srcs or common_srcs: |
| stubs_dir = file_factory.declare_directory("/kapt/gen/stubs") |
| _kapt_stubs( |
| ctx, |
| stubs_dir, |
| kt_srcs, |
| common_srcs, |
| java_srcs, |
| kotlincopts, |
| plugin_processors, |
| plugin_classpaths, |
| toolchain, |
| classpath, |
| ) |
| |
| # Create a srcjar for the .java stubs generated by kapt, |
| # mostly to filter out non-.java stub outputs, e.g. .kapt_metadata. |
| stub_srcjars.append(_create_jar( |
| ctx, |
| toolchain, |
| file_factory.declare_file("stubs-srcjar.jar"), |
| kt_inputs = [stubs_dir], |
| ignore_not_allowed_files = True, |
| )) |
| |
| output_jar = file_factory.declare_file("-kapt.jar") |
| output_srcjar = file_factory.declare_file("-kapt.srcjar") |
| output_manifest = file_factory.declare_file("-kapt.jar_manifest_proto") |
| _run_turbine( |
| ctx, |
| toolchain, |
| plugin_processors, |
| plugin_classpaths, |
| plugin_data, |
| classpath, |
| javacopts, |
| java_srcs, |
| output_jar, |
| output_srcjar, |
| output_manifest, |
| stub_srcjars, |
| ) |
| |
| return struct( |
| jar = output_jar, |
| manifest = output_manifest, |
| srcjar = output_srcjar, |
| java_info = JavaInfo( |
| output_jar = output_jar, |
| compile_jar = output_jar, |
| ), |
| ) |
| |
| _EMPTY_KAPT_OUTPUTS = struct( |
| jar = None, |
| manifest = None, |
| srcjar = None, |
| java_info = None, |
| ) |
| |
| def _kapt_stubs( |
| ctx, |
| stubs_dir, |
| kt_srcs = [], |
| common_srcs = [], |
| java_srcs = [], |
| kotlincopts = [], |
| plugin_processors = [], |
| plugin_classpaths = None, |
| toolchain = None, |
| classpath = []): |
| """Runs kapt3's "stubs" mode to generate .java stubs from given .kt sources.""" |
| |
| # Use params file to handle long classpaths (b/76185759). |
| kaptargs = ctx.actions.args() |
| kaptargs.use_param_file("@%s", use_always = True) |
| kaptargs.set_param_file_format("multiline") # avoid shell-quoting which breaks workers |
| |
| kaptargs.add(toolchain.kotlin_annotation_processing, format = "-Xplugin=%s") |
| kaptargs.add("-P", "plugin:org.jetbrains.kotlin.kapt3:aptMode=stubs") |
| |
| # List processor classes one by one (comma-separated list doesn't work even though documentation |
| # seems to say that it should: http://kotlinlang.org/docs/reference/kapt.html#using-in-cli) |
| kaptargs.add_all( |
| plugin_processors, |
| before_each = "-P", |
| format_each = "plugin:org.jetbrains.kotlin.kapt3:processors=%s", |
| uniquify = True, # multiple plugins can define the same processor, theoretically |
| ) |
| kaptargs.add_all( |
| plugin_classpaths, # no need to uniquify depsets |
| before_each = "-P", |
| format_each = "plugin:org.jetbrains.kotlin.kapt3:apclasspath=%s", |
| ) |
| kaptargs.add("-P", "plugin:org.jetbrains.kotlin.kapt3:sources=/tmp") |
| kaptargs.add("-P", "plugin:org.jetbrains.kotlin.kapt3:classes=/tmp") |
| kaptargs.add("-P", stubs_dir.path, format = "plugin:org.jetbrains.kotlin.kapt3:stubs=%s") |
| kaptargs.add("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true") |
| |
| # kapt requires javac options to be base64-encoded, |
| # see: http://kotlinlang.org/docs/reference/kapt.html#apjavac-options-encoding |
| # The string below is the encoding of "-source 8 -target 8". |
| # TODO: use the full google3 defaults instead of hard-coding. |
| kaptargs.add("-P", "plugin:org.jetbrains.kotlin.kapt3:javacArguments=rO0ABXccAAAAAgAHLXNvdXJjZQABOAAHLXRhcmdldAABOA") |
| kaptargs.add_all(_common_kapt_and_kotlinc_args(ctx, toolchain)) |
| kaptargs.add_joined("-cp", classpath, join_with = ":") |
| kaptargs.add_all(kotlincopts) |
| |
| kaptargs.add_all(kt_srcs) |
| kaptargs.add_all(common_srcs) |
| if java_srcs: |
| kaptargs.add_all(java_srcs) |
| |
| tool_inputs = [toolchain.kotlin_annotation_processing] |
| |
| ctx.actions.run( |
| executable = toolchain.kotlin_compiler, |
| arguments = [kaptargs], |
| inputs = depset( |
| direct = ( |
| kt_srcs + |
| common_srcs + |
| java_srcs + |
| tool_inputs |
| ), |
| transitive = [ |
| classpath, |
| plugin_classpaths, |
| ], |
| ), |
| outputs = [stubs_dir], |
| mnemonic = "KtKaptStubs", |
| progress_message = "Kapt stubs generation: %s" % _get_original_kt_target_label(ctx), |
| execution_requirements = { |
| "worker-key-mnemonic": "Kt2JavaCompile", # share workers with Kt2JavaCompile (b/179578322) |
| }, |
| ) |
| |
| def _run_turbine( |
| ctx, |
| toolchain, |
| plugin_processors, |
| plugin_classpaths, |
| plugin_data, |
| classpath, |
| javacopts, |
| java_srcs, |
| output_jar, |
| output_srcjar, |
| output_manifest, |
| stub_srcjar = []): |
| turbineargs = ctx.actions.args() |
| turbineargs.use_param_file("@%s") |
| turbineargs.add_all("--processors", plugin_processors) |
| turbineargs.add_all("--processorpath", plugin_classpaths) |
| |
| # --define=header_compiler_builtin_processors_setting=false should disable built-in processors, |
| # see: http://google3/tools/jdk/BUILD?l=338&rcl=269833772 |
| enable_builtin_processors = ctx.var.get("header_compiler_builtin_processors_setting", default = "true") != "false" |
| if enable_builtin_processors: |
| turbineargs.add_all("--builtin_processors", DEFAULT_BUILTIN_PROCESSORS) |
| |
| turbineargs.add_all("--javacopts", javacopts) |
| turbineargs.add("--") |
| |
| turbineargs.add_all("--classpath", classpath) |
| |
| turbineargs.add("--gensrc_output", output_srcjar) |
| turbineargs.add("--resource_output", output_jar) |
| turbineargs.add("--output_manifest_proto", output_manifest) |
| |
| turbineargs.add_all("--source_jars", stub_srcjar) |
| |
| if java_srcs: |
| turbineargs.add("--sources") |
| turbineargs.add_all(java_srcs) |
| |
| outputs = [output_srcjar, output_jar, output_manifest] |
| progress_message = "Kotlin annotation processing: %s %s" % (_get_original_kt_target_label(ctx), ", ".join(plugin_processors)) |
| inputs = depset(direct = java_srcs + stub_srcjar, transitive = [classpath, plugin_classpaths, plugin_data]) |
| |
| if enable_builtin_processors and toolchain.turbine_direct and all([p in DEFAULT_BUILTIN_PROCESSORS for p in plugin_processors]): |
| ctx.actions.run( |
| executable = toolchain.turbine_direct, |
| arguments = [turbineargs], |
| inputs = inputs, |
| outputs = outputs, |
| mnemonic = "KtKaptAptDirect", |
| progress_message = progress_message, |
| ) |
| else: |
| java_runtime = toolchain.turbine_java_runtime |
| if not java_runtime: |
| java_runtime = toolchain.java_runtime |
| _actions_run_deploy_jar( |
| ctx = ctx, |
| java_runtime = java_runtime, |
| deploy_jar = toolchain.turbine, |
| deploy_jsa = toolchain.turbine_jsa, |
| inputs = inputs, |
| outputs = outputs, |
| args = [turbineargs], |
| mnemonic = "KtKaptApt", |
| progress_message = progress_message, |
| ) |
| |
| def _derive_gen_class_jar( |
| ctx, |
| toolchain, |
| manifest_proto, |
| javac_jar, |
| java_srcs = []): |
| """Returns the annotation processor-generated classes contained in given Jar.""" |
| if not manifest_proto: |
| return None |
| if not javac_jar: |
| fail("There must be a javac Jar if there was annotation processing") |
| if not java_srcs: |
| # If there weren't any hand-written .java srcs, just use Javac's output |
| return javac_jar |
| |
| # Run GenClass tool to derive gen_class_jar by filtering hand-written sources. |
| # cf. Bazel's JavaCompilationHelper#createGenJarAction |
| result = FileFactory(ctx, javac_jar).declare_file("-gen.jar") |
| |
| genclass_args = ctx.actions.args() |
| genclass_args.add("--manifest_proto", manifest_proto) |
| genclass_args.add("--class_jar", javac_jar) |
| genclass_args.add("--output_jar", result) |
| |
| _actions_run_deploy_jar( |
| ctx = ctx, |
| java_runtime = toolchain.java_runtime, |
| deploy_jar = toolchain.genclass, |
| inputs = [manifest_proto, javac_jar], |
| outputs = [result], |
| args = [genclass_args], |
| mnemonic = "KtGenClassJar", |
| progress_message = "Deriving %{output}", |
| ) |
| |
| return result |
| |
| def _kt_plugins_map( |
| java_plugin_infos = [], |
| kt_compiler_plugin_infos = []): |
| """A struct containing all the plugin types understood by rules_kotlin. |
| |
| Args: |
| java_plugin_infos: (list[JavaPluginInfo]) |
| kt_compiler_plugin_infos: (list[KtCompilerPluginInfo]) |
| """ |
| return struct( |
| java_plugin_infos = java_plugin_infos, |
| kt_compiler_plugin_infos = kt_compiler_plugin_infos, |
| ) |
| |
| def _run_kotlinc( |
| ctx, |
| file_factory, |
| kt_srcs = [], |
| common_srcs = [], |
| coverage_srcs = [], |
| java_srcs_and_dirs = [], |
| kotlincopts = [], |
| compile_jdeps = depset(), |
| toolchain = None, |
| classpath = [], |
| directdep_jars = depset(), |
| plugins = _kt_plugins_map(), |
| friend_jars = depset(), |
| enforce_strict_deps = False, |
| enforce_complete_jdeps = False): |
| direct_inputs = [] |
| transitive_inputs = [] |
| outputs = [] |
| |
| # Args to kotlinc. |
| # |
| # These go at the end of the commandline. They should be passed through all wrapper |
| # layers without post-processing, except to unpack param files. |
| kotlinc_args = ctx.actions.args() |
| kotlinc_args.use_param_file("@%s", use_always = True) # Use params file to handle long classpaths (b/76185759) |
| kotlinc_args.set_param_file_format("multiline") # kotlinc only supports double-quotes ("): https://youtrack.jetbrains.com/issue/KT-24472 |
| |
| kotlinc_args.add_all(_common_kapt_and_kotlinc_args(ctx, toolchain)) |
| kotlinc_args.add_joined("-cp", classpath, join_with = ":") |
| transitive_inputs.append(classpath) |
| kotlinc_args.add_all(kotlincopts) |
| |
| kotlinc_args.add(toolchain.jvm_abi_gen_plugin, format = "-Xplugin=%s") |
| direct_inputs.append(toolchain.jvm_abi_gen_plugin) |
| kt_ijar = file_factory.declare_file("-ijar.jar") |
| kotlinc_args.add("-P", kt_ijar, format = "plugin:org.jetbrains.kotlin.jvm.abi:outputDir=%s") |
| outputs.append(kt_ijar) |
| |
| for p in plugins.kt_compiler_plugin_infos: |
| kotlinc_args.add(p.jar, format = "-Xplugin=%s") |
| direct_inputs.append(p.jar) |
| kotlinc_args.add_all(p.args, before_each = "-P") |
| |
| # Common sources must also be specified as -Xcommon-sources= in addition to appearing in the |
| # source list. |
| if common_srcs: |
| kotlinc_args.add("-Xmulti-platform=true") |
| kotlinc_args.add_all(common_srcs, format_each = "-Xcommon-sources=%s") |
| direct_inputs.extend(common_srcs) |
| |
| output = file_factory.declare_file(".jar") |
| kotlinc_args.add("-d", output) |
| outputs.append(output) |
| kotlinc_args.add_all(kt_srcs) |
| direct_inputs.extend(kt_srcs) |
| kotlinc_args.add_all(common_srcs) |
| direct_inputs.extend(common_srcs) |
| |
| if java_srcs_and_dirs: |
| # This expands any directories into their contained files |
| kotlinc_args.add_all(java_srcs_and_dirs) |
| direct_inputs.extend(java_srcs_and_dirs) |
| |
| kotlinc_args.add_joined(friend_jars, format_joined = "-Xfriend-paths=%s", join_with = ",") |
| transitive_inputs.append(friend_jars) |
| |
| # Do not change the "shape" or mnemonic of this action without consulting Kythe team |
| # (kythe-eng@), to avoid breaking the Kotlin Kythe extractor which "shadows" this action. In |
| # particular, the extractor expects this to be a vanilla "spawn" (ctx.actions.run) so don't |
| # change this to ctx.actions.run_shell or something else without considering Kythe implications |
| # (b/112439843). |
| ctx.actions.run( |
| executable = toolchain.kotlin_compiler, |
| arguments = [kotlinc_args], |
| inputs = depset(direct = direct_inputs, transitive = transitive_inputs), |
| outputs = outputs, |
| mnemonic = "Kt2JavaCompile", |
| progress_message = "Compiling Kotlin For Java Runtime: %s" % _get_original_kt_target_label(ctx), |
| execution_requirements = { |
| "worker-key-mnemonic": "Kt2JavaCompile", |
| }, |
| ) |
| |
| srcjar = _create_jar( |
| ctx, |
| toolchain, |
| file_factory.declare_file("-kt-src.jar"), |
| kt_inputs = kt_srcs, |
| common_inputs = common_srcs, |
| ) |
| |
| if ctx.coverage_instrumented(): |
| output = _offline_instrument_jar( |
| ctx, |
| toolchain, |
| output, |
| kt_srcs + common_srcs + coverage_srcs, |
| ) |
| |
| result = dict( |
| output_jar = output, |
| compile_jar = kt_ijar, |
| source_jar = srcjar, |
| ) |
| return struct(java_info = JavaInfo(**result), **result) |
| |
| def _get_original_kt_target_label(ctx): |
| label = ctx.label |
| if label.name.find("_DO_NOT_DEPEND") > 0: |
| # Remove rule suffix added by kt_android_library |
| label = label.relative(":%s" % label.name[0:label.name.find("_DO_NOT_DEPEND")]) |
| elif hasattr(ctx.attr, "_kt_codegen_plugin_build_tool") and label.name.endswith("_processed_srcs"): |
| # Remove rule suffix added by kt_codegen_filegroup. b/259984258 |
| label = label.relative(":{}".format(label.name.removesuffix("_processed_srcs"))) |
| return label |
| |
| def _run_import_deps_checker( |
| ctx, |
| jars_to_check = [], |
| merged_deps = None, |
| enforce_strict_deps = True, |
| jdeps_output = None, |
| deps_checker = None, |
| java_toolchain = None): |
| full_classpath = _create_classpath(java_toolchain, [merged_deps]) |
| label = _get_original_kt_target_label(ctx) |
| |
| args = ctx.actions.args() |
| args.add("--jdeps_output", jdeps_output) |
| args.add_all(jars_to_check, before_each = "--input") |
| args.add_all(java_toolchain.bootclasspath, before_each = "--bootclasspath_entry") |
| args.add_all(full_classpath, before_each = "--classpath_entry") |
| if enforce_strict_deps: |
| args.add_all(merged_deps.compile_jars, before_each = "--directdep") |
| args.add("error" if enforce_strict_deps else "silence", format = "--checking_mode=%s") |
| args.add("--nocheck_missing_members") # compiler was happy so no need |
| args.add("--rule_label", label) |
| |
| ctx.actions.run( |
| executable = deps_checker, |
| arguments = [args], |
| inputs = depset(jars_to_check, transitive = [full_classpath]), |
| outputs = [jdeps_output], |
| mnemonic = "KtCheckStrictDeps" if enforce_strict_deps else "KtJdeps", |
| progress_message = "%s deps for %s" % ( |
| "Checking strict" if enforce_strict_deps else "Computing", |
| label, |
| ), |
| ) |
| |
| def _offline_instrument_jar(ctx, toolchain, jar, srcs = []): |
| if not jar.basename.endswith(".jar"): |
| fail("Expect JAR input but got %s" % jar) |
| file_factory = FileFactory(ctx, jar) |
| |
| paths_for_coverage_file = file_factory.declare_file("-kt-paths-for-coverage.txt") |
| paths = ctx.actions.args() |
| paths.set_param_file_format("multiline") # don't shell-quote, just list file names |
| paths.add_all([src for src in srcs if src.is_source]) |
| ctx.actions.write(paths_for_coverage_file, paths) |
| |
| output = file_factory.declare_file("-instrumented.jar") |
| args = ctx.actions.args() |
| args.add(jar) |
| args.add(output) |
| args.add(paths_for_coverage_file) |
| ctx.actions.run( |
| executable = toolchain.coverage_instrumenter, |
| arguments = [args], |
| inputs = [jar, paths_for_coverage_file], |
| outputs = [output], |
| mnemonic = "KtJaCoCoInstrument", |
| progress_message = "Instrumenting Kotlin for coverage collection: %s" % _get_original_kt_target_label(ctx), |
| ) |
| |
| return output |
| |
| def _singlejar( |
| ctx, |
| inputs, |
| output, |
| singlejar, |
| mnemonic = "KtMergeJar", |
| content = "final Jar", |
| preserve_compression = False, |
| pseudo_inputs = []): |
| label = _get_original_kt_target_label(ctx) |
| args = ctx.actions.args() |
| args.add("--normalize") |
| args.add("--add_missing_directories") # make output more similar to jar tool (b/114414678) |
| args.add("--exclude_build_data") |
| args.add("--no_duplicates") # No Kt/Java classname collisions (b/216841985) |
| args.add("--output") |
| args.add(output) |
| args.add("--sources") |
| args.add_all(inputs) |
| args.add("--deploy_manifest_lines") |
| args.add("Target-Label: %s" % label) |
| if preserve_compression: |
| args.add("--dont_change_compression") |
| |
| ctx.actions.run( |
| executable = singlejar, |
| arguments = [args], |
| inputs = inputs + pseudo_inputs, |
| outputs = [output], |
| mnemonic = mnemonic, |
| progress_message = "Merging %s: %s" % (content, label), |
| ) |
| |
| def _merge_jdeps(ctx, kt_jvm_toolchain, jdeps_files, file_factory): |
| merged_jdeps_file = file_factory.declare_file("-merged.jdeps") |
| |
| args = ctx.actions.args() |
| args.add("--kind=jdeps") |
| args.add(merged_jdeps_file, format = "--output=%s") |
| args.add(_get_original_kt_target_label(ctx), format = "--rule_label=%s") |
| args.add_all(jdeps_files) |
| |
| ctx.actions.run( |
| executable = kt_jvm_toolchain.jdeps_merger, |
| inputs = jdeps_files, |
| outputs = [merged_jdeps_file], |
| arguments = [args], |
| mnemonic = "KtMergeJdeps", |
| progress_message = "Merging jdeps files %{output}", |
| ) |
| |
| return merged_jdeps_file |
| |
| def _expand_zip(ctx, toolchain, dir, input): |
| args = ctx.actions.args() |
| args.add("unzip", input) |
| args.add(dir.path) |
| |
| _actions_run_deploy_jar( |
| ctx = ctx, |
| java_runtime = toolchain.java_runtime, |
| deploy_jar = toolchain.source_jar_zipper, |
| inputs = [input], |
| outputs = [dir], |
| args = [args], |
| mnemonic = "SrcJarUnzip", |
| ) |
| |
| return dir |
| |
| def _create_jar(ctx, toolchain, out_jar, kt_inputs = [], common_inputs = [], ignore_not_allowed_files = False): |
| args = ctx.actions.args() |
| args.add("zip") |
| args.add(out_jar) |
| args.add_joined("--kotlin_srcs", kt_inputs, join_with = ",") |
| args.add_joined("--common_srcs", common_inputs, join_with = ",") |
| if ignore_not_allowed_files: |
| args.add("-i") |
| |
| _actions_run_deploy_jar( |
| ctx = ctx, |
| java_runtime = toolchain.java_runtime, |
| deploy_jar = toolchain.source_jar_zipper, |
| inputs = kt_inputs + common_inputs, |
| outputs = [out_jar], |
| args = [args], |
| mnemonic = "KtJar", |
| progress_message = "Create Jar (kotlin/common.bzl): %{output}", |
| ) |
| |
| return out_jar |
| |
| def _create_jar_from_tree_artifacts(ctx, toolchain, output_jar, input_dirs): |
| """Packs a sequence of tree artifacts into a single jar. |
| |
| Given the following file directory structure, |
| /usr/home/a/x/1.txt |
| /usr/home/b/y/1.txt |
| with an input_dirs as [ |
| "/usr/home/a", |
| "/usr/home/b", |
| ], |
| The tool produces a jar with in-archive structure of, |
| x/1.txt |
| y/1.txt |
| |
| The function fails on the duplicate jar entry case. e.g. if we pass an |
| input_dirs as [ |
| "/usr/home/a/x", |
| "/usr/home/b/y", |
| ], |
| then the blaze action would fail with an error message. |
| "java.lang.IllegalStateException: 1.txt has the same path as 1.txt! |
| If it is intended behavior rename one or both of them." |
| |
| Args: |
| ctx: The build rule context. |
| toolchain: Toolchain containing the jar tool. |
| output_jar: The jar to be produced by this action. |
| input_dirs: A sequence of tree artifacts to be zipped. |
| |
| Returns: |
| The generated output jar, i.e. output_jar |
| """ |
| |
| args = ctx.actions.args() |
| args.add("zip_resources") |
| args.add(output_jar) |
| args.add_joined( |
| "--input_dirs", |
| input_dirs, |
| join_with = ",", |
| omit_if_empty = False, |
| expand_directories = False, |
| ) |
| |
| _actions_run_deploy_jar( |
| ctx = ctx, |
| java_runtime = toolchain.java_runtime, |
| deploy_jar = toolchain.source_jar_zipper, |
| inputs = input_dirs, |
| outputs = [output_jar], |
| args = [args], |
| mnemonic = "KtJarActionFromTreeArtifacts", |
| progress_message = "Create Jar %{output}", |
| ) |
| |
| return output_jar |
| |
| def _DirSrcjarSyncer(ctx, kt_toolchain, file_factory): |
| _dirs = [] |
| _srcjars = [] |
| |
| def add_dirs(dirs): |
| if not dirs: |
| return |
| |
| _dirs.extend(dirs) |
| _srcjars.append( |
| _create_jar_from_tree_artifacts( |
| ctx, |
| kt_toolchain, |
| file_factory.declare_file("%s.srcjar" % len(_srcjars)), |
| dirs, |
| ), |
| ) |
| |
| def add_srcjars(srcjars): |
| if not srcjars: |
| return |
| |
| for srcjar in srcjars: |
| _dirs.append( |
| _expand_zip( |
| ctx, |
| kt_toolchain, |
| file_factory.declare_directory("%s.expand" % len(_dirs)), |
| srcjar, |
| ), |
| ) |
| _srcjars.extend(srcjars) |
| |
| return struct( |
| add_dirs = add_dirs, |
| add_srcjars = add_srcjars, |
| dirs = _dirs, |
| srcjars = _srcjars, |
| ) |
| |
| def _actions_run_deploy_jar( |
| ctx, |
| java_runtime, |
| deploy_jar, |
| inputs, |
| args = [], |
| deploy_jsa = None, |
| **kwargs): |
| java_args = ctx.actions.args() |
| java_inputs = [] |
| if deploy_jsa: |
| java_args.add("-Xshare:auto") |
| java_args.add(deploy_jsa, format = "-XX:SharedArchiveFile=%s") |
| java_args.add("-XX:-VerifySharedSpaces") |
| java_args.add("-XX:-ValidateSharedClassPaths") |
| java_inputs.append(deploy_jsa) |
| java_args.add("-jar", deploy_jar) |
| java_inputs.append(deploy_jar) |
| |
| java_depset = depset(direct = java_inputs, transitive = [java_runtime[DefaultInfo].files]) |
| if type(inputs) == "depset": |
| all_inputs = depset(transitive = [java_depset, inputs]) |
| else: |
| all_inputs = depset(direct = inputs, transitive = [java_depset]) |
| |
| ctx.actions.run( |
| executable = str(java_runtime[java_common.JavaRuntimeInfo].java_executable_exec_path), |
| inputs = all_inputs, |
| arguments = BASE_JVMOPTS + [java_args] + args, |
| **kwargs |
| ) |
| |
| def _check_srcs_package(target_package, srcs, attr_name): |
| """Makes sure the given srcs live in the given package.""" |
| |
| # Analogous to RuleContext.checkSrcsSamePackage |
| for src in srcs: |
| if target_package != src.owner.package: |
| fail(("Please do not depend on %s directly in %s. Either move it to this package or " + |
| "depend on an appropriate rule in its package.") % (src.owner, attr_name)) |
| |
| # TODO: Streamline API to generate less actions. |
| def _kt_jvm_library( |
| ctx, |
| kt_toolchain, |
| srcs = [], |
| common_srcs = [], |
| coverage_srcs = [], |
| manifest = None, # set for Android libs, otherwise None. |
| merged_manifest = None, # set for Android libs, otherwise None. |
| resource_files = [], # set for Android libs, otherwise empty. |
| classpath_resources = [], # set for kt_jvm_library, otherwise empty. |
| output = None, |
| output_srcjar = None, # Will derive default filename if not set. |
| deps = [], |
| codegen_output_java_infos = [], # JavaInfo sequence from kt_codegen_plugin. |
| exports = [], # passthrough for JavaInfo constructor |
| runtime_deps = [], # passthrough for JavaInfo constructor |
| native_libraries = [], # passthrough of CcInfo for JavaInfo constructor |
| plugins = _kt_plugins_map(), |
| pre_processed_java_plugin_processors = sets.make([]), |
| exported_plugins = [], |
| android_lint_plugins = [], |
| android_lint_rules_jars = depset(), # Depset with standalone Android Lint rules Jars |
| javacopts = [], |
| kotlincopts = [], |
| compile_jdeps = depset(), |
| disable_lint_checks = [], |
| neverlink = False, |
| testonly = False, # used by Android Lint |
| enforce_strict_deps = True, |
| rule_family = _RULE_FAMILY.UNKNOWN, |
| enforce_complete_jdeps = False, |
| java_toolchain = None, |
| friend_jars = depset(), |
| annotation_processor_additional_outputs = [], |
| annotation_processor_additional_inputs = []): |
| if not java_toolchain: |
| fail("Missing or invalid java_toolchain") |
| if not kt_toolchain: |
| fail("Missing or invalid kt_toolchain") |
| |
| file_factory = FileFactory(ctx, output) |
| deps = list(deps) # Defensive copy |
| |
| # Split sources, as java requires a separate compile step. |
| kt_srcs = [s for s in srcs if _is_kt_src(s)] |
| java_srcs = [s for s in srcs if s.path.endswith(_EXT.JAVA)] |
| java_syncer = _DirSrcjarSyncer(ctx, kt_toolchain, file_factory) |
| java_syncer.add_dirs([s for s in srcs if _is_dir(s, "java")]) |
| java_syncer.add_srcjars([s for s in srcs if s.path.endswith(_EXT.SRCJAR)]) |
| |
| expected_srcs = sets.make(kt_srcs + java_srcs + java_syncer.dirs + java_syncer.srcjars) |
| unexpected_srcs = sets.difference(sets.make(srcs), expected_srcs) |
| if sets.length(unexpected_srcs) != 0: |
| fail("Unexpected srcs: %s" % sets.to_list(unexpected_srcs)) |
| |
| # TODO: Remove this special case |
| if kt_srcs and ("flogger" in [p.plugin_id for p in plugins.kt_compiler_plugin_infos]): |
| deps.append(kt_toolchain.flogger_runtime) |
| |
| if srcs or common_srcs or rule_family != _RULE_FAMILY.ANDROID_LIBRARY: |
| deps.extend(kt_toolchain.kotlin_libs) |
| |
| # Skip srcs package check for android_library targets with no kotlin sources: b/239725424 |
| if rule_family != _RULE_FAMILY.ANDROID_LIBRARY or kt_srcs: |
| _check_srcs_package(ctx.label.package, srcs, "srcs") |
| _check_srcs_package(ctx.label.package, common_srcs, "common_srcs") |
| _check_srcs_package(ctx.label.package, coverage_srcs, "coverage_srcs") |
| |
| full_classpath = _create_classpath(java_toolchain, deps + codegen_output_java_infos) |
| |
| # Collect all plugin data, including processors to run and all plugin classpaths, |
| # whether they have processors or not (b/120995492). |
| # This may include go/errorprone plugin classpaths that kapt will ignore. |
| java_plugin_datas = [info.plugins for info in plugins.java_plugin_infos] + [dep.plugins for dep in deps] |
| plugin_processors = [ |
| cls |
| for p in java_plugin_datas |
| for cls in p.processor_classes.to_list() |
| if not sets.contains(pre_processed_java_plugin_processors, cls) |
| ] |
| plugin_classpaths = depset(transitive = [p.processor_jars for p in java_plugin_datas]) |
| |
| out_jars = [] |
| out_srcjars = [] |
| out_compilejars = [] |
| |
| # Kotlin compilation requires two passes when annotation processing is |
| # required. The initial pass processes the annotations and generates |
| # additional sources and the following pass compiles the Kotlin code. |
| # Skip kapt if no plugins have processors (can happen with only |
| # go/errorprone plugins, # b/110540324) |
| kapt_outputs = _EMPTY_KAPT_OUTPUTS |
| if kt_srcs and plugin_processors: |
| kapt_outputs = _kapt( |
| ctx, |
| file_factory = file_factory, |
| kt_srcs = kt_srcs, |
| common_srcs = common_srcs, |
| java_srcs = java_srcs, |
| plugin_processors = plugin_processors, |
| plugin_classpaths = plugin_classpaths, |
| plugin_data = depset(transitive = [p.processor_data for p in java_plugin_datas]), |
| # Put contents of Bazel flag --javacopt before given javacopts as is Java rules. |
| # This still ignores package configurations, which aren't exposed to Starlark. |
| javacopts = (java_common.default_javac_opts(java_toolchain = java_toolchain) + |
| ctx.fragments.java.default_javac_flags + |
| javacopts), |
| kotlincopts = kotlincopts, # don't need strict_deps flags for kapt |
| toolchain = kt_toolchain, |
| classpath = full_classpath, |
| ) |
| |
| out_jars.append(kapt_outputs.jar) |
| java_syncer.add_srcjars([kapt_outputs.srcjar]) |
| |
| kotlinc_result = None |
| if kt_srcs or common_srcs: |
| kotlinc_result = _run_kotlinc( |
| ctx, |
| kt_srcs = kt_srcs, |
| common_srcs = common_srcs, |
| coverage_srcs = coverage_srcs, |
| java_srcs_and_dirs = java_srcs + java_syncer.dirs, |
| file_factory = file_factory.derive("-kt"), |
| kotlincopts = kotlincopts, |
| compile_jdeps = compile_jdeps, |
| toolchain = kt_toolchain, |
| classpath = full_classpath, |
| plugins = plugins, |
| friend_jars = friend_jars, |
| enforce_strict_deps = enforce_strict_deps, |
| enforce_complete_jdeps = enforce_complete_jdeps, |
| ) |
| |
| # Use un-instrumented Jar at compile-time to avoid double-instrumenting inline functions |
| # (see b/110763361 for the comparable Gradle issue) |
| out_compilejars.append(kotlinc_result.compile_jar) |
| out_srcjars.append(kotlinc_result.source_jar) |
| out_jars.append(kotlinc_result.output_jar) |
| |
| classpath_resources_dirs, classpath_resources_non_dirs = _partition( |
| classpath_resources, |
| filter = lambda res: res.is_directory, |
| ) |
| if classpath_resources_dirs: |
| out_jars.append( |
| _create_jar_from_tree_artifacts( |
| ctx, |
| kt_toolchain, |
| file_factory.declare_file("-dir-res.jar"), |
| classpath_resources_dirs, |
| ), |
| ) |
| |
| javac_java_info = None |
| java_native_headers_jar = None |
| java_gensrcjar = None |
| java_genjar = None |
| is_android_library_without_kt_srcs = rule_family == _RULE_FAMILY.ANDROID_LIBRARY and not kt_srcs |
| if java_srcs or java_syncer.srcjars or classpath_resources: |
| javac_deps = deps + codegen_output_java_infos # Defensive copy |
| if kapt_outputs.java_info: |
| javac_deps.append(kapt_outputs.java_info) |
| if kotlinc_result: |
| javac_deps.append(kotlinc_result.java_info) |
| if ctx.coverage_instrumented(): |
| # Including the coverage runtime improves jdeps computation (b/130747644), but it |
| # could be runtime-only if we computed compile-time jdeps using the compile JAR |
| # (which doesn't contain instrumentation). See b/117897097. |
| javac_deps.append(kt_toolchain.coverage_runtime) |
| |
| javac_out = output if is_android_library_without_kt_srcs else file_factory.declare_file("-java.jar") |
| javac_java_info = java_common.compile( |
| ctx, |
| source_files = java_srcs, |
| source_jars = java_syncer.srcjars, |
| resources = classpath_resources_non_dirs, |
| # For targets that are not android_library with java-only srcs, exports will be passed |
| # to the final constructed JavaInfo. |
| exports = exports if is_android_library_without_kt_srcs else [], |
| output = javac_out, |
| exported_plugins = exported_plugins, |
| deps = javac_deps, |
| # Include default_javac_flags, which reflect Blaze's --javacopt flag, so they win over |
| # all sources of default flags (for Ellipsis builds, see b/125452475). |
| # TODO: remove default_javac_flags here once java_common.compile is fixed. |
| javac_opts = ctx.fragments.java.default_javac_flags + javacopts, |
| plugins = plugins.java_plugin_infos, |
| strict_deps = "DEFAULT", |
| java_toolchain = java_toolchain, |
| neverlink = neverlink, |
| # Enable annotation processing for java-only sources to enable data binding |
| enable_annotation_processing = not kt_srcs, |
| annotation_processor_additional_outputs = annotation_processor_additional_outputs, |
| annotation_processor_additional_inputs = annotation_processor_additional_inputs, |
| ) |
| |
| # Directly return the JavaInfo from java.compile() for java-only andorid_library targets |
| # to avoid creating a new JavaInfo. See b/239847857 for additional context. |
| if is_android_library_without_kt_srcs: |
| return struct( |
| java_info = javac_java_info, |
| validations = [], |
| ) |
| |
| out_jars.append(javac_out) |
| out_srcjars.extend(javac_java_info.source_jars) |
| out_compilejars.extend(javac_java_info.compile_jars.to_list()) # unpack singleton depset |
| java_native_headers_jar = javac_java_info.outputs.native_headers |
| |
| if kt_srcs: |
| java_gensrcjar = kapt_outputs.srcjar |
| java_genjar = _derive_gen_class_jar(ctx, kt_toolchain, kapt_outputs.manifest, javac_out, java_srcs) |
| else: |
| java_gensrcjar = javac_java_info.annotation_processing.source_jar |
| java_genjar = javac_java_info.annotation_processing.class_jar |
| if java_gensrcjar: |
| java_syncer.add_srcjars([java_gensrcjar]) |
| |
| jdeps_output = None |
| compile_jdeps_output = None |
| manifest_proto = None |
| |
| # TODO: Move severity overrides to config file when possible again |
| blocking_action_outs = [] |
| |
| if output_srcjar == None: |
| output_srcjar = file_factory.declare_file("-src.jar") |
| compile_jar = file_factory.declare_file("-compile.jar") |
| single_jar = java_toolchain.single_jar |
| _singlejar(ctx, out_srcjars, output_srcjar, single_jar, mnemonic = "KtMergeSrcjar", content = "srcjar", preserve_compression = True) |
| |
| # Don't block compile-time Jar on Android Lint and other validations (b/117991324). |
| _singlejar(ctx, out_compilejars, compile_jar, single_jar, mnemonic = "KtMergeCompileJar", content = "compile-time Jar") |
| |
| # Disable validation for Guitar BUILD targets (b/144326858). |
| # TODO Remove use of RUN_ANALYSIS_TIME_VALIDATION once Guitar disables validations |
| use_validation = ctx.var.get("RUN_ANALYSIS_TIME_VALIDATION", "true") # will be "0" if set by Guitar |
| use_validation = ctx.var.get("kt_use_validations", use_validation) |
| |
| # Include marker file in runtime Jar so we can reliably identify 1P Kotlin code |
| # TODO: consider only doing this for kt_android_library |
| _singlejar( |
| ctx, |
| out_jars + ([kt_toolchain.build_marker] if kt_srcs and ctx.label.package.startswith("java/") else []), |
| output, |
| single_jar, |
| preserve_compression = True, |
| pseudo_inputs = ([] if use_validation == "true" else blocking_action_outs), |
| ) |
| result_java_info = JavaInfo( |
| output_jar = output, |
| compile_jar = compile_jar, |
| source_jar = output_srcjar, |
| deps = deps, |
| exports = exports, |
| exported_plugins = exported_plugins, |
| runtime_deps = runtime_deps, |
| manifest_proto = manifest_proto, |
| neverlink = neverlink, |
| jdeps = jdeps_output, |
| compile_jdeps = compile_jdeps_output, |
| native_libraries = native_libraries, |
| native_headers_jar = java_native_headers_jar, |
| generated_source_jar = java_gensrcjar, |
| generated_class_jar = java_genjar, |
| ) |
| |
| return struct( |
| java_info = result_java_info, |
| validations = (blocking_action_outs if use_validation == "true" else []), |
| ) |
| |
| def _kt_jvm_import( |
| ctx, |
| kt_toolchain, |
| jars = [], |
| srcjar = None, |
| deps = [], |
| runtime_deps = [], |
| neverlink = False, |
| java_toolchain = None, |
| deps_checker = None): |
| if not java_toolchain: |
| fail("Missing or invalid java_toolchain") |
| if not jars: |
| fail("Must import at least one JAR") |
| |
| file_factory = FileFactory(ctx, jars[0]) |
| deps = java_common.merge(deps + kt_toolchain.kotlin_libs) |
| |
| # Check that any needed deps are declared unless neverlink, in which case Jars won't be used |
| # at runtime so we skip the check, though we'll populate jdeps either way. |
| jdeps_output = file_factory.declare_file(".jdeps") |
| _run_import_deps_checker( |
| ctx, |
| jars_to_check = jars, |
| merged_deps = deps, |
| enforce_strict_deps = not neverlink, |
| jdeps_output = jdeps_output, |
| deps_checker = deps_checker, |
| java_toolchain = java_toolchain, |
| ) |
| |
| java_info = java_common.merge([ |
| JavaInfo( |
| output_jar = jar, |
| compile_jar = jar, |
| source_jar = srcjar, |
| deps = [deps], |
| runtime_deps = runtime_deps, |
| neverlink = neverlink, |
| # TODO: Set compile-time jdeps to help reduce Javac classpaths downstream |
| jdeps = jdeps_output, # not clear this is useful but let's populate since we have it |
| ) |
| for jar in jars |
| ]) |
| |
| # TODO Remove use of RUN_ANALYSIS_TIME_VALIDATION once Guitar disables validations |
| use_validation = ctx.var.get("RUN_ANALYSIS_TIME_VALIDATION", "true") # will be "0" if set by Guitar |
| |
| return struct( |
| java_info = java_info, |
| validations = [jdeps_output] if use_validation == "true" and not neverlink else [], |
| ) |
| |
| def _validate_proguard_specs( |
| ctx, |
| proguard_specs, |
| proguard_allowlister): |
| validated_proguard_specs = [] |
| for proguard_spec in proguard_specs: |
| validated_proguard_spec = ctx.actions.declare_file( |
| "validated_proguard/%s/%s_valid" % (ctx.label.name, proguard_spec.path), |
| ) |
| validated_proguard_specs.append(validated_proguard_spec) |
| |
| args = ctx.actions.args() |
| args.add("--path", proguard_spec) |
| args.add("--output", validated_proguard_spec) |
| |
| ctx.actions.run( |
| executable = proguard_allowlister, |
| arguments = [args], |
| inputs = [proguard_spec], |
| outputs = [validated_proguard_spec], |
| mnemonic = "ValidateProguard", |
| progress_message = ( |
| "Validating proguard configuration %s" % proguard_spec |
| ), |
| ) |
| return validated_proguard_specs |
| |
| def _collect_proguard_specs( |
| ctx, |
| proguard_specs, |
| propagated_deps, |
| proguard_allowlister): |
| validated_proguard_specs = _validate_proguard_specs( |
| ctx, |
| proguard_specs, |
| proguard_allowlister, |
| ) |
| |
| return depset( |
| validated_proguard_specs, |
| transitive = [p.specs for p in _collect_providers(ProguardSpecProvider, propagated_deps)], |
| order = "preorder", |
| ) |
| |
| def _collect_providers(provider, deps): |
| """Collects the requested provider from the given list of deps.""" |
| return [dep[provider] for dep in deps if provider in dep] |
| |
| def _create_classpath(java_toolchain, deps): |
| # To not confuse strictdeps, order as boot > direct > transitive JARs (b/149107867). |
| return depset( |
| order = "preorder", |
| transitive = ( |
| [java_toolchain.bootclasspath] + |
| [dep.compile_jars for dep in deps] + |
| [dep.transitive_compile_time_jars for dep in deps] |
| ), |
| ) |
| |
| def _partition(sequence, filter): |
| pos, neg = [], [] |
| for element in sequence: |
| if filter(element): |
| pos.append(element) |
| else: |
| neg.append(element) |
| return pos, neg |
| |
| common = struct( |
| ALLOWED_ANDROID_RULES = _ALLOWED_ANDROID_RULES, |
| ALLOWED_JVM_RULES = _ALLOWED_JVM_RULES, |
| JAR_FILE_TYPE = _JAR_FILE_TYPE, |
| JVM_FLAGS = BASE_JVMOPTS, |
| KT_FILE_TYPES = _KT_FILE_TYPES, |
| KT_JVM_FILE_TYPES = _KT_JVM_FILE_TYPES, |
| RULE_FAMILY = _RULE_FAMILY, |
| SRCJAR_FILE_TYPES = _SRCJAR_FILE_TYPES, |
| collect_proguard_specs = _collect_proguard_specs, |
| collect_providers = _collect_providers, |
| create_jar_from_tree_artifacts = _create_jar_from_tree_artifacts, |
| common_kapt_and_kotlinc_args = _common_kapt_and_kotlinc_args, |
| is_kt_src = _is_kt_src, |
| kt_jvm_import = _kt_jvm_import, |
| kt_jvm_library = _kt_jvm_library, |
| kt_plugins_map = _kt_plugins_map, |
| partition = _partition, |
| ) |