Skip to content

Commit 0feeb24

Browse files
committed
feat: expose stable per-layer image targets
Add fixed layer_N output groups and filegroups so image rules can map each generated tar to an individual image layer. Pad unused slots with deterministic empty tar archives. Mark the Go SDK configuration as development-only and test the BCR example with Bazel 9.
1 parent bb6ee91 commit 0feeb24

5 files changed

Lines changed: 64 additions & 5 deletions

File tree

‎.bcr/presubmit.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ bcr_test_module:
22
module_path: "example/hello"
33
matrix:
44
platform: ["macos", "ubuntu2204"]
5-
bazel: ["8.*"]
5+
bazel: ["8.*", "9.*"]
66
tasks:
77
build_example:
88
name: "Build example image"

‎MODULE.bazel‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ bazel_dep(name = "gazelle", version = "0.47.0", dev_dependency = True)
1010
bazel_dep(name = "rules_go", version = "0.59.0")
1111
bazel_dep(name = "rules_java", version = "8.14.0")
1212

13-
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
13+
go_sdk = use_extension(
14+
"@rules_go//go:extensions.bzl",
15+
"go_sdk",
16+
dev_dependency = True,
17+
)
1418
go_sdk.from_file(
1519
go_mod = "//:go.mod",
1620
)

‎cmd/jar_layerer/main.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ func main() {
3232
flag.Var(&artifactLayers, "artifact_layer", "ARTIFACT_ID=path.tar (repeatable)")
3333
var artifactGroupLayers repeatedFlag
3434
flag.Var(&artifactGroupLayers, "artifact_group_layer", "ID1,ID2,...=path.tar (repeatable)")
35+
var padLayers repeatedFlag
36+
flag.Var(&padLayers, "pad_layer", "path to write an empty layer tar (repeatable)")
3537

3638
flag.Parse()
3739

@@ -124,4 +126,11 @@ func main() {
124126
os.Exit(1)
125127
}
126128
}
129+
130+
for _, path := range padLayers {
131+
if err := jarlayer.WriteEmptyTar(path); err != nil {
132+
fmt.Fprintf(os.Stderr, "writing pad layer: %v\n", err)
133+
os.Exit(1)
134+
}
135+
}
127136
}

‎jvm_jar_layers.bzl‎

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ def _group_artifacts(artifact_ids, max_groups):
154154
# max_groups is 0: no artifact layers at all.
155155
return []
156156

157+
# Non-maven layer slots: the data-runfiles tar and the fallback tar.
158+
_EXTRA_LAYER_SLOTS = 2
159+
160+
def jvm_jar_layer_slots(max_layers):
161+
"""Number of `<name>.layer_N` filegroups emitted for a given max_layers."""
162+
return max_layers + _EXTRA_LAYER_SLOTS
163+
157164
def jvm_jar_layers(
158165
name,
159166
binary,
@@ -172,6 +179,13 @@ def jvm_jar_layers(
172179
The container classpath uses Java's @file syntax to reference a classpath
173180
file listing all JARs.
174181
182+
Because the number and names of the layer tars are only known at analysis
183+
time, each tar is also exposed through a fixed-name `<name>.layer_N`
184+
filegroup (N in range(jvm_jar_layer_slots(max_layers))) so image rules can
185+
map each tar to its own image layer. Slots beyond the produced tar count
186+
are padded with empty tars, which compress to byte-identical, deduplicable
187+
layer blobs.
188+
175189
Args:
176190
name: target name
177191
binary: label of a java_binary or scala_binary target
@@ -201,6 +215,13 @@ def jvm_jar_layers(
201215
**kwargs
202216
)
203217

218+
for index in range(jvm_jar_layer_slots(max_layers)):
219+
native.filegroup(
220+
name = "%s.layer_%d" % (name, index),
221+
srcs = [name],
222+
output_group = "layer_%d" % index,
223+
)
224+
204225
def _jvm_jar_layers_impl(ctx):
205226
runtime_jars = _runtime_jars(ctx.attr.binary)
206227
if not runtime_jars:
@@ -289,6 +310,14 @@ def _jvm_jar_layers_impl(ctx):
289310
args.add("--artifact_group_layer", ",".join(group_ids) + "=" + group_out.path)
290311
tar_outputs.append(group_out)
291312

313+
# Pad remaining slots with empty tars so every layer_N output group (and
314+
# its filegroup) yields exactly one tar file — image rules typically reject
315+
# labels that produce no tar.
316+
for index in range(len(tar_outputs), jvm_jar_layer_slots(ctx.attr.max_layers)):
317+
pad = ctx.actions.declare_file(ctx.label.name + ".pad_%d.tar" % index)
318+
args.add("--pad_layer", pad)
319+
tar_outputs.append(pad)
320+
292321
ctx.actions.run(
293322
inputs = inputs,
294323
outputs = tar_outputs + [classpath_file],
@@ -300,11 +329,14 @@ def _jvm_jar_layers_impl(ctx):
300329

301330
# DefaultInfo only includes tar files — the classpath file is a plain text
302331
# file and must not be passed to container_image's tars attribute.
332+
output_groups = {"classpath": depset([classpath_file])}
333+
for index in range(jvm_jar_layer_slots(ctx.attr.max_layers)):
334+
files = [tar_outputs[index]] if index < len(tar_outputs) else []
335+
output_groups["layer_%d" % index] = depset(files)
336+
303337
return [
304338
DefaultInfo(files = depset(tar_outputs)),
305-
OutputGroupInfo(
306-
classpath = depset([classpath_file]),
307-
),
339+
OutputGroupInfo(**output_groups),
308340
]
309341

310342
_jvm_jar_layers = rule(

‎pkg/jarlayer/jarlayer.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,20 @@ func LayerJars(opts LayerOptions) (retErr error) {
190190
return nil
191191
}
192192

193+
// WriteEmptyTar writes a valid tar archive containing no entries. Pad layers
194+
// use it so every declared layer slot yields a byte-identical, deduplicable
195+
// blob.
196+
func WriteEmptyTar(outputPath string) (retErr error) {
197+
lw, err := newLayerWriter(outputPath)
198+
if err != nil {
199+
return fmt.Errorf("creating pad layer: %w", err)
200+
}
201+
if closeErr := lw.Close(); closeErr != nil {
202+
return fmt.Errorf("closing pad layer: %w", closeErr)
203+
}
204+
return nil
205+
}
206+
193207
// LayerData writes files into a deterministic tar using their Bazel runfiles
194208
// paths. Directories are expanded recursively and symlinks are rejected.
195209
func LayerData(outputPath string, files []DataFile) (retErr error) {

0 commit comments

Comments
 (0)