• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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