• 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"""Bazel java_binary rule"""
15
16load("@bazel_skylib//lib:paths.bzl", "paths")
17load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain")
18load("//java/common:java_semantics.bzl", "semantics")
19load(
20    "//java/common/rules:android_lint.bzl",
21    "android_lint_subrule",
22)
23load("//java/common/rules:java_binary.bzl", "BASIC_JAVA_BINARY_ATTRIBUTES")
24load("//java/common/rules:rule_util.bzl", "merge_attrs")
25load("//java/common/rules/impl:java_binary_deploy_jar.bzl", "create_deploy_archives")
26load("//java/common/rules/impl:java_binary_impl.bzl", "basic_java_binary")
27load("//java/common/rules/impl:java_helper.bzl", "helper")
28load("//java/private:java_info.bzl", "JavaInfo")
29
30def _bazel_java_binary_impl(ctx):
31    return bazel_base_binary_impl(ctx, is_test_rule_class = False) + helper.executable_providers(ctx)
32
33def bazel_base_binary_impl(ctx, is_test_rule_class):
34    """Common implementation for binaries and tests
35
36    Args:
37        ctx: (RuleContext)
38        is_test_rule_class: (bool)
39
40    Returns:
41        [Provider]
42    """
43    deps = _collect_all_targets_as_deps(ctx, classpath_type = "compile_only")
44    runtime_deps = _collect_all_targets_as_deps(ctx)
45
46    main_class = _check_and_get_main_class(ctx)
47    coverage_main_class = main_class
48    coverage_config = helper.get_coverage_config(ctx, _get_coverage_runner(ctx))
49    if coverage_config:
50        main_class = coverage_config.main_class
51
52    launcher_info = _get_launcher_info(ctx)
53
54    executable = _get_executable(ctx)
55
56    feature_config = helper.get_feature_config(ctx)
57    if feature_config:
58        strip_as_default = helper.should_strip_as_default(ctx, feature_config)
59    else:
60        # No C++ toolchain available.
61        strip_as_default = False
62
63    providers, default_info, jvm_flags = basic_java_binary(
64        ctx,
65        deps,
66        runtime_deps,
67        ctx.files.resources,
68        main_class,
69        coverage_main_class,
70        coverage_config,
71        launcher_info,
72        executable,
73        strip_as_default,
74        is_test_rule_class = is_test_rule_class,
75    )
76
77    if ctx.attr.use_testrunner:
78        if semantics.find_java_runtime_toolchain(ctx).version >= 17:
79            jvm_flags.append("-Djava.security.manager=allow")
80        test_class = ctx.attr.test_class if hasattr(ctx.attr, "test_class") else ""
81        if test_class == "":
82            test_class = helper.primary_class(ctx)
83        if test_class == None:
84            fail("cannot determine test class. You might want to rename the " +
85                 " rule or add a 'test_class' attribute.")
86        jvm_flags.extend([
87            "-ea",
88            "-Dbazel.test_suite=" + helper.shell_escape(test_class),
89        ])
90
91    java_attrs = providers["InternalDeployJarInfo"].java_attrs
92
93    if executable:
94        _create_stub(ctx, java_attrs, launcher_info.launcher, executable, jvm_flags, main_class, coverage_main_class)
95
96    runfiles = default_info.runfiles
97
98    if executable:
99        runtime_toolchain = semantics.find_java_runtime_toolchain(ctx)
100        runfiles = runfiles.merge(ctx.runfiles(transitive_files = runtime_toolchain.files))
101
102    test_support = helper.get_test_support(ctx)
103    if test_support:
104        runfiles = runfiles.merge(test_support[DefaultInfo].default_runfiles)
105
106    providers["DefaultInfo"] = DefaultInfo(
107        files = default_info.files,
108        runfiles = runfiles,
109        executable = default_info.executable,
110    )
111
112    info = providers.pop("InternalDeployJarInfo")
113    create_deploy_archives(
114        ctx,
115        info.java_attrs,
116        launcher_info,
117        main_class,
118        coverage_main_class,
119        info.strip_as_default,
120        add_exports = info.add_exports,
121        add_opens = info.add_opens,
122    )
123
124    return providers.values()
125
126def _get_coverage_runner(ctx):
127    if ctx.configuration.coverage_enabled and ctx.attr.create_executable:
128        toolchain = semantics.find_java_toolchain(ctx)
129        runner = toolchain.jacocorunner
130        if not runner:
131            fail("jacocorunner not set in java_toolchain: %s" % toolchain.label)
132        runner_jar = runner.executable
133
134        # wrap the jar in JavaInfo so we can add it to deps for java_common.compile()
135        return JavaInfo(output_jar = runner_jar, compile_jar = runner_jar)
136
137    return None
138
139def _collect_all_targets_as_deps(ctx, classpath_type = "all"):
140    deps = helper.collect_all_targets_as_deps(ctx, classpath_type = classpath_type)
141
142    if classpath_type == "compile_only" and ctx.fragments.java.enforce_explicit_java_test_deps():
143        return deps
144
145    test_support = helper.get_test_support(ctx)
146    if test_support:
147        deps.append(test_support)
148    return deps
149
150def _check_and_get_main_class(ctx):
151    create_executable = ctx.attr.create_executable
152    main_class = _get_main_class(ctx)
153
154    if not create_executable and main_class:
155        fail("main class must not be specified when executable is not created")
156    if create_executable and not main_class:
157        if not ctx.attr.srcs:
158            fail("need at least one of 'main_class' or Java source files")
159        main_class = helper.primary_class(ctx)
160        if main_class == None:
161            fail("main_class was not provided and cannot be inferred: " +
162                 "source path doesn't include a known root (java, javatests, src, testsrc)")
163
164    return _get_main_class(ctx)
165
166def _get_main_class(ctx):
167    if not ctx.attr.create_executable:
168        return None
169
170    main_class = _get_main_class_from_rule(ctx)
171
172    if main_class == "":
173        main_class = helper.primary_class(ctx)
174    return main_class
175
176def _get_main_class_from_rule(ctx):
177    main_class = ctx.attr.main_class
178    if main_class:
179        return main_class
180    if ctx.attr.use_testrunner:
181        return "com.google.testing.junit.runner.BazelTestRunner"
182    return main_class
183
184def _get_launcher_info(ctx):
185    launcher = helper.launcher_artifact_for_target(ctx)
186    return struct(
187        launcher = launcher,
188        unstripped_launcher = launcher,
189        runfiles = [],
190        runtime_jars = [],
191        jvm_flags = [],
192        classpath_resources = [],
193    )
194
195def _get_executable(ctx):
196    if not ctx.attr.create_executable:
197        return None
198    executable_name = ctx.label.name
199    if helper.is_target_platform_windows(ctx):
200        executable_name = executable_name + ".exe"
201
202    return ctx.actions.declare_file(executable_name)
203
204def _create_stub(ctx, java_attrs, launcher, executable, jvm_flags, main_class, coverage_main_class):
205    java_runtime_toolchain = semantics.find_java_runtime_toolchain(ctx)
206    java_executable = helper.get_java_executable(ctx, java_runtime_toolchain, launcher)
207    workspace_name = ctx.workspace_name
208    workspace_prefix = workspace_name + ("/" if workspace_name else "")
209    runfiles_enabled = helper.runfiles_enabled(ctx)
210    coverage_enabled = ctx.configuration.coverage_enabled
211
212    test_support = helper.get_test_support(ctx)
213    test_support_jars = test_support[JavaInfo].transitive_runtime_jars if test_support else depset()
214    classpath = depset(
215        transitive = [
216            java_attrs.runtime_classpath,
217            test_support_jars if ctx.fragments.java.enforce_explicit_java_test_deps() else depset(),
218        ],
219    )
220
221    if helper.is_target_platform_windows(ctx):
222        jvm_flags_for_launcher = []
223        for flag in jvm_flags:
224            jvm_flags_for_launcher.extend(ctx.tokenize(flag))
225        return _create_windows_exe_launcher(ctx, java_executable, classpath, main_class, jvm_flags_for_launcher, runfiles_enabled, executable)
226
227    if runfiles_enabled:
228        prefix = "" if helper.is_absolute_target_platform_path(ctx, java_executable) else "${JAVA_RUNFILES}/"
229        java_bin = "JAVABIN=${JAVABIN:-" + prefix + java_executable + "}"
230    else:
231        java_bin = "JAVABIN=${JAVABIN:-$(rlocation " + java_executable + ")}"
232
233    td = ctx.actions.template_dict()
234    td.add_joined(
235        "%classpath%",
236        classpath,
237        map_each = lambda file: _format_classpath_entry(runfiles_enabled, workspace_prefix, file),
238        join_with = ctx.configuration.host_path_separator,
239        format_joined = "\"%s\"",
240        allow_closure = True,
241    )
242
243    ctx.actions.expand_template(
244        template = ctx.file._stub_template,
245        output = executable,
246        substitutions = {
247            "%runfiles_manifest_only%": "" if runfiles_enabled else "1",
248            "%workspace_prefix%": workspace_prefix,
249            "%javabin%": java_bin,
250            "%needs_runfiles%": "0" if helper.is_absolute_target_platform_path(ctx, java_runtime_toolchain.java_executable_exec_path) else "1",
251            "%set_jacoco_metadata%": "",
252            "%set_jacoco_main_class%": "export JACOCO_MAIN_CLASS=" + coverage_main_class if coverage_enabled else "",
253            "%set_jacoco_java_runfiles_root%": "export JACOCO_JAVA_RUNFILES_ROOT=${JAVA_RUNFILES}/" + workspace_prefix if coverage_enabled else "",
254            "%java_start_class%": helper.shell_escape(main_class),
255            "%jvm_flags%": " ".join(jvm_flags),
256        },
257        computed_substitutions = td,
258        is_executable = True,
259    )
260    return executable
261
262def _format_classpath_entry(runfiles_enabled, workspace_prefix, file):
263    if runfiles_enabled:
264        return "${RUNPATH}" + file.short_path
265
266    return "$(rlocation " + paths.normalize(workspace_prefix + file.short_path) + ")"
267
268def _create_windows_exe_launcher(ctx, java_executable, classpath, main_class, jvm_flags_for_launcher, runfiles_enabled, executable):
269    launch_info = ctx.actions.args().use_param_file("%s", use_always = True).set_param_file_format("multiline")
270    launch_info.add("binary_type=Java")
271    launch_info.add(ctx.workspace_name, format = "workspace_name=%s")
272    launch_info.add("1" if runfiles_enabled else "0", format = "symlink_runfiles_enabled=%s")
273    launch_info.add(java_executable, format = "java_bin_path=%s")
274    launch_info.add(main_class, format = "java_start_class=%s")
275    launch_info.add_joined(classpath, map_each = _short_path, join_with = ";", format_joined = "classpath=%s", omit_if_empty = False)
276    launch_info.add_joined(jvm_flags_for_launcher, join_with = "\t", format_joined = "jvm_flags=%s", omit_if_empty = False)
277    launch_info.add(semantics.find_java_runtime_toolchain(ctx).java_home_runfiles_path, format = "jar_bin_path=%s/bin/jar.exe")
278
279    # TODO(b/295221112): Change to use the "launcher" attribute (only windows use a fixed _launcher attribute)
280    launcher_artifact = ctx.executable._launcher
281    ctx.actions.run(
282        executable = ctx.executable._windows_launcher_maker,
283        inputs = [launcher_artifact],
284        outputs = [executable],
285        arguments = [launcher_artifact.path, launch_info, executable.path],
286        use_default_shell_env = True,
287    )
288    return executable
289
290def _short_path(file):
291    return file.short_path
292
293def _compute_test_support(use_testrunner):
294    return Label(semantics.JAVA_TEST_RUNNER_LABEL) if use_testrunner else None
295
296def make_binary_rule(implementation, *, doc, attrs, executable = False, test = False, initializer = None):
297    return rule(
298        implementation = implementation,
299        initializer = initializer,
300        doc = doc,
301        attrs = attrs,
302        executable = executable,
303        test = test,
304        fragments = ["cpp", "java"],
305        provides = [JavaInfo],
306        toolchains = [semantics.JAVA_TOOLCHAIN] + use_cc_toolchain() + (
307            [semantics.JAVA_RUNTIME_TOOLCHAIN] if executable or test else []
308        ),
309        # TODO(hvd): replace with filegroups?
310        outputs = {
311            "classjar": "%{name}.jar",
312            "sourcejar": "%{name}-src.jar",
313            "deploysrcjar": "%{name}_deploy-src.jar",
314            "deployjar": "%{name}_deploy.jar",
315            "unstrippeddeployjar": "%{name}_deploy.jar.unstripped",
316        },
317        exec_groups = {
318            "cpp_link": exec_group(toolchains = use_cc_toolchain()),
319        },
320        subrules = [android_lint_subrule],
321    )
322
323BASE_BINARY_ATTRS = merge_attrs(
324    BASIC_JAVA_BINARY_ATTRIBUTES,
325    {
326        "resource_strip_prefix": attr.string(
327            doc = """
328The path prefix to strip from Java resources.
329<p>
330If specified, this path prefix is stripped from every file in the <code>resources</code>
331attribute. It is an error for a resource file not to be under this directory. If not
332specified (the default), the path of resource file is determined according to the same
333logic as the Java package of source files. For example, a source file at
334<code>stuff/java/foo/bar/a.txt</code> will be located at <code>foo/bar/a.txt</code>.
335</p>
336            """,
337        ),
338        "_test_support": attr.label(default = _compute_test_support),
339        "_launcher": attr.label(
340            cfg = "exec",
341            executable = True,
342            default = "@bazel_tools//tools/launcher:launcher",
343        ),
344        "_windows_launcher_maker": attr.label(
345            default = "@bazel_tools//tools/launcher:launcher_maker",
346            cfg = "exec",
347            executable = True,
348        ),
349    },
350)
351
352def make_java_binary(executable):
353    return make_binary_rule(
354        _bazel_java_binary_impl,
355        doc = """
356<p>
357  Builds a Java archive ("jar file"), plus a wrapper shell script with the same name as the rule.
358  The wrapper shell script uses a classpath that includes, among other things, a jar file for each
359  library on which the binary depends. When running the wrapper shell script, any nonempty
360  <code>JAVABIN</code> environment variable will take precedence over the version specified via
361  Bazel's <code>--java_runtime_version</code> flag.
362</p>
363<p>
364  The wrapper script accepts several unique flags. Refer to
365  <code>//src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt</code>
366  for a list of configurable flags and environment variables accepted by the wrapper.
367</p>
368
369<h4 id="java_binary_implicit_outputs">Implicit output targets</h4>
370<ul>
371  <li><code><var>name</var>.jar</code>: A Java archive, containing the class files and other
372    resources corresponding to the binary's direct dependencies.</li>
373  <li><code><var>name</var>-src.jar</code>: An archive containing the sources ("source
374    jar").</li>
375  <li><code><var>name</var>_deploy.jar</code>: A Java archive suitable for deployment (only
376    built if explicitly requested).
377    <p>
378      Building the <code>&lt;<var>name</var>&gt;_deploy.jar</code> target for your rule
379      creates a self-contained jar file with a manifest that allows it to be run with the
380      <code>java -jar</code> command or with the wrapper script's <code>--singlejar</code>
381      option. Using the wrapper script is preferred to <code>java -jar</code> because it
382      also passes the <a href="#java_binary-jvm_flags">JVM flags</a> and the options
383      to load native libraries.
384    </p>
385    <p>
386      The deploy jar contains all the classes that would be found by a classloader that
387      searched the classpath from the binary's wrapper script from beginning to end. It also
388      contains the native libraries needed for dependencies. These are automatically loaded
389      into the JVM at runtime.
390    </p>
391    <p>If your target specifies a <a href="#java_binary.launcher">launcher</a>
392      attribute, then instead of being a normal JAR file, the _deploy.jar will be a
393      native binary. This will contain the launcher plus any native (C++) dependencies of
394      your rule, all linked into a static binary. The actual jar file's bytes will be
395      appended to that native binary, creating a single binary blob containing both the
396      executable and the Java code. You can execute the resulting jar file directly
397      like you would execute any native binary.</p>
398  </li>
399  <li><code><var>name</var>_deploy-src.jar</code>: An archive containing the sources
400    collected from the transitive closure of the target. These will match the classes in the
401    <code>deploy.jar</code> except where jars have no matching source jar.</li>
402</ul>
403
404<p>
405It is good practice to use the name of the source file that is the main entry point of the
406application (minus the extension). For example, if your entry point is called
407<code>Main.java</code>, then your name could be <code>Main</code>.
408</p>
409
410<p>
411  A <code>deps</code> attribute is not allowed in a <code>java_binary</code> rule without
412  <a href="#java_binary-srcs"><code>srcs</code></a>; such a rule requires a
413  <a href="#java_binary-main_class"><code>main_class</code></a> provided by
414  <a href="#java_binary-runtime_deps"><code>runtime_deps</code></a>.
415</p>
416
417<p>The following code snippet illustrates a common mistake:</p>
418
419<pre class="code">
420<code class="lang-starlark">
421java_binary(
422    name = "DontDoThis",
423    srcs = [
424        <var>...</var>,
425        <code class="deprecated">"GeneratedJavaFile.java"</code>,  # a generated .java file
426    ],
427    deps = [<code class="deprecated">":generating_rule",</code>],  # rule that generates that file
428)
429</code>
430</pre>
431
432<p>Do this instead:</p>
433
434<pre class="code">
435<code class="lang-starlark">
436java_binary(
437    name = "DoThisInstead",
438    srcs = [
439        <var>...</var>,
440        ":generating_rule",
441    ],
442)
443</code>
444</pre>
445        """,
446        attrs = merge_attrs(
447            BASE_BINARY_ATTRS,
448            ({} if executable else {
449                "args": attr.string_list(),
450                "output_licenses": attr.string_list(),
451            }),
452        ),
453        executable = executable,
454    )
455
456java_binary = make_java_binary(executable = True)
457