Skip to content

Commit a384fdf

Browse files
feat(wanda): Build dependencies in topological order
Extend Build() to automatically build all dependencies before building the root spec. In local mode, specs are discovered by scanning the repo for *.wanda.yaml files and built in topological order. For a slight optimization, in RayCI mode, only the root spec is built (deps built by prior pipeline steps). Topic: wanda-build-deps Relative: wanda-deps Signed-off-by: andrew <andrew@anyscale.com>
1 parent 7475e00 commit a384fdf

File tree

8 files changed

+126
-6
lines changed

8 files changed

+126
-6
lines changed

wanda/forge.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,51 @@ import (
1414
"github.com/google/go-containerregistry/pkg/v1/remote"
1515
)
1616

17-
// Build builds a container image from the given specification file.
17+
// Build builds a container image from the given specification file, and builds
18+
// all its dependencies in topological order.
19+
// In RayCI mode, dependencies are assumed built by prior pipeline steps; only
20+
// the root is built.
1821
func Build(specFile string, config *ForgeConfig) error {
1922
if config == nil {
2023
config = &ForgeConfig{}
2124
}
2225

23-
spec, err := parseSpecFile(specFile)
26+
wandaSpecsFile := config.WandaSpecsFile
27+
if wandaSpecsFile == "" {
28+
wandaSpecsFile = filepath.Join(config.WorkDir, ".wandaspecs")
29+
}
30+
31+
graph, err := buildDepGraph(specFile, os.LookupEnv, config.NamePrefix, wandaSpecsFile)
2432
if err != nil {
25-
return fmt.Errorf("parse spec file: %w", err)
33+
return fmt.Errorf("build dep graph: %w", err)
2634
}
2735

28-
// Expand env variable.
29-
spec = spec.expandVar(os.LookupEnv)
36+
if err := graph.validateDeps(); err != nil {
37+
return fmt.Errorf("validate deps: %w", err)
38+
}
3039

3140
forge, err := NewForge(config)
3241
if err != nil {
3342
return fmt.Errorf("make forge: %w", err)
3443
}
35-
return forge.Build(spec)
44+
45+
// In RayCI mode, only build the root (deps built by prior pipeline steps).
46+
order := graph.Order
47+
if config.RayCI {
48+
order = []string{graph.Root}
49+
}
50+
51+
for _, name := range order {
52+
rs := graph.Specs[name]
53+
54+
log.Printf("building %s (from %s)", name, rs.Path)
55+
56+
if err := forge.Build(rs.Spec); err != nil {
57+
return fmt.Errorf("build %s: %w", name, err)
58+
}
59+
}
60+
61+
return nil
3662
}
3763

3864
// Forge is a forge to build container images.

wanda/forge_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net"
1111
"net/http/httptest"
1212
"os"
13+
"path/filepath"
1314
"runtime"
1415

1516
"github.com/google/go-containerregistry/pkg/name"
@@ -455,6 +456,83 @@ func TestForgeWithRemoteWorkRepo(t *testing.T) {
455456
}
456457
}
457458

459+
func TestBuild_WithDeps(t *testing.T) {
460+
// Test: dep-top -> dep-middle -> dep-base
461+
// Build should build in order: dep-base, dep-middle, dep-top
462+
463+
// Create a wandaspecs file pointing to testdata directory.
464+
wandaSpecs := filepath.Join(t.TempDir(), ".wandaspecs")
465+
absTestdata, err := filepath.Abs("testdata")
466+
if err != nil {
467+
t.Fatalf("abs testdata: %v", err)
468+
}
469+
if err := os.WriteFile(wandaSpecs, []byte(absTestdata), 0644); err != nil {
470+
t.Fatalf("write wandaspecs: %v", err)
471+
}
472+
473+
config := &ForgeConfig{
474+
WorkDir: "testdata",
475+
NamePrefix: "cr.ray.io/rayproject/",
476+
WandaSpecsFile: wandaSpecs,
477+
}
478+
479+
if err := Build("testdata/dep-top.wanda.yaml", config); err != nil {
480+
t.Fatalf("build with deps: %v", err)
481+
}
482+
483+
// Verify dep-top was built and can be read
484+
ref, err := name.ParseReference("cr.ray.io/rayproject/dep-top")
485+
if err != nil {
486+
t.Fatalf("parse reference: %v", err)
487+
}
488+
489+
img, err := daemon.Image(ref)
490+
if err != nil {
491+
t.Fatalf("read dep-top image: %v", err)
492+
}
493+
494+
layers, err := img.Layers()
495+
if err != nil {
496+
t.Fatalf("read layers: %v", err)
497+
}
498+
499+
// Should have 3 layers: dep-base, dep-middle, dep-top
500+
if got, want := len(layers), 3; got != want {
501+
t.Errorf("got %d layers, want %d", got, want)
502+
}
503+
}
504+
505+
func TestBuild_NoDeps(t *testing.T) {
506+
// Test backward compatibility: a spec with no deps should work
507+
config := &ForgeConfig{
508+
WorkDir: "testdata",
509+
NamePrefix: "cr.ray.io/rayproject/",
510+
}
511+
512+
if err := Build("testdata/hello-test.wanda.yaml", config); err != nil {
513+
t.Fatalf("build with deps: %v", err)
514+
}
515+
516+
ref, err := name.ParseReference("cr.ray.io/rayproject/hello-test")
517+
if err != nil {
518+
t.Fatalf("parse reference: %v", err)
519+
}
520+
521+
img, err := daemon.Image(ref)
522+
if err != nil {
523+
t.Fatalf("read hello image: %v", err)
524+
}
525+
526+
layers, err := img.Layers()
527+
if err != nil {
528+
t.Fatalf("read layers: %v", err)
529+
}
530+
531+
if got, want := len(layers), 1; got != want {
532+
t.Errorf("got %d layers, want %d", got, want)
533+
}
534+
}
535+
458536
func TestForgeLocal_withNamePrefix(t *testing.T) {
459537
if runtime.GOOS != "linux" {
460538
t.Skip("skipping test on non-linux")

wanda/testdata/Dockerfile.dep-base

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM scratch
2+
COPY Dockerfile.dep-base /opt/
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM cr.ray.io/rayproject/dep-base
2+
COPY world.txt /opt/

wanda/testdata/Dockerfile.dep-top

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM cr.ray.io/rayproject/dep-middle
2+
COPY Dockerfile.dep-top /opt/

wanda/testdata/dep-base.wanda.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name: dep-base
2+
dockerfile: Dockerfile.dep-base
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: dep-middle
2+
froms: ["cr.ray.io/rayproject/dep-base"]
3+
dockerfile: Dockerfile.dep-middle
4+
srcs:
5+
- world.txt

wanda/testdata/dep-top.wanda.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: dep-top
2+
froms: ["cr.ray.io/rayproject/dep-middle"]
3+
dockerfile: Dockerfile.dep-top

0 commit comments

Comments
 (0)