• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 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""" Implementation of java_binary for bazel """
16
17load("@com_google_protobuf//bazel/common:proto_info.bzl", "ProtoInfo")
18load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
19load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
20load("//java/common:java_semantics.bzl", "semantics")
21load("//java/common/rules/impl:basic_java_library_impl.bzl", "basic_java_library", "collect_deps")
22load("//java/private:java_common.bzl", "java_common")
23load(
24    "//java/private:java_common_internal.bzl",
25    "collect_native_deps_dirs",
26    "get_runtime_classpath_for_archive",
27)
28load("//java/private:java_info.bzl", "JavaCompilationInfo", "JavaInfo", "to_java_binary_info")
29load(":java_binary_deploy_jar.bzl", "create_deploy_archive")
30load(":java_helper.bzl", "helper")
31
32# copybara: default visibility
33
34InternalDeployJarInfo = provider(
35    "Provider for passing info to deploy jar rule",
36    fields = [
37        "java_attrs",
38        "strip_as_default",
39        "add_exports",
40        "add_opens",
41    ],
42)
43
44def basic_java_binary(
45        ctx,
46        deps,
47        runtime_deps,
48        resources,
49        main_class,
50        coverage_main_class,
51        coverage_config,
52        launcher_info,
53        executable,
54        strip_as_default,
55        extra_java_info = None,
56        is_test_rule_class = False):
57    """Creates actions for compiling and linting java sources, coverage support, and sources jar (_deploy-src.jar).
58
59    Args:
60        ctx: (RuleContext) The rule context
61        deps: (list[Target]) The list of other targets to be compiled with
62        runtime_deps: (list[Target]) The list of other targets to be linked in
63        resources: (list[File]) The list of data files to be included in the class jar
64        main_class: (String) FQN of the java main class
65        coverage_main_class: (String) FQN of the actual main class if coverage is enabled
66        coverage_config: (Struct|None) If coverage is enabled, a struct with fields (runner, manifest, env, support_files), None otherwise
67        launcher_info: (Struct) Structure with fields (launcher, unstripped_launcher, runfiles, runtime_jars, jvm_flags, classpath_resources)
68        executable: (File) The executable output of the rule
69        strip_as_default: (bool) Whether this target outputs a stripped launcher and deploy jar
70        extra_java_info: (JavaInfo) additional outputs to merge
71        is_test_rule_class: (bool) Whether this rule is a test rule
72
73    Returns:
74        Tuple(
75            dict[str, Provider],    // providers
76            Struct(                 // default info
77                files_to_build: depset(File),
78                runfiles: Runfiles,
79                executable: File
80            ),
81            list[String]            // jvm flags
82          )
83
84    """
85    if not ctx.attr.create_executable and (ctx.attr.launcher and cc_common.launcher_provider in ctx.attr.launcher):
86        fail("launcher specified but create_executable is false")
87    if not ctx.attr.use_launcher and (ctx.attr.launcher and ctx.attr.launcher.label != semantics.LAUNCHER_FLAG_LABEL):
88        fail("launcher specified but use_launcher is false")
89
90    if not ctx.attr.srcs and ctx.attr.deps:
91        fail("deps not allowed without srcs; move to runtime_deps?")
92
93    module_flags = [dep[JavaInfo].module_flags_info for dep in runtime_deps if JavaInfo in dep]
94    add_exports = depset(ctx.attr.add_exports, transitive = [m.add_exports for m in module_flags])
95    add_opens = depset(ctx.attr.add_opens, transitive = [m.add_opens for m in module_flags])
96
97    classpath_resources = []
98    classpath_resources.extend(launcher_info.classpath_resources)
99    if hasattr(ctx.files, "classpath_resources"):
100        classpath_resources.extend(ctx.files.classpath_resources)
101
102    toolchain = semantics.find_java_toolchain(ctx)
103    timezone_data = [toolchain._timezone_data] if toolchain._timezone_data else []
104    target, common_info = basic_java_library(
105        ctx,
106        srcs = ctx.files.srcs,
107        deps = deps,
108        runtime_deps = runtime_deps,
109        plugins = ctx.attr.plugins,
110        resources = resources,
111        resource_jars = timezone_data,
112        classpath_resources = classpath_resources,
113        javacopts = ctx.attr.javacopts,
114        neverlink = ctx.attr.neverlink,
115        enable_compile_jar_action = False,
116        coverage_config = coverage_config,
117        add_exports = ctx.attr.add_exports,
118        add_opens = ctx.attr.add_opens,
119        bootclasspath = ctx.attr.bootclasspath,
120    )
121    java_info = target["JavaInfo"]
122    compilation_info = java_info.compilation_info
123    runtime_classpath = depset(
124        order = "preorder",
125        transitive = [
126            java_info.transitive_runtime_jars
127            for java_info in (
128                collect_deps(ctx.attr.runtime_deps + deps) +
129                ([coverage_config.runner] if coverage_config and coverage_config.runner else [])
130            )
131        ],
132    )
133    if extra_java_info:
134        runtime_classpath = depset(order = "preorder", transitive = [
135            extra_java_info.transitive_runtime_jars,
136            runtime_classpath,
137        ])
138        java_info = java_common.merge([java_info, extra_java_info])
139        compilation_info = JavaCompilationInfo(
140            compilation_classpath = compilation_info.compilation_classpath,
141            runtime_classpath = runtime_classpath,
142            boot_classpath = compilation_info.boot_classpath,
143            javac_options = compilation_info.javac_options,
144        )
145
146    java_attrs = _collect_attrs(ctx, runtime_classpath, classpath_resources)
147
148    jvm_flags = []
149
150    jvm_flags.extend(launcher_info.jvm_flags)
151
152    native_libs_depsets = []
153    for dep in runtime_deps:
154        if JavaInfo in dep:
155            native_libs_depsets.append(dep[JavaInfo].transitive_native_libraries)
156        if CcInfo in dep:
157            native_libs_depsets.append(dep[CcInfo].transitive_native_libraries())
158    native_libs_dirs = collect_native_deps_dirs(depset(transitive = native_libs_depsets))
159    if native_libs_dirs:
160        prefix = "${JAVA_RUNFILES}/" + ctx.workspace_name + "/"
161        jvm_flags.append("-Djava.library.path=%s" % (
162            ":".join([prefix + d for d in native_libs_dirs])
163        ))
164
165    jvm_flags.extend(ctx.fragments.java.default_jvm_opts)
166    jvm_flags.extend([ctx.expand_make_variables(
167        "jvm_flags",
168        ctx.expand_location(flag, ctx.attr.data, short_paths = True),
169        {},
170    ) for flag in ctx.attr.jvm_flags])
171
172    # TODO(cushon): make string formatting lazier once extend_template support is added
173    # https://github.com/bazelbuild/proposals#:~:text=2022%2D04%2D25,Starlark
174    jvm_flags.extend(["--add-exports=%s=ALL-UNNAMED" % x for x in add_exports.to_list()])
175    jvm_flags.extend(["--add-opens=%s=ALL-UNNAMED" % x for x in add_opens.to_list()])
176
177    files_to_build = []
178
179    if executable:
180        files_to_build.append(executable)
181
182    output_groups = common_info.output_groups
183
184    if coverage_config:
185        _generate_coverage_manifest(ctx, coverage_config.manifest, java_attrs.runtime_classpath)
186        files_to_build.append(coverage_config.manifest)
187
188    if extra_java_info:
189        files_to_build.extend(extra_java_info.runtime_output_jars)
190        output_groups["_direct_source_jars"] = (
191            output_groups["_direct_source_jars"] + extra_java_info.source_jars
192        )
193        output_groups["_source_jars"] = depset(
194            direct = extra_java_info.source_jars,
195            transitive = [output_groups["_source_jars"]],
196        )
197
198    if (ctx.fragments.java.one_version_enforcement_on_java_tests or not is_test_rule_class):
199        one_version_output = _create_one_version_check(ctx, java_attrs.runtime_classpath, is_test_rule_class)
200    else:
201        one_version_output = None
202
203    validation_outputs = [one_version_output] if one_version_output else []
204
205    _create_deploy_sources_jar(ctx, output_groups["_source_jars"])
206
207    files = depset(files_to_build + common_info.files_to_build)
208
209    transitive_runfiles_artifacts = depset(transitive = [
210        files,
211        java_attrs.runtime_classpath,
212        depset(transitive = launcher_info.runfiles),
213    ])
214
215    runfiles = ctx.runfiles(
216        transitive_files = transitive_runfiles_artifacts,
217        collect_default = True,
218    )
219
220    if launcher_info.launcher:
221        default_launcher = helper.filter_launcher_for_target(ctx)
222        default_launcher_artifact = helper.launcher_artifact_for_target(ctx)
223        default_launcher_runfiles = default_launcher[DefaultInfo].default_runfiles
224        if default_launcher_artifact == launcher_info.launcher:
225            runfiles = runfiles.merge(default_launcher_runfiles)
226        else:
227            # N.B. The "default launcher" referred to here is the launcher target specified through
228            # an attribute or flag. We wish to retain the runfiles of the default launcher, *except*
229            # for the original cc_binary artifact, because we've swapped it out with our custom
230            # launcher. Hence, instead of calling builder.addTarget(), or adding an odd method
231            # to Runfiles.Builder, we "unravel" the call and manually add things to the builder.
232            # Because the NestedSet representing each target's launcher runfiles is re-built here,
233            # we may see increased memory consumption for representing the target's runfiles.
234            runfiles = runfiles.merge(
235                ctx.runfiles(
236                    files = [launcher_info.launcher],
237                    transitive_files = depset([
238                        file
239                        for file in default_launcher_runfiles.files.to_list()
240                        if file != default_launcher_artifact
241                    ]),
242                    symlinks = default_launcher_runfiles.symlinks,
243                    root_symlinks = default_launcher_runfiles.root_symlinks,
244                ),
245            )
246
247    runfiles = runfiles.merge_all([
248        dep[DefaultInfo].default_runfiles
249        for dep in ctx.attr.runtime_deps
250        if DefaultInfo in dep
251    ])
252
253    if validation_outputs:
254        output_groups["_validation"] = output_groups.get("_validation", []) + validation_outputs
255
256    _filter_validation_output_group(ctx, output_groups)
257
258    java_binary_info = to_java_binary_info(java_info, compilation_info)
259
260    internal_deploy_jar_info = InternalDeployJarInfo(
261        java_attrs = java_attrs,
262        strip_as_default = strip_as_default,
263        add_exports = add_exports,
264        add_opens = add_opens,
265    )
266
267    # "temporary" workaround for https://github.com/bazelbuild/intellij/issues/5845
268    extra_files = []
269    if is_test_rule_class and ctx.fragments.java.auto_create_java_test_deploy_jars():
270        extra_files.append(_auto_create_deploy_jar(ctx, internal_deploy_jar_info, launcher_info, main_class, coverage_main_class))
271
272    default_info = struct(
273        files = depset(extra_files, transitive = [files]),
274        runfiles = runfiles,
275        executable = executable,
276    )
277
278    return {
279        "OutputGroupInfo": OutputGroupInfo(**output_groups),
280        "JavaInfo": java_binary_info,
281        "InstrumentedFilesInfo": target["InstrumentedFilesInfo"],
282        "JavaRuntimeClasspathInfo": java_common.JavaRuntimeClasspathInfo(runtime_classpath = java_info.transitive_runtime_jars),
283        "InternalDeployJarInfo": internal_deploy_jar_info,
284    }, default_info, jvm_flags
285
286def _collect_attrs(ctx, runtime_classpath, classpath_resources):
287    deploy_env_jars = depset(transitive = [
288        dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath
289        for dep in ctx.attr.deploy_env
290    ]) if hasattr(ctx.attr, "deploy_env") else depset()
291
292    runtime_classpath_for_archive = get_runtime_classpath_for_archive(runtime_classpath, deploy_env_jars)
293    runtime_jars = [ctx.outputs.classjar]
294
295    resources = [p for p in ctx.files.srcs if p.extension == "properties"]
296    transitive_resources = []
297    for r in ctx.attr.resources:
298        transitive_resources.append(
299            r[ProtoInfo].transitive_sources if ProtoInfo in r else r.files,
300        )
301
302    resource_names = dict()
303    for r in classpath_resources:
304        if r.basename in resource_names:
305            fail("entries must have different file names (duplicate: %s)" % r.basename)
306        resource_names[r.basename] = None
307
308    return struct(
309        runtime_jars = depset(runtime_jars),
310        runtime_classpath_for_archive = runtime_classpath_for_archive,
311        classpath_resources = depset(classpath_resources),
312        runtime_classpath = depset(order = "preorder", direct = runtime_jars, transitive = [runtime_classpath]),
313        resources = depset(resources, transitive = transitive_resources),
314    )
315
316def _generate_coverage_manifest(ctx, output, runtime_classpath):
317    ctx.actions.write(
318        output = output,
319        content = "\n".join([file.short_path for file in runtime_classpath.to_list()]),
320    )
321
322def _create_one_version_check(ctx, inputs, is_test_rule_class):
323    one_version_level = ctx.fragments.java.one_version_enforcement_level
324    if one_version_level == "OFF":
325        return None
326    tool = helper.check_and_get_one_version_attribute(ctx, "_one_version_tool")
327
328    if is_test_rule_class:
329        toolchain = semantics.find_java_toolchain(ctx)
330        allowlist = toolchain._one_version_allowlist_for_tests
331    else:
332        allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist")
333
334    if not tool:  # On Mac oneversion tool is not available
335        return None
336
337    output = ctx.actions.declare_file("%s-one-version.txt" % ctx.label.name)
338
339    args = ctx.actions.args()
340    args.set_param_file_format("shell").use_param_file("@%s", use_always = True)
341
342    one_version_inputs = []
343    args.add("--output", output)
344    if allowlist:
345        args.add("--allowlist", allowlist)
346        one_version_inputs.append(allowlist)
347    if one_version_level == "WARNING":
348        args.add("--succeed_on_found_violations")
349    args.add_all(
350        "--inputs",
351        inputs,
352        map_each = helper.jar_and_target_arg_mapper,
353    )
354
355    ctx.actions.run(
356        mnemonic = "JavaOneVersion",
357        progress_message = "Checking for one-version violations in %{label}",
358        executable = tool,
359        toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
360        inputs = depset(one_version_inputs, transitive = [inputs]),
361        tools = [tool],
362        outputs = [output],
363        arguments = [args],
364    )
365
366    return output
367
368def _create_deploy_sources_jar(ctx, sources):
369    helper.create_single_jar(
370        ctx.actions,
371        toolchain = semantics.find_java_toolchain(ctx),
372        output = ctx.outputs.deploysrcjar,
373        sources = sources,
374    )
375
376def _filter_validation_output_group(ctx, output_group):
377    to_exclude = depset(transitive = [
378        dep[OutputGroupInfo]._validation
379        for dep in ctx.attr.deploy_env
380        if OutputGroupInfo in dep and hasattr(dep[OutputGroupInfo], "_validation")
381    ]) if hasattr(ctx.attr, "deploy_env") else depset()
382    if to_exclude:
383        transitive_validations = depset(transitive = [
384            _get_validations_from_attr(ctx, attr_name)
385            for attr_name in dir(ctx.attr)
386            # we also exclude implicit, cfg=host/exec and tool attributes
387            if not attr_name.startswith("_") and
388               attr_name not in [
389                   "deploy_env",
390                   "applicable_licenses",
391                   "package_metadata",
392                   "plugins",
393                   "translations",
394                   # special ignored attributes
395                   "compatible_with",
396                   "restricted_to",
397                   "exec_compatible_with",
398                   "target_compatible_with",
399               ]
400        ])
401        if not ctx.attr.create_executable:
402            excluded_set = {x: None for x in to_exclude.to_list()}
403            transitive_validations = [
404                x
405                for x in transitive_validations.to_list()
406                if x not in excluded_set
407            ]
408        output_group["_validation_transitive"] = transitive_validations
409
410def _get_validations_from_attr(ctx, attr_name):
411    attr = getattr(ctx.attr, attr_name)
412    if type(attr) == "list":
413        return depset(transitive = [_get_validations_from_target(t) for t in attr])
414    else:
415        return _get_validations_from_target(attr)
416
417def _get_validations_from_target(target):
418    if (
419        type(target) == "Target" and
420        OutputGroupInfo in target and
421        hasattr(target[OutputGroupInfo], "_validation")
422    ):
423        return target[OutputGroupInfo]._validation
424    else:
425        return depset()
426
427# TODO: bazelbuild/intellij/issues/5845 - remove this once no longer required
428# this need not be completely identical to the regular deploy jar since we only
429# care about packaging the classpath
430def _auto_create_deploy_jar(ctx, info, launcher_info, main_class, coverage_main_class):
431    output = ctx.actions.declare_file(ctx.label.name + "_auto_deploy.jar")
432    java_attrs = info.java_attrs
433    runtime_classpath = depset(
434        direct = launcher_info.runtime_jars,
435        transitive = [
436            java_attrs.runtime_jars,
437            java_attrs.runtime_classpath_for_archive,
438        ],
439        order = "preorder",
440    )
441    create_deploy_archive(
442        ctx,
443        launcher = launcher_info.launcher,
444        main_class = main_class,
445        coverage_main_class = coverage_main_class,
446        resources = java_attrs.resources,
447        classpath_resources = java_attrs.classpath_resources,
448        runtime_classpath = runtime_classpath,
449        manifest_lines = [],
450        build_info_files = [],
451        build_target = str(ctx.label),
452        output = output,
453        one_version_level = ctx.fragments.java.one_version_enforcement_level,
454        one_version_allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist"),
455        multi_release = ctx.fragments.java.multi_release_deploy_jars,
456        hermetic = hasattr(ctx.attr, "hermetic") and ctx.attr.hermetic,
457        add_exports = info.add_exports,
458        add_opens = info.add_opens,
459    )
460    return output
461