openllb / hlb

A developer-first language to build and test any software efficiently
https://openllb.github.io/hlb/
Apache License 2.0
108 stars 12 forks source link

ForceNoOutput causing graph duplication #350

Open coryb opened 1 year ago

coryb commented 1 year ago

When we run the second target from this HLB:

fs _first() {
    image "busybox"
    run "echo first" with option {
        mount scratch localCwd as first
    }
}

fs _second() {
    _first
    run "echo second" with option {
        mount first localCwd as second
    }
}

we will see something like:

#1 resolve image config for docker.io/library/busybox:latest
#1 DONE 0.4s

#2 docker-image://docker.io/library/busybox:latest
#2 resolve docker.io/library/busybox:latest
#2 resolve docker.io/library/busybox:latest 0.3s done
#2 CACHED

#3 /bin/sh -c 'echo first'
#3 CACHED

#4 /bin/sh -c 'echo first'
#4 0.270 first
#4 DONE 0.3s

#5 /bin/sh -c 'echo second'
#5 0.499 second
#5 DONE 0.5s

Note the echo first is evaluated twice, if we add noCache it will run echo first twice in two different containers. The duplication goes away when we remove this line

I am at a loss for how to fix this. It seems the first mount is getting evaluated twice once has ForceNoOutput applied and one has the Bind logic applied. I am not sure why the Bind logic would not be applied in both locations.

We should probably revert #347 and #348 unless anyone has ideas on how to work around this.

hinshun commented 1 year ago

Can you reproduce this with just LLB? i.e. _first will be execSt.Root() whereas first will be execSt.GetMount(...).

coryb commented 1 year ago

Pretty sure this is just an an issue with how hlb generates the state. At least this does what I expect with LLB:

func main() {
    ctx := context.Background()
    c, err := client.New(context.Background(), "docker-container://buildkitd")
    if err != nil {
        panic(err)
    }
    defer c.Close()

    localCwd, _ := os.Getwd()
    execFirst := llb.Image("busybox").Run(
        llb.Shlex("echo first"),
        llb.AddMount(localCwd, llb.Scratch()),
    )
    _first := execFirst.Root()
    first := execFirst.GetMount(localCwd)

    second := _first.Run(
        llb.Shlex("echo second"),
        llb.AddMount(localCwd, first),
    ).GetMount(localCwd)

    def, err := second.Marshal(ctx, llb.LinuxAmd64)
    if err != nil {
        panic(err)
    }

    ch := make(chan *client.SolveStatus)
    eg, ctx := errgroup.WithContext(ctx)
    eg.Go(func() error {
        _, err = c.Solve(ctx, def, client.SolveOpt{}, ch)
        return err
    })
    eg.Go(func() error {
        _, err = progressui.DisplaySolveStatus(context.TODO(), "", nil, os.Stdout, ch)
        return err
    })
    err = eg.Wait()
    if err != nil {
        panic(err)
    }
}

Running that I see:

#1 docker-image://docker.io/library/busybox:latest
#1 resolve docker.io/library/busybox:latest
#1 resolve docker.io/library/busybox:latest 0.3s done
#1 DONE 0.3s

#2 echo first
#2 CACHED

#3 echo second
#0 0.051 second
#3 DONE 0.1s