Skip to content

Commit 4566a5d

Browse files
hvadehrarules_java Copybara
authored andcommitted
Fix java_single_jar compatibility with Bazel 7
Rule extension APIs weren't enabled by default in Bazel 7, so the rule was broken after 37b099c Also add a dummy target under `test/repo` to build as a regression test. Fixes #350 PiperOrigin-RevId: 866808173 Change-Id: I594f0194be02f935154585e68d091172140301e1
1 parent bef4f98 commit 4566a5d

6 files changed

Lines changed: 212 additions & 157 deletions

File tree

java/BUILD

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ bzl_library(
6565
name = "java_single_jar",
6666
srcs = ["java_single_jar.bzl"],
6767
visibility = ["//visibility:public"],
68-
deps = ["//java/common"],
68+
deps = [
69+
"//java/bazel/rules", # copybara-use-repo-external-label
70+
],
6971
)
7072

7173
bzl_library(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2026 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_single_jar rule"""
15+
16+
load("//java/common/rules:java_single_jar.bzl", _bazel_java_single_jar = "bazel_java_single_jar")
17+
18+
def java_single_jar(*, name, **kwargs):
19+
if "output" not in kwargs:
20+
kwargs["output"] = name + ".jar"
21+
_bazel_java_single_jar(name = name, **kwargs)

java/common/rules/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ bzl_library(
3131
visibility = ["//java:__subpackages__"],
3232
)
3333

34+
bzl_library(
35+
name = "java_single_jar_bzl",
36+
srcs = ["java_single_jar.bzl"],
37+
visibility = ["//java:__subpackages__"],
38+
deps = ["//java/common"],
39+
)
40+
3441
bzl_library(
3542
name = "core_rules",
3643
srcs = [
@@ -81,6 +88,7 @@ filegroup(
8188
srcs = [
8289
"BUILD",
8390
":core_rules",
91+
":java_single_jar_bzl",
8492
":toolchain_rules",
8593
"//java/common/rules/impl:for_bazel_tests",
8694
"@rules_cc//cc/private/rules_impl:srcs",
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Copyright 2026 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+
"""Definition of the java_single_jar rule."""
15+
16+
load("//java/common:java_common.bzl", "java_common")
17+
load("//java/common:java_info.bzl", "JavaInfo")
18+
19+
# copybara: default visibility
20+
21+
def _single_jar_inputs(deps, deploy_env):
22+
transitive_inputs = []
23+
for dep in deps:
24+
if JavaInfo in dep:
25+
info = dep[JavaInfo]
26+
transitive_inputs.append(info.transitive_runtime_jars)
27+
if hasattr(info, "compilation_info"):
28+
compilation_info = info.compilation_info
29+
if hasattr(compilation_info, "runtime_classpath"):
30+
transitive_inputs.append(compilation_info.runtime_classpath)
31+
else:
32+
files = []
33+
for f in dep[DefaultInfo].files.to_list():
34+
if not f.extension == "jar":
35+
fail("unexpected file type in java_single_jar.deps: %s" % f.path)
36+
files.append(f)
37+
transitive_inputs.append(depset(files))
38+
inputs = depset(transitive = transitive_inputs)
39+
40+
if hasattr(java_common, "JavaRuntimeClasspathInfo"):
41+
deploy_env_jars = depset(transitive = [
42+
dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath
43+
for dep in deploy_env
44+
])
45+
excluded_jars = {jar: None for jar in deploy_env_jars.to_list()}
46+
if excluded_jars:
47+
inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars])
48+
return inputs
49+
50+
def _bazel_java_single_jar_impl(ctx):
51+
inputs = _single_jar_inputs(ctx.attr.deps, ctx.attr.deploy_env)
52+
53+
args = ctx.actions.args()
54+
args.add_all("--sources", inputs)
55+
args.use_param_file("@%s")
56+
args.set_param_file_format("multiline")
57+
args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines)
58+
args.add("--output", ctx.outputs.output)
59+
args.add("--normalize")
60+
61+
# Deal with limitation of singlejar flags: tool's default behavior is
62+
# "no", but you get that behavior only by absence of compression flags.
63+
if ctx.attr.compress == "preserve":
64+
args.add("--dont_change_compression")
65+
elif ctx.attr.compress == "yes":
66+
args.add("--compression")
67+
elif ctx.attr.compress == "no":
68+
pass
69+
else:
70+
fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress)
71+
72+
if ctx.attr.exclude_build_data:
73+
args.add("--exclude_build_data")
74+
if ctx.attr.multi_release:
75+
args.add("--multi_release")
76+
77+
if ctx.attr.exclude_pattern:
78+
args.add("--exclude_pattern", ctx.attr.exclude_pattern)
79+
80+
ctx.actions.run(
81+
inputs = inputs,
82+
outputs = [ctx.outputs.output],
83+
arguments = [args],
84+
progress_message = "Merging into %s" % ctx.outputs.output.short_path,
85+
mnemonic = "JavaSingleJar",
86+
executable = ctx.executable._singlejar,
87+
use_default_shell_env = True,
88+
)
89+
90+
files = depset([ctx.outputs.output])
91+
providers = [DefaultInfo(
92+
files = files,
93+
runfiles = ctx.runfiles(transitive_files = files),
94+
)]
95+
if hasattr(java_common, "JavaRuntimeClasspathInfo"):
96+
providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs))
97+
return providers
98+
99+
bazel_java_single_jar = rule(
100+
attrs = {
101+
"deps": attr.label_list(
102+
allow_files = True,
103+
doc = """
104+
The Java targets (including java_import and java_library) to collect
105+
transitive dependencies from. Runtime dependencies are collected via
106+
deps, exports, and runtime_deps. Resources are also collected.
107+
Native cc_library or java_wrap_cc dependencies are not.""",
108+
),
109+
"deploy_manifest_lines": attr.string_list(doc = """
110+
A list of lines to add to the <code>META-INF/manifest.mf</code> file."""),
111+
"deploy_env": attr.label_list(
112+
providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [],
113+
allow_files = False,
114+
doc = """
115+
A list of `java_binary` or `java_single_jar` targets which represent
116+
the deployment environment for this binary.
117+
118+
Set this attribute when building a plugin which will be loaded by another
119+
`java_binary`.
120+
121+
`deploy_env` dependencies are excluded from the jar built by this rule.""",
122+
),
123+
"compress": attr.string(default = "preserve", doc = """
124+
Whether to always deflate ("yes"), always store ("no"), or pass
125+
through unmodified ("preserve"). The default is "preserve", and is the
126+
most efficient option -- no extra work is done to inflate or deflate."""),
127+
"exclude_build_data": attr.bool(default = True, doc = """
128+
Whether to omit the build-data.properties file generated
129+
by default."""),
130+
"multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""),
131+
"exclude_pattern": attr.string(default = "", doc = """
132+
A regex pattern of files to exclude from the jar.
133+
"""),
134+
"_singlejar": attr.label(
135+
default = Label("//toolchains:singlejar"),
136+
cfg = "exec",
137+
allow_single_file = True,
138+
executable = True,
139+
),
140+
"output": attr.output(),
141+
},
142+
implementation = _bazel_java_single_jar_impl,
143+
doc = """
144+
Collects Java dependencies and jar files into a single jar
145+
146+
`java_single_jar` collects Java dependencies and jar files into a single jar.
147+
This is similar to java_binary with everything related to executables disabled,
148+
and provides an alternative to the java_binary "deploy jar hack".
149+
150+
## Example
151+
152+
```skylark
153+
load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar")
154+
155+
java_single_jar(
156+
name = "my_single_jar",
157+
deps = [
158+
"//java/com/google/foo",
159+
"//java/com/google/bar",
160+
],
161+
)
162+
```
163+
164+
Outputs:
165+
{name}.jar: A single jar containing all of the inputs.
166+
""",
167+
)

java/java_single_jar.bzl

Lines changed: 3 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,5 @@
1-
""" Definition of _java_single_jar. """
1+
"""The java_single_jar rule"""
22

3-
load("//java/common:java_common.bzl", "java_common")
4-
load("//java/common:java_info.bzl", "JavaInfo")
3+
load("//java/bazel/rules:bazel_java_single_jar.bzl", _java_single_jar = "java_single_jar")
54

6-
def _single_jar_inputs(deps, deploy_env):
7-
transitive_inputs = []
8-
for dep in deps:
9-
if JavaInfo in dep:
10-
info = dep[JavaInfo]
11-
transitive_inputs.append(info.transitive_runtime_jars)
12-
if hasattr(info, "compilation_info"):
13-
compilation_info = info.compilation_info
14-
if hasattr(compilation_info, "runtime_classpath"):
15-
transitive_inputs.append(compilation_info.runtime_classpath)
16-
else:
17-
files = []
18-
for f in dep[DefaultInfo].files.to_list():
19-
if not f.extension == "jar":
20-
fail("unexpected file type in java_single_jar.deps: %s" % f.path)
21-
files.append(f)
22-
transitive_inputs.append(depset(files))
23-
inputs = depset(transitive = transitive_inputs)
24-
25-
if hasattr(java_common, "JavaRuntimeClasspathInfo"):
26-
deploy_env_jars = depset(transitive = [
27-
dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath
28-
for dep in deploy_env
29-
])
30-
excluded_jars = {jar: None for jar in deploy_env_jars.to_list()}
31-
if excluded_jars:
32-
inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars])
33-
return inputs
34-
35-
def _java_single_jar(ctx):
36-
inputs = _single_jar_inputs(ctx.attr.deps, ctx.attr.deploy_env)
37-
38-
args = ctx.actions.args()
39-
args.add_all("--sources", inputs)
40-
args.use_param_file("@%s")
41-
args.set_param_file_format("multiline")
42-
args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines)
43-
args.add("--output", ctx.outputs.output)
44-
args.add("--normalize")
45-
46-
# Deal with limitation of singlejar flags: tool's default behavior is
47-
# "no", but you get that behavior only by absence of compression flags.
48-
if ctx.attr.compress == "preserve":
49-
args.add("--dont_change_compression")
50-
elif ctx.attr.compress == "yes":
51-
args.add("--compression")
52-
elif ctx.attr.compress == "no":
53-
pass
54-
else:
55-
fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress)
56-
57-
if ctx.attr.exclude_build_data:
58-
args.add("--exclude_build_data")
59-
if ctx.attr.multi_release:
60-
args.add("--multi_release")
61-
62-
if ctx.attr.exclude_pattern:
63-
args.add("--exclude_pattern", ctx.attr.exclude_pattern)
64-
65-
ctx.actions.run(
66-
inputs = inputs,
67-
outputs = [ctx.outputs.output],
68-
arguments = [args],
69-
progress_message = "Merging into %s" % ctx.outputs.output.short_path,
70-
mnemonic = "JavaSingleJar",
71-
executable = ctx.executable._singlejar,
72-
use_default_shell_env = True,
73-
)
74-
75-
files = depset([ctx.outputs.output])
76-
providers = [DefaultInfo(
77-
files = files,
78-
runfiles = ctx.runfiles(transitive_files = files),
79-
)]
80-
if hasattr(java_common, "JavaRuntimeClasspathInfo"):
81-
providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs))
82-
return providers
83-
84-
def _init(name, **kwargs):
85-
if "output" not in kwargs:
86-
kwargs["output"] = name + ".jar"
87-
return kwargs
88-
89-
java_single_jar = rule(
90-
attrs = {
91-
"deps": attr.label_list(
92-
allow_files = True,
93-
doc = """
94-
The Java targets (including java_import and java_library) to collect
95-
transitive dependencies from. Runtime dependencies are collected via
96-
deps, exports, and runtime_deps. Resources are also collected.
97-
Native cc_library or java_wrap_cc dependencies are not.""",
98-
),
99-
"deploy_manifest_lines": attr.string_list(doc = """
100-
A list of lines to add to the <code>META-INF/manifest.mf</code> file."""),
101-
"deploy_env": attr.label_list(
102-
providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [],
103-
allow_files = False,
104-
doc = """
105-
A list of `java_binary` or `java_single_jar` targets which represent
106-
the deployment environment for this binary.
107-
108-
Set this attribute when building a plugin which will be loaded by another
109-
`java_binary`.
110-
111-
`deploy_env` dependencies are excluded from the jar built by this rule.""",
112-
),
113-
"compress": attr.string(default = "preserve", doc = """
114-
Whether to always deflate ("yes"), always store ("no"), or pass
115-
through unmodified ("preserve"). The default is "preserve", and is the
116-
most efficient option -- no extra work is done to inflate or deflate."""),
117-
"exclude_build_data": attr.bool(default = True, doc = """
118-
Whether to omit the build-data.properties file generated
119-
by default."""),
120-
"multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""),
121-
"exclude_pattern": attr.string(default = "", doc = """
122-
A regex pattern of files to exclude from the jar.
123-
"""),
124-
"_singlejar": attr.label(
125-
default = Label("//toolchains:singlejar"),
126-
cfg = "exec",
127-
allow_single_file = True,
128-
executable = True,
129-
),
130-
"output": attr.output(),
131-
},
132-
initializer = _init,
133-
implementation = _java_single_jar,
134-
doc = """
135-
Collects Java dependencies and jar files into a single jar
136-
137-
`java_single_jar` collects Java dependencies and jar files into a single jar.
138-
This is similar to java_binary with everything related to executables disabled,
139-
and provides an alternative to the java_binary "deploy jar hack".
140-
141-
## Example
142-
143-
```skylark
144-
load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar")
145-
146-
java_single_jar(
147-
name = "my_single_jar",
148-
deps = [
149-
"//java/com/google/foo",
150-
"//java/com/google/bar",
151-
],
152-
)
153-
```
154-
155-
Outputs:
156-
{name}.jar: A single jar containing all of the inputs.
157-
""",
158-
)
5+
java_single_jar = _java_single_jar

0 commit comments

Comments
 (0)