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