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><<var>name</var>>_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