Skip to content

Commit 3acbce1

Browse files
authored
Merge pull request #6 from stackb/stable-per-layer-image-targets
feat: expose stable per-layer image targets
2 parents bb6ee91 + 576f55d commit 3acbce1

6 files changed

Lines changed: 64 additions & 167 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
}

example/hello/MODULE.bazel.lock

Lines changed: 0 additions & 162 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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)