Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/jar_layerer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func main() {
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)")
var ensureDirs repeatedFlag
flag.Var(&ensureDirs, "ensure_dir", "directory entry to always create in the fallback tar (repeatable)")

flag.Parse()

Expand All @@ -48,6 +50,7 @@ func main() {
ClasspathPath: *classpath,
AppPrefix: *appPrefix,
PathPrefix: *pathPrefix,
EnsureDirs: ensureDirs,
}

// Read JAR list from file.
Expand Down
6 changes: 6 additions & 0 deletions jvm_jar_layers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,12 @@ def _jvm_jar_layers_impl(ctx):
args.add("--fallback", fallback)
tar_outputs.append(fallback)

# Always materialize the workspace runfiles directory: consumers commonly
# set the image WorkingDir to /app/<workspace> for runfiles-relative
# flags, and OCI runtimes fail chdir when the directory is absent (it is
# otherwise only created when data runfiles exist).
args.add("--ensure_dir", "app/" + ctx.workspace_name)

# Maven artifact layers via aspect.
if ctx.file.maven_lock_file:
lock_file = ctx.file.maven_lock_file
Expand Down
11 changes: 11 additions & 0 deletions pkg/jarlayer/jarlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ type LayerOptions struct {
AppPrefix string
// PathPrefix is prepended to tar entry paths (e.g., "app/lib/").
PathPrefix string
// EnsureDirs lists directories always created in the fallback tar (e.g.
// "app/_main"), so that an image WorkingDir pointing at them exists even
// when no other entry would create them. OCI runtimes fail chdir on a
// missing working directory.
EnsureDirs []string
}

// ArtifactLayer maps one or more artifact IDs to a single output tar.
Expand Down Expand Up @@ -108,6 +113,12 @@ func LayerJars(opts LayerOptions) (retErr error) {
// Track written directories to avoid duplicates across JARs.
writtenDirs := make(map[string]map[string]bool) // writer path -> set of dirs

for _, dir := range opts.EnsureDirs {
if err := ensureParentDirs(fallback, strings.TrimSuffix(dir, "/")+"/", writtenDirs); err != nil {
return fmt.Errorf("ensuring dir %s: %w", dir, err)
}
}

// Process each JAR: determine layer, write to tar, collect classpath.
var classpathEntries []string
usedNames := make(map[string]bool)
Expand Down
30 changes: 30 additions & 0 deletions pkg/jarlayer/jarlayer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,3 +622,33 @@ func TestLayerData_RejectsUnsafeDestination(t *testing.T) {
t.Fatalf("LayerData() error = %v, want unsafe destination error", err)
}
}

func TestLayerJars_EnsureDirs(t *testing.T) {
dir := t.TempDir()

jar1 := filepath.Join(dir, "dep1.jar")
createTestJar(t, jar1, map[string]string{
"com/example/Foo.class": "foo-bytes",
})

fallbackPath := filepath.Join(dir, "fallback.tar")

err := LayerJars(LayerOptions{
JarPaths: []string{jar1},
FallbackPath: fallbackPath,
ClasspathPath: filepath.Join(dir, "classpath"),
AppPrefix: "/app/lib",
PathPrefix: "app/lib/",
EnsureDirs: []string{"app/_main"},
})
if err != nil {
t.Fatal(err)
}

entries := readTar(t, fallbackPath)
for _, want := range []string{"app/", "app/_main/"} {
if _, ok := entries[want]; !ok {
t.Errorf("expected directory entry %q in fallback tar", want)
}
}
}
Loading