| # Copyright 2026 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Definition of the java_single_jar rule.""" | |
| load("//java/common:java_common.bzl", "java_common") | |
| load("//java/common:java_info.bzl", "JavaInfo") | |
| load("//java/common:java_semantics.bzl", "semantics") | |
| load("//java/common/rules/impl:java_helper.bzl", "helper") | |
| # copybara: default visibility | |
| def _single_jar_inputs(deps, deploy_env): | |
| transitive_inputs = [] | |
| for dep in deps: | |
| if JavaInfo in dep: | |
| info = dep[JavaInfo] | |
| transitive_inputs.append(info.transitive_runtime_jars) | |
| if hasattr(info, "compilation_info"): | |
| compilation_info = info.compilation_info | |
| if hasattr(compilation_info, "runtime_classpath"): | |
| transitive_inputs.append(compilation_info.runtime_classpath) | |
| else: | |
| files = [] | |
| for f in dep[DefaultInfo].files.to_list(): | |
| if not f.extension == "jar": | |
| fail("unexpected file type in java_single_jar.deps: %s" % f.path) | |
| files.append(f) | |
| transitive_inputs.append(depset(files)) | |
| inputs = depset(transitive = transitive_inputs) | |
| if hasattr(java_common, "JavaRuntimeClasspathInfo"): | |
| deploy_env_jars = depset(transitive = [ | |
| dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath | |
| for dep in deploy_env | |
| ]) | |
| excluded_jars = {jar: None for jar in deploy_env_jars.to_list()} | |
| if excluded_jars: | |
| inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars]) | |
| return inputs | |
| def _bazel_java_single_jar_impl(ctx): | |
| inputs = _single_jar_inputs(ctx.attr.deps, ctx.attr.deploy_env) | |
| args = ctx.actions.args() | |
| args.add_all("--sources", inputs) | |
| args.use_param_file("@%s") | |
| args.set_param_file_format("multiline") | |
| args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines) | |
| args.add("--output", ctx.outputs.output) | |
| args.add("--normalize") | |
| # Deal with limitation of singlejar flags: tool's default behavior is | |
| # "no", but you get that behavior only by absence of compression flags. | |
| if ctx.attr.compress == "preserve": | |
| args.add("--dont_change_compression") | |
| elif ctx.attr.compress == "yes": | |
| args.add("--compression") | |
| elif ctx.attr.compress == "no": | |
| pass | |
| else: | |
| fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress) | |
| if ctx.attr.exclude_build_data and ctx.attr.stamp == 1: | |
| fail("Enabling stamping has not effect with exclude_build_data enabled") | |
| build_info_files = [] | |
| if ctx.attr.exclude_build_data: | |
| args.add("--exclude_build_data") | |
| else: | |
| build_info_files = helper.get_build_info(ctx, ctx.attr.stamp) | |
| args.add_all(build_info_files, before_each = "--build_info_file") | |
| if ctx.attr.multi_release: | |
| args.add("--multi_release") | |
| if ctx.attr.exclude_pattern: | |
| args.add("--exclude_pattern", ctx.attr.exclude_pattern) | |
| ctx.actions.run( | |
| inputs = depset(build_info_files, transitive = [inputs]), | |
| outputs = [ctx.outputs.output], | |
| arguments = [args], | |
| progress_message = "Merging into %s" % ctx.outputs.output.short_path, | |
| mnemonic = "JavaSingleJar", | |
| executable = ctx.executable._singlejar, | |
| use_default_shell_env = True, | |
| ) | |
| files = depset([ctx.outputs.output]) | |
| providers = [DefaultInfo( | |
| files = files, | |
| runfiles = ctx.runfiles(transitive_files = files), | |
| )] | |
| if hasattr(java_common, "JavaRuntimeClasspathInfo"): | |
| providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs)) | |
| return providers | |
| bazel_java_single_jar = rule( | |
| attrs = { | |
| "deps": attr.label_list( | |
| allow_files = True, | |
| doc = """ | |
| The Java targets (including java_import and java_library) to collect | |
| transitive dependencies from. Runtime dependencies are collected via | |
| deps, exports, and runtime_deps. Resources are also collected. | |
| Native cc_library or java_wrap_cc dependencies are not.""", | |
| ), | |
| "deploy_manifest_lines": attr.string_list(doc = """ | |
| A list of lines to add to the <code>META-INF/manifest.mf</code> file."""), | |
| "deploy_env": attr.label_list( | |
| providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [], | |
| allow_files = False, | |
| doc = """ | |
| A list of `java_binary` or `java_single_jar` targets which represent | |
| the deployment environment for this binary. | |
| Set this attribute when building a plugin which will be loaded by another | |
| `java_binary`. | |
| `deploy_env` dependencies are excluded from the jar built by this rule.""", | |
| ), | |
| "compress": attr.string(default = "preserve", doc = """ | |
| Whether to always deflate ("yes"), always store ("no"), or pass | |
| through unmodified ("preserve"). The default is "preserve", and is the | |
| most efficient option -- no extra work is done to inflate or deflate."""), | |
| "exclude_build_data": attr.bool(default = True, doc = """ | |
| Whether to omit the build-data.properties file generated | |
| by default."""), | |
| "multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""), | |
| "exclude_pattern": attr.string(default = "", doc = """ | |
| A regex pattern of files to exclude from the jar. | |
| """), | |
| "_singlejar": attr.label( | |
| default = Label("//toolchains:singlejar"), | |
| cfg = "exec", | |
| allow_single_file = True, | |
| executable = True, | |
| ), | |
| "output": attr.output(), | |
| "stamp": attr.int( | |
| doc = """ | |
| Whether to embed extra Bazel build information into the build_data.properties file: | |
| * `stamp = 1`: Always embed Bazel build information, even in `--nostamp` builds. | |
| * `stamp = 0`: Embed Bazel build information with constant values, even in `--stamp` builds. | |
| * `stamp = -1`: Embedding of Bazel build information is controlled by the `--[no]stamp` flag. | |
| Note: whether the output contains the build_data.properties file is controlled | |
| by the `exclude_build_data` attribute. | |
| """, | |
| default = 0, | |
| values = [-1, 0, 1], | |
| ), | |
| "_build_info_translator": attr.label(default = semantics.BUILD_INFO_TRANSLATOR_LABEL), | |
| }, | |
| implementation = _bazel_java_single_jar_impl, | |
| doc = """ | |
| Collects Java dependencies and jar files into a single jar | |
| `java_single_jar` collects Java dependencies and jar files into a single jar. | |
| This is similar to java_binary with everything related to executables disabled, | |
| and provides an alternative to the java_binary "deploy jar hack". | |
| ## Example | |
| ```skylark | |
| load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar") | |
| java_single_jar( | |
| name = "my_single_jar", | |
| deps = [ | |
| "//java/com/google/foo", | |
| "//java/com/google/bar", | |
| ], | |
| ) | |
| ``` | |
| Outputs: | |
| {name}.jar: A single jar containing all of the inputs. | |
| """, | |
| ) |