# 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.""" # go/keep-sorted start load("//kotlin/jvm/internal_do_not_use/util:file_factory.bzl", "FileFactory") load("//kotlin/jvm/internal_do_not_use/util:run_deploy_jar.bzl", "kt_run_deploy_jar") load("//kotlin/jvm/internal_do_not_use/util:srcjars.bzl", "kt_srcjars") load("//toolchains/kotlin_jvm:androidlint_toolchains.bzl", "androidlint_toolchains") load("//:visibility.bzl", "RULES_DEFS_THAT_COMPILE_KOTLIN") load("@bazel_skylib//lib:sets.bzl", "sets") load("//bazel:stubs.bzl", "lint_actions") load("//bazel:stubs.bzl", "BASE_JVMOPTS") load("//bazel:stubs.bzl", "DEFAULT_BUILTIN_PROCESSORS") # go/keep-sorted end # 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 "kt_flatbuffers_library", # b/263252785 ] _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_file(file, extension): return (not file.is_directory) and file.path.endswith(extension) 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 _is_file(src, _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(kt_srcjars.zip( ctx, toolchain, file_factory.declare_file("stubs-srcjar.jar"), srcs = [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 kt_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) kt_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.insert(0, output) # The param file name is derived from the 0th 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 = kt_srcjars.zip( ctx, toolchain, file_factory.declare_file("-kt-src.jar"), srcs = kt_srcs, common_srcs = 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 _is_file(jar, _EXT.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 _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)) def _split_srcs_by_language(srcs, common_srcs, java_syncer): srcs_set = sets.make(srcs) common_srcs_set = sets.make(common_srcs) overlapping_srcs_set = sets.intersection(srcs_set, common_srcs_set) if sets.length(overlapping_srcs_set) != 0: fail("Overlap between srcs and common_srcs: %s" % sets.to_list(overlapping_srcs_set)) # 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 _is_file(s, _EXT.JAVA)] java_syncer.add_dirs([s for s in srcs if _is_dir(s, "java")]) java_syncer.add_srcjars([s for s in srcs if _is_file(s, _EXT.SRCJAR)]) expected_srcs_set = sets.make(kt_srcs + java_srcs + java_syncer.dirs + java_syncer.srcjars) unexpected_srcs_set = sets.difference(srcs_set, expected_srcs_set) if sets.length(unexpected_srcs_set) != 0: fail("Unexpected srcs: %s" % sets.to_list(unexpected_srcs_set)) return (kt_srcs, java_srcs) # 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 = [], exports = [], # passthrough for JavaInfo constructor runtime_deps = [], # passthrough for JavaInfo constructor native_libraries = [], # passthrough of CcInfo for JavaInfo constructor plugins = _kt_plugins_map(), kt_codegen_processing_env = dict( pre_processed_processors = depset(), codegen_output_java_infos = [], ), 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") pre_processed_processors = kt_codegen_processing_env["pre_processed_processors"] codegen_output_java_infos = kt_codegen_processing_env["codegen_output_java_infos"] file_factory = FileFactory(ctx, output) deps = list(deps) # Defensive copy java_syncer = kt_srcjars.DirSrcjarSyncer(ctx, kt_toolchain, file_factory) kt_srcs, java_srcs = _split_srcs_by_language(srcs, common_srcs, java_syncer) # 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 kt_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 cls not in pre_processed_processors.to_list() ] 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( kt_srcjars.zip_resources( 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") annotation_plugins = list(plugins.java_plugin_infos) # Enable annotation processing for java-only sources to enable data binding enable_annotation_processing = not kt_srcs 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 = annotation_plugins, strict_deps = "DEFAULT", java_toolchain = java_toolchain, neverlink = neverlink, enable_annotation_processing = enable_annotation_processing, 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 android_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: if pre_processed_processors: java_gensrcjar = kt_codegen_processing_env["java_info_genearted_source_jar"] java_genjar = javac_out else: 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 = [] # TODO: Remove the is_android_library_without_kt_srcs condition once KtAndroidLint # uses the same lint checks with AndroidLint if not is_android_library_without_kt_srcs: lint_flags = [ "--java-language-level", # b/159950410 kt_toolchain.java_language_version, "--kotlin-language-level", kt_toolchain.kotlin_language_version, "--nowarn", # Check for "errors", which includes custom checks that report errors. "--XallowBaselineSuppress", # allow baseline exemptions of otherwise unsuppressable errors "--exitcode", # fail on error "--fullpath", # reduce file path clutter in reported issues "--text", "stdout", # also log to stdout ] if disable_lint_checks != None and disable_lint_checks and disable_lint_checks != [""]: lint_flags.append("--disable") lint_flags.append(",".join(disable_lint_checks)) # TODO: Support Android Lint plugins coming from plugins and exported_plugins attributes android_lint_plugins_jars = depset( order = "preorder", transitive = [plugin_classpaths] + [dep.transitive_runtime_jars for dep in android_lint_plugins], ) android_lint_out = lint_actions.run_lint_on_library( ctx, runner = kt_toolchain.android_lint_runner, output = file_factory.declare_file("_android_lint_output.xml"), srcs = kt_srcs + java_srcs + common_srcs, source_jars = java_syncer.srcjars, classpath = full_classpath, manifest = manifest, merged_manifest = merged_manifest, resource_files = resource_files, baseline_file = androidlint_toolchains.get_baseline(ctx), config = kt_toolchain.android_lint_config, android_lint_plugins_depset = android_lint_plugins_jars, android_lint_rules = android_lint_rules_jars, lint_flags = lint_flags, extra_input_depsets = [p.processor_data for p in java_plugin_datas], testonly = testonly, android_java8_libs = kt_toolchain.android_java8_apis_desugared, mnemonic = "KtAndroidLint", # so LSA extractor can distinguish Kotlin (b/189442586) ) blocking_action_outs.append(android_lint_out) 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") _check_srcs_package(ctx.label.package, jars, "jars") if srcjar: _check_srcs_package(ctx.label.package, [srcjar], "srcjar") 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 _gen_java_info_genearted_class_jar(ctx, kt_toolchain, input_jars, srcjars): output_jar = ctx.actions.declare_file(ctx.label.name + "_java_info_genearted_class_jar.jar") input_jars = depset(input_jars) transformer_env_files = depset(srcjars) transformer_entry_point = "com.google.devtools.jar.transformation.ClassFileSelectorBySourceFile" transformer_jars = kt_toolchain.class_file_selector_by_source_file[JavaInfo].transitive_runtime_jars jar_transformer = kt_toolchain.jar_transformer[DefaultInfo].files_to_run args = ctx.actions.args() args.add_joined("--input_jars", input_jars, join_with = ",") args.add_joined("--transformer_jars", transformer_jars, join_with = ",") args.add("--transformer_entry_point", transformer_entry_point) args.add_all("--transformer_env_files", transformer_env_files) args.add("--result", output_jar) ctx.actions.run( inputs = depset(transitive = [ input_jars, transformer_jars, transformer_env_files, ]), outputs = [output_jar], arguments = [args], progress_message = "Transforming into %s" % output_jar.short_path, mnemonic = "JarTransformer", executable = jar_transformer, ) return output_jar 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 = kt_srcjars.zip_resources, 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, )