1# Copyright 2023 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" Private utilities for Java compilation support in Starlark. """ 16 17load("@bazel_skylib//lib:paths.bzl", "paths") 18load("//java/common:java_semantics.bzl", "semantics") 19load("//java/common/rules:java_toolchain.bzl", "JavaToolchainInfo") 20load("//java/common/rules/impl:java_helper.bzl", "helper") 21load( 22 ":java_info.bzl", 23 "JavaPluginInfo", 24 "disable_plugin_info_annotation_processing", 25 "java_info_for_compilation", 26 "merge_plugin_info_without_outputs", 27) 28load(":native.bzl", "get_internal_java_common") 29 30# copybara: default multiline visibility 31 32def compile( 33 ctx, 34 output, 35 java_toolchain, 36 source_jars = [], 37 source_files = [], 38 output_source_jar = None, 39 javac_opts = [], 40 deps = [], 41 runtime_deps = [], 42 exports = [], 43 plugins = [], 44 exported_plugins = [], 45 native_libraries = [], 46 annotation_processor_additional_inputs = [], 47 annotation_processor_additional_outputs = [], 48 strict_deps = "ERROR", 49 bootclasspath = None, 50 javabuilder_jvm_flags = None, 51 sourcepath = [], 52 resources = [], 53 add_exports = [], 54 add_opens = [], 55 neverlink = False, 56 enable_annotation_processing = True, 57 # private to @_builtins: 58 enable_compile_jar_action = True, 59 enable_jspecify = True, 60 include_compilation_info = True, 61 classpath_resources = [], 62 resource_jars = [], 63 injecting_rule_kind = None): 64 """Compiles Java source files/jars from the implementation of a Starlark rule 65 66 The result is a provider that represents the results of the compilation and can be added to the 67 set of providers emitted by this rule. 68 69 Args: 70 ctx: (RuleContext) The rule context 71 output: (File) The output of compilation 72 java_toolchain: (JavaToolchainInfo) Toolchain to be used for this compilation. Mandatory. 73 source_jars: ([File]) A list of the jars to be compiled. At least one of source_jars or 74 source_files should be specified. 75 source_files: ([File]) A list of the Java source files to be compiled. At least one of 76 source_jars or source_files should be specified. 77 output_source_jar: (File) The output source jar. Optional. Defaults to 78 `{output_jar}-src.jar` if unset. 79 javac_opts: ([str]|depset[str]) A list of the desired javac options. Optional. 80 deps: ([JavaInfo]) A list of dependencies. Optional. 81 runtime_deps: ([JavaInfo]) A list of runtime dependencies. Optional. 82 exports: ([JavaInfo]) A list of exports. Optional. 83 plugins: ([JavaPluginInfo|JavaInfo]) A list of plugins. Optional. 84 exported_plugins: ([JavaPluginInfo|JavaInfo]) A list of exported plugins. Optional. 85 native_libraries: ([CcInfo]) CC library dependencies that are needed for this library. 86 annotation_processor_additional_inputs: ([File]) A list of inputs that the Java compilation 87 action will take in addition to the Java sources for annotation processing. 88 annotation_processor_additional_outputs: ([File]) A list of outputs that the Java 89 compilation action will output in addition to the class jar from annotation processing. 90 strict_deps: (str) A string that specifies how to handle strict deps. Possible values: 91 'OFF', 'ERROR', 'WARN' and 'DEFAULT'. 92 bootclasspath: (BootClassPathInfo) If present, overrides the bootclasspath associated with 93 the provided java_toolchain. Optional. 94 javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder. 95 sourcepath: ([File]) 96 resources: ([File]) 97 resource_jars: ([File]) 98 classpath_resources: ([File]) 99 neverlink: (bool) 100 enable_annotation_processing: (bool) Disables annotation processing in this compilation, 101 causing any annotation processors provided in plugins or in exported_plugins of deps to 102 be ignored. 103 enable_compile_jar_action: (bool) Enables header compilation or ijar creation. If set to 104 False, it forces use of the full class jar in the compilation classpaths of any 105 dependants. Doing so is intended for use by non-library targets such as binaries that 106 do not have dependants. 107 enable_jspecify: (bool) 108 include_compilation_info: (bool) 109 injecting_rule_kind: (str|None) 110 add_exports: ([str]) Allow this library to access the given <module>/<package>. Optional. 111 add_opens: ([str]) Allow this library to reflectively access the given <module>/<package>. 112 Optional. 113 114 Returns: 115 (JavaInfo) 116 """ 117 get_internal_java_common().check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo) 118 get_internal_java_common().check_provider_instances(plugins, "plugins", JavaPluginInfo) 119 120 plugin_info = merge_plugin_info_without_outputs(plugins + deps) 121 122 all_javac_opts = [] # [depset[str]] 123 all_javac_opts.append(java_toolchain._javacopts) 124 125 all_javac_opts.append(ctx.fragments.java.default_javac_flags_depset) 126 all_javac_opts.append(semantics.compatible_javac_options(ctx, java_toolchain)) 127 128 if ("com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor" in 129 plugin_info.plugins.processor_classes.to_list()): 130 all_javac_opts.append(depset( 131 ["-Abazel.repository=" + ctx.label.workspace_name], 132 order = "preorder", 133 )) 134 system_bootclasspath = None 135 for package_config in java_toolchain._package_configuration: 136 if package_config.matches(package_config.package_specs, ctx.label): 137 all_javac_opts.append(package_config.javac_opts) 138 if package_config.system: 139 if system_bootclasspath: 140 fail("Multiple system package configurations found for %s" % ctx.label) 141 system_bootclasspath = package_config.system 142 if not bootclasspath: 143 bootclasspath = system_bootclasspath 144 145 all_javac_opts.append(depset( 146 ["--add-exports=%s=ALL-UNNAMED" % x for x in add_exports], 147 order = "preorder", 148 )) 149 150 if type(javac_opts) == type([]): 151 # detokenize target's javacopts, it will be tokenized before compilation 152 all_javac_opts.append(helper.detokenize_javacopts(helper.tokenize_javacopts(ctx, javac_opts))) 153 elif type(javac_opts) == type(depset()): 154 all_javac_opts.append(javac_opts) 155 else: 156 fail("Expected javac_opts to be a list or depset, got:", type(javac_opts)) 157 158 # we reverse the list of javacopts depsets, so that we keep the right-most set 159 # in case it's deduped. When this depset is flattened, we will reverse again, 160 # and then tokenize before passing to javac. This way, right-most javacopts will 161 # be retained and "win out". 162 all_javac_opts = depset(order = "preorder", transitive = reversed(all_javac_opts)) 163 164 # Optimization: skip this if there are no annotation processors, to avoid unnecessarily 165 # disabling the direct classpath optimization if `enable_annotation_processor = False` 166 # but there aren't any annotation processors. 167 enable_direct_classpath = True 168 if not enable_annotation_processing and plugin_info.plugins.processor_classes: 169 plugin_info = disable_plugin_info_annotation_processing(plugin_info) 170 enable_direct_classpath = False 171 172 all_javac_opts_list = helper.tokenize_javacopts(ctx, all_javac_opts) 173 uses_annotation_processing = False 174 if "-processor" in all_javac_opts_list or plugin_info.plugins.processor_classes: 175 uses_annotation_processing = True 176 177 has_sources = source_files or source_jars 178 has_resources = resources or resource_jars 179 180 is_strict_mode = strict_deps != "OFF" 181 classpath_mode = ctx.fragments.java.reduce_java_classpath() 182 183 direct_jars = depset() 184 if is_strict_mode: 185 direct_jars = depset(order = "preorder", transitive = [dep.compile_jars for dep in deps]) 186 compilation_classpath = depset( 187 order = "preorder", 188 transitive = [direct_jars] + [dep.transitive_compile_time_jars for dep in deps], 189 ) 190 compile_time_java_deps = depset() 191 if is_strict_mode and classpath_mode != "OFF": 192 compile_time_java_deps = depset(transitive = [dep._compile_time_java_dependencies for dep in deps]) 193 194 # create compile time jar action 195 if not has_sources: 196 compile_jar = None 197 compile_deps_proto = None 198 elif not enable_compile_jar_action: 199 compile_jar = output 200 compile_deps_proto = None 201 elif _should_use_header_compilation(ctx, java_toolchain): 202 compile_jar = helper.derive_output_file(ctx, output, name_suffix = "-hjar", extension = "jar") 203 compile_deps_proto = helper.derive_output_file(ctx, output, name_suffix = "-hjar", extension = "jdeps") 204 get_internal_java_common().create_header_compilation_action( 205 ctx, 206 java_toolchain, 207 compile_jar, 208 compile_deps_proto, 209 plugin_info, 210 depset(source_files), 211 source_jars, 212 compilation_classpath, 213 direct_jars, 214 bootclasspath, 215 compile_time_java_deps, 216 all_javac_opts, 217 strict_deps, 218 ctx.label, 219 injecting_rule_kind, 220 enable_direct_classpath, 221 annotation_processor_additional_inputs, 222 ) 223 elif ctx.fragments.java.use_ijars(): 224 compile_jar = run_ijar( 225 ctx.actions, 226 output, 227 java_toolchain, 228 target_label = ctx.label, 229 injecting_rule_kind = injecting_rule_kind, 230 ) 231 compile_deps_proto = None 232 else: 233 compile_jar = output 234 compile_deps_proto = None 235 236 native_headers_jar = helper.derive_output_file(ctx, output, name_suffix = "-native-header") 237 manifest_proto = helper.derive_output_file(ctx, output, extension_suffix = "_manifest_proto") 238 deps_proto = None 239 if ctx.fragments.java.generate_java_deps() and has_sources: 240 deps_proto = helper.derive_output_file(ctx, output, extension = "jdeps") 241 generated_class_jar = None 242 generated_source_jar = None 243 if uses_annotation_processing: 244 generated_class_jar = helper.derive_output_file(ctx, output, name_suffix = "-gen") 245 generated_source_jar = helper.derive_output_file(ctx, output, name_suffix = "-gensrc") 246 get_internal_java_common().create_compilation_action( 247 ctx, 248 java_toolchain, 249 output, 250 manifest_proto, 251 plugin_info, 252 compilation_classpath, 253 direct_jars, 254 bootclasspath, 255 depset(javabuilder_jvm_flags), 256 compile_time_java_deps, 257 all_javac_opts, 258 strict_deps, 259 ctx.label, 260 deps_proto, 261 generated_class_jar, 262 generated_source_jar, 263 native_headers_jar, 264 depset(source_files), 265 source_jars, 266 resources, 267 depset(resource_jars), 268 classpath_resources, 269 sourcepath, 270 injecting_rule_kind, 271 enable_jspecify, 272 enable_direct_classpath, 273 annotation_processor_additional_inputs, 274 annotation_processor_additional_outputs, 275 ) 276 277 create_output_source_jar = len(source_files) > 0 or source_jars != [output_source_jar] 278 if not output_source_jar: 279 output_source_jar = helper.derive_output_file(ctx, output, name_suffix = "-src", extension = "jar") 280 if create_output_source_jar: 281 helper.create_single_jar( 282 ctx.actions, 283 toolchain = java_toolchain, 284 output = output_source_jar, 285 sources = depset(source_jars + ([generated_source_jar] if generated_source_jar else [])), 286 resources = depset(source_files), 287 progress_message = "Building source jar %{output}", 288 mnemonic = "JavaSourceJar", 289 ) 290 291 if has_sources or has_resources: 292 direct_runtime_jars = [output] 293 else: 294 direct_runtime_jars = [] 295 296 compilation_info = struct( 297 javac_options = all_javac_opts, 298 # needs to be flattened because the public API is a list 299 boot_classpath = (bootclasspath.bootclasspath if bootclasspath else java_toolchain.bootclasspath).to_list(), 300 # we only add compile time jars from deps, and not exports 301 compilation_classpath = compilation_classpath, 302 runtime_classpath = depset( 303 order = "preorder", 304 direct = direct_runtime_jars, 305 transitive = [dep.transitive_runtime_jars for dep in runtime_deps + deps], 306 ), 307 uses_annotation_processing = uses_annotation_processing, 308 ) if include_compilation_info else None 309 310 return java_info_for_compilation( 311 output_jar = output, 312 compile_jar = compile_jar, 313 source_jar = output_source_jar, 314 generated_class_jar = generated_class_jar, 315 generated_source_jar = generated_source_jar, 316 plugin_info = plugin_info, 317 deps = deps, 318 runtime_deps = runtime_deps, 319 exports = exports, 320 exported_plugins = exported_plugins, 321 compile_jdeps = compile_deps_proto if compile_deps_proto else deps_proto, 322 jdeps = deps_proto if include_compilation_info else None, 323 native_headers_jar = native_headers_jar, 324 manifest_proto = manifest_proto, 325 native_libraries = native_libraries, 326 neverlink = neverlink, 327 add_exports = add_exports, 328 add_opens = add_opens, 329 direct_runtime_jars = direct_runtime_jars, 330 compilation_info = compilation_info, 331 ) 332 333def _should_use_header_compilation(ctx, toolchain): 334 if not ctx.fragments.java.use_header_compilation(): 335 return False 336 if toolchain._forcibly_disable_header_compilation: 337 return False 338 if not toolchain._header_compiler: 339 fail( 340 "header compilation was requested but it is not supported by the " + 341 "current Java toolchain '" + str(toolchain.label) + 342 "'; see the java_toolchain.header_compiler attribute", 343 ) 344 if not toolchain._header_compiler_direct: 345 fail( 346 "header compilation was requested but it is not supported by the " + 347 "current Java toolchain '" + str(toolchain.label) + 348 "'; see the java_toolchain.header_compiler_direct attribute", 349 ) 350 return True 351 352def run_ijar( 353 actions, 354 jar, 355 java_toolchain, 356 target_label = None, 357 # private to @_builtins: 358 output = None, 359 injecting_rule_kind = None): 360 """Runs ijar on a jar, stripping it of its method bodies. 361 362 This helps reduce rebuilding of dependent jars during any recompiles consisting only of simple 363 changes to method implementations. The return value is typically passed to JavaInfo.compile_jar 364 365 Args: 366 actions: (actions) ctx.actions 367 jar: (File) The jar to run ijar on. 368 java_toolchain: (JavaToolchainInfo) The toolchain to used to find the ijar tool. 369 target_label: (Label|None) A target label to stamp the jar with. Used for `add_dep` support. 370 Typically, you would pass `ctx.label` to stamp the jar with the current rule's label. 371 output: (File) Optional. 372 injecting_rule_kind: (str) the rule class of the current target 373 Returns: 374 (File) The output artifact 375 """ 376 if not output: 377 output = actions.declare_file(paths.replace_extension(jar.basename, "-ijar.jar"), sibling = jar) 378 args = actions.args() 379 args.add(jar) 380 args.add(output) 381 if target_label != None: 382 args.add("--target_label", target_label) 383 if injecting_rule_kind != None: 384 args.add("--injecting_rule_kind", injecting_rule_kind) 385 386 actions.run( 387 mnemonic = "JavaIjar", 388 inputs = [jar], 389 outputs = [output], 390 executable = java_toolchain.ijar, 391 arguments = [args], 392 progress_message = "Extracting interface for jar %{input}", 393 toolchain = semantics.JAVA_TOOLCHAIN_TYPE, 394 use_default_shell_env = True, 395 ) 396 return output 397 398def target_kind(target): 399 """Get the rule class string for a target 400 401 Args: 402 target: (Target) 403 404 Returns: 405 (str) The rule class string of the target 406 """ 407 return get_internal_java_common().target_kind(target) 408 409def collect_native_deps_dirs(libraries): 410 """Collect the set of root-relative paths containing native libraries 411 412 Args: 413 libraries: (depset[LibraryToLink]) set of native libraries 414 415 Returns: 416 ([String]) A set of root-relative paths as a list 417 """ 418 return get_internal_java_common().collect_native_deps_dirs(libraries) 419 420def get_runtime_classpath_for_archive(jars, excluded_jars): 421 """Filters a classpath to remove certain entries 422 423 Args 424 jars: (depset[File]) The classpath to filter 425 excluded_jars: (depset[File]) The files to remove 426 427 Returns: 428 (depset[File]) The filtered classpath 429 """ 430 return get_internal_java_common().get_runtime_classpath_for_archive( 431 jars, 432 excluded_jars, 433 ) 434