From 0feeb2491c7d6bebb6375b29806454a247264386 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Tue, 21 Jul 2026 11:15:29 -0600 Subject: [PATCH 1/2] 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. --- .bcr/presubmit.yml | 2 +- MODULE.bazel | 6 +++++- cmd/jar_layerer/main.go | 9 +++++++++ jvm_jar_layers.bzl | 38 +++++++++++++++++++++++++++++++++++--- pkg/jarlayer/jarlayer.go | 14 ++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml index 08475b2..c89d52e 100644 --- a/.bcr/presubmit.yml +++ b/.bcr/presubmit.yml @@ -2,7 +2,7 @@ bcr_test_module: module_path: "example/hello" matrix: platform: ["macos", "ubuntu2204"] - bazel: ["8.*"] + bazel: ["8.*", "9.*"] tasks: build_example: name: "Build example image" diff --git a/MODULE.bazel b/MODULE.bazel index df58828..2aa0f79 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,7 +10,11 @@ bazel_dep(name = "gazelle", version = "0.47.0", dev_dependency = True) bazel_dep(name = "rules_go", version = "0.59.0") bazel_dep(name = "rules_java", version = "8.14.0") -go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +go_sdk = use_extension( + "@rules_go//go:extensions.bzl", + "go_sdk", + dev_dependency = True, +) go_sdk.from_file( go_mod = "//:go.mod", ) diff --git a/cmd/jar_layerer/main.go b/cmd/jar_layerer/main.go index 496affc..e40b49f 100644 --- a/cmd/jar_layerer/main.go +++ b/cmd/jar_layerer/main.go @@ -32,6 +32,8 @@ func main() { flag.Var(&artifactLayers, "artifact_layer", "ARTIFACT_ID=path.tar (repeatable)") var artifactGroupLayers repeatedFlag flag.Var(&artifactGroupLayers, "artifact_group_layer", "ID1,ID2,...=path.tar (repeatable)") + var padLayers repeatedFlag + flag.Var(&padLayers, "pad_layer", "path to write an empty layer tar (repeatable)") flag.Parse() @@ -124,4 +126,11 @@ func main() { os.Exit(1) } } + + for _, path := range padLayers { + if err := jarlayer.WriteEmptyTar(path); err != nil { + fmt.Fprintf(os.Stderr, "writing pad layer: %v\n", err) + os.Exit(1) + } + } } diff --git a/jvm_jar_layers.bzl b/jvm_jar_layers.bzl index f4b4317..b103051 100644 --- a/jvm_jar_layers.bzl +++ b/jvm_jar_layers.bzl @@ -154,6 +154,13 @@ def _group_artifacts(artifact_ids, max_groups): # max_groups is 0: no artifact layers at all. return [] +# Non-maven layer slots: the data-runfiles tar and the fallback tar. +_EXTRA_LAYER_SLOTS = 2 + +def jvm_jar_layer_slots(max_layers): + """Number of `.layer_N` filegroups emitted for a given max_layers.""" + return max_layers + _EXTRA_LAYER_SLOTS + def jvm_jar_layers( name, binary, @@ -172,6 +179,13 @@ def jvm_jar_layers( The container classpath uses Java's @file syntax to reference a classpath file listing all JARs. + Because the number and names of the layer tars are only known at analysis + time, each tar is also exposed through a fixed-name `.layer_N` + filegroup (N in range(jvm_jar_layer_slots(max_layers))) so image rules can + map each tar to its own image layer. Slots beyond the produced tar count + are padded with empty tars, which compress to byte-identical, deduplicable + layer blobs. + Args: name: target name binary: label of a java_binary or scala_binary target @@ -201,6 +215,13 @@ def jvm_jar_layers( **kwargs ) + for index in range(jvm_jar_layer_slots(max_layers)): + native.filegroup( + name = "%s.layer_%d" % (name, index), + srcs = [name], + output_group = "layer_%d" % index, + ) + def _jvm_jar_layers_impl(ctx): runtime_jars = _runtime_jars(ctx.attr.binary) if not runtime_jars: @@ -289,6 +310,14 @@ def _jvm_jar_layers_impl(ctx): args.add("--artifact_group_layer", ",".join(group_ids) + "=" + group_out.path) tar_outputs.append(group_out) + # Pad remaining slots with empty tars so every layer_N output group (and + # its filegroup) yields exactly one tar file — image rules typically reject + # labels that produce no tar. + for index in range(len(tar_outputs), jvm_jar_layer_slots(ctx.attr.max_layers)): + pad = ctx.actions.declare_file(ctx.label.name + ".pad_%d.tar" % index) + args.add("--pad_layer", pad) + tar_outputs.append(pad) + ctx.actions.run( inputs = inputs, outputs = tar_outputs + [classpath_file], @@ -300,11 +329,14 @@ def _jvm_jar_layers_impl(ctx): # DefaultInfo only includes tar files — the classpath file is a plain text # file and must not be passed to container_image's tars attribute. + output_groups = {"classpath": depset([classpath_file])} + for index in range(jvm_jar_layer_slots(ctx.attr.max_layers)): + files = [tar_outputs[index]] if index < len(tar_outputs) else [] + output_groups["layer_%d" % index] = depset(files) + return [ DefaultInfo(files = depset(tar_outputs)), - OutputGroupInfo( - classpath = depset([classpath_file]), - ), + OutputGroupInfo(**output_groups), ] _jvm_jar_layers = rule( diff --git a/pkg/jarlayer/jarlayer.go b/pkg/jarlayer/jarlayer.go index bdab41a..b36dbaa 100644 --- a/pkg/jarlayer/jarlayer.go +++ b/pkg/jarlayer/jarlayer.go @@ -190,6 +190,20 @@ func LayerJars(opts LayerOptions) (retErr error) { return nil } +// WriteEmptyTar writes a valid tar archive containing no entries. Pad layers +// use it so every declared layer slot yields a byte-identical, deduplicable +// blob. +func WriteEmptyTar(outputPath string) (retErr error) { + lw, err := newLayerWriter(outputPath) + if err != nil { + return fmt.Errorf("creating pad layer: %w", err) + } + if closeErr := lw.Close(); closeErr != nil { + return fmt.Errorf("closing pad layer: %w", closeErr) + } + return nil +} + // LayerData writes files into a deterministic tar using their Bazel runfiles // paths. Directories are expanded recursively and symlinks are rejected. func LayerData(outputPath string, files []DataFile) (retErr error) { From 576f55d86279b14c7e8a0d9aec21f17aa78dc126 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Tue, 21 Jul 2026 11:19:13 -0600 Subject: [PATCH 2/2] Update module lock --- example/hello/MODULE.bazel.lock | 162 -------------------------------- 1 file changed, 162 deletions(-) diff --git a/example/hello/MODULE.bazel.lock b/example/hello/MODULE.bazel.lock index eb4f556..97fa1a8 100644 --- a/example/hello/MODULE.bazel.lock +++ b/example/hello/MODULE.bazel.lock @@ -482,168 +482,6 @@ "go1.25.0.windows-arm64.zip", "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" ] - }, - "1.25.12": { - "aix_ppc64": [ - "go1.25.12.aix-ppc64.tar.gz", - "70b4b6509ed60735eff6ed9496824ad9f96201fdf5bd190184123f5908f224ef" - ], - "darwin_amd64": [ - "go1.25.12.darwin-amd64.tar.gz", - "00a2e743b82bccec03c51c4b0f7e46d5fec52184075fd6c5183c3bb39ae9fb00" - ], - "darwin_arm64": [ - "go1.25.12.darwin-arm64.tar.gz", - "fa2c88bbcf64bd3b2aef355f026cfec6d3a4a01c132f999c8f8c964eb767164f" - ], - "dragonfly_amd64": [ - "go1.25.12.dragonfly-amd64.tar.gz", - "2124cfbc1cf0b949eb819478365d4d665f84297a60f327cc65f75f61b2629b96" - ], - "freebsd_386": [ - "go1.25.12.freebsd-386.tar.gz", - "bdb8ab506428e9633653e67c13d582a6230406f01037023b327427c66b058ff2" - ], - "freebsd_amd64": [ - "go1.25.12.freebsd-amd64.tar.gz", - "4433a424d466d47d1716df69e6a77c65b1a34d82121488014e4656d73740ebb0" - ], - "freebsd_arm": [ - "go1.25.12.freebsd-arm.tar.gz", - "429fbcb46468a66d1cd24129a1b6163167676aaae8e0989a08ba95ba6aae65c4" - ], - "freebsd_arm64": [ - "go1.25.12.freebsd-arm64.tar.gz", - "c0e31a4cb827fd20fac950bcb4688fd94cdd1491dbdcff7c3754ac8f3b136eb1" - ], - "freebsd_riscv64": [ - "go1.25.12.freebsd-riscv64.tar.gz", - "0ea50c6d43e809d46c2c5504e97ef11822f7ca137625171924cda971449b5373" - ], - "illumos_amd64": [ - "go1.25.12.illumos-amd64.tar.gz", - "d74c214e0c3ae8f9db23f4ec6f6b2f6cb300c3e4b9d840b82fa517af629c455d" - ], - "linux_386": [ - "go1.25.12.linux-386.tar.gz", - "b71e88ae779850dc2afcd0f2e2208798652311dfda72fdef5d05721d601b8fd3" - ], - "linux_amd64": [ - "go1.25.12.linux-amd64.tar.gz", - "234828b7a89e0e303d2556310ee549fbcf253d28de937bac3da13d6294262ac1" - ], - "linux_arm64": [ - "go1.25.12.linux-arm64.tar.gz", - "8b5884aef89600aef5b0b051fb971f11f49bb996521e911f30f02a66884f7bd2" - ], - "linux_armv6l": [ - "go1.25.12.linux-armv6l.tar.gz", - "6cd7311c02c73ba0b482a1cf8c885268edf23519261bf4b5cef3353ad934d1f1" - ], - "linux_loong64": [ - "go1.25.12.linux-loong64.tar.gz", - "0c6b2b7db8509d1df189433709ddc8fe84c12843e3713508e9ca04dc9e75ddeb" - ], - "linux_mips": [ - "go1.25.12.linux-mips.tar.gz", - "5abc63471425ab8b3408f596cd547d28d374f6dc860a4cbd6de79497123db4f3" - ], - "linux_mips64": [ - "go1.25.12.linux-mips64.tar.gz", - "a18f7a5ac799ed8ac264b63707e7fa475b1b81da6bbdd41c394fcfd69cc6b736" - ], - "linux_mips64le": [ - "go1.25.12.linux-mips64le.tar.gz", - "5bde8a5d4c05b428fca3e6022dc41d4c735858be9c6945e57473da00fcddd0cc" - ], - "linux_mipsle": [ - "go1.25.12.linux-mipsle.tar.gz", - "33c243d2b0700589e75a410ebab5b64580a477e908872abc46eef840e6ea535a" - ], - "linux_ppc64": [ - "go1.25.12.linux-ppc64.tar.gz", - "b5288a7adb38540177146b47aa43bc75f1936c5e14026d6b6e531b534a49eb1a" - ], - "linux_ppc64le": [ - "go1.25.12.linux-ppc64le.tar.gz", - "64adb4ddefef4f0a6f11af550547f39bf510350da69ab308438a21eacfde97ad" - ], - "linux_riscv64": [ - "go1.25.12.linux-riscv64.tar.gz", - "26919d62d21b0bee9c5c67ad76ace462edd16c2c128a983d942cb45b0bf7693c" - ], - "linux_s390x": [ - "go1.25.12.linux-s390x.tar.gz", - "09875de1d6cb3237437112271b9df3a96bac9fcd10cbf9bc777c00e67f4e3e3d" - ], - "netbsd_386": [ - "go1.25.12.netbsd-386.tar.gz", - "2f19f4afc3d6551804228484569ec1ebf4a57a91cda75d746384aa71d5016be7" - ], - "netbsd_amd64": [ - "go1.25.12.netbsd-amd64.tar.gz", - "d2237237d34058658187455d4f05f8f4f5f2118b33827308ad8313ab0f00a693" - ], - "netbsd_arm": [ - "go1.25.12.netbsd-arm.tar.gz", - "4d44c8141a829541c3499db6665a47be2c6fdb97903575058809a2a8be53af89" - ], - "netbsd_arm64": [ - "go1.25.12.netbsd-arm64.tar.gz", - "fc96791f8b9cdaea544e827dc15574731afd28b7378053e801b3bd466df6dd9d" - ], - "openbsd_386": [ - "go1.25.12.openbsd-386.tar.gz", - "e9ced39c191409207a211c6013b9a156bd90dbd5a76ebd8c66dc3bf69bcb2f9e" - ], - "openbsd_amd64": [ - "go1.25.12.openbsd-amd64.tar.gz", - "bae7c016f0aff806c9af08ecff96b59a232144c96b1d25f4c5f6c85c8e0dbf01" - ], - "openbsd_arm": [ - "go1.25.12.openbsd-arm.tar.gz", - "8517c4bca20e975acdd5a2f5e425cd2bec737bbb6d7ee18d11e3a1545014267e" - ], - "openbsd_arm64": [ - "go1.25.12.openbsd-arm64.tar.gz", - "ba4ac29243f43b85a52f12ed7dd02b767e4524785fb6d04fececb7ce125aaa82" - ], - "openbsd_ppc64": [ - "go1.25.12.openbsd-ppc64.tar.gz", - "dca067f41d00ba805342062ab21b88831a06a5682e9aaec1c990c175a5656c6b" - ], - "openbsd_riscv64": [ - "go1.25.12.openbsd-riscv64.tar.gz", - "4f1f5376464fc91f32cd253dadfe3535ff57d1729b24e6ab014fb83964dfe10f" - ], - "plan9_386": [ - "go1.25.12.plan9-386.tar.gz", - "c938792ec65ba33592deea6673265d509aaf9b5297119bc91ae973fb6beb62b9" - ], - "plan9_amd64": [ - "go1.25.12.plan9-amd64.tar.gz", - "5aec7ab115c1c6cd049a64f1617ef91d24ea07c8b0f40436d531b42d546f5e37" - ], - "plan9_arm": [ - "go1.25.12.plan9-arm.tar.gz", - "efe1dcb9750507260a28688a749c72f35c3160646f08a80606f38c3d30d74a12" - ], - "solaris_amd64": [ - "go1.25.12.solaris-amd64.tar.gz", - "83c42cfedcc0bb03fd601d692d75b085406c9a7f5ff505bf41030f2734b62ed2" - ], - "windows_386": [ - "go1.25.12.windows-386.zip", - "18abdf76719f5f84eaa35eeaf87b467a02ed075a8632ad941f10aa7a3d0de713" - ], - "windows_amd64": [ - "go1.25.12.windows-amd64.zip", - "d5dc82da351b00e5eedd04f41356817d674cc4308131f0f638a5b14c5c3af4cb" - ], - "windows_arm64": [ - "go1.25.12.windows-arm64.zip", - "054f046a5fa31fdcc9491cc19065cbf43bf521d805bbe298ae8d65dd981fca84" - ] } } }