2017-07-02 19:16:09 +00:00
|
|
|
package solver
|
|
|
|
|
|
|
|
import (
|
2017-07-06 20:15:54 +00:00
|
|
|
"encoding/json"
|
2017-07-02 19:16:09 +00:00
|
|
|
"io"
|
|
|
|
"os"
|
2017-07-11 02:07:47 +00:00
|
|
|
"sort"
|
2017-07-02 19:16:09 +00:00
|
|
|
|
|
|
|
"github.com/moby/buildkit/cache"
|
|
|
|
"github.com/moby/buildkit/client"
|
|
|
|
"github.com/moby/buildkit/identity"
|
|
|
|
"github.com/moby/buildkit/solver/pb"
|
|
|
|
"github.com/moby/buildkit/util/progress"
|
|
|
|
"github.com/moby/buildkit/worker"
|
2017-07-06 20:15:54 +00:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
2017-07-02 19:16:09 +00:00
|
|
|
"github.com/pkg/errors"
|
2017-07-06 04:25:51 +00:00
|
|
|
"golang.org/x/net/context"
|
2017-07-02 19:16:09 +00:00
|
|
|
)
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
type execOp struct {
|
2017-07-06 20:15:54 +00:00
|
|
|
op *pb.ExecOp
|
2017-07-06 04:25:51 +00:00
|
|
|
cm cache.Manager
|
|
|
|
w worker.Worker
|
|
|
|
}
|
|
|
|
|
|
|
|
func newExecOp(op *pb.Op_Exec, cm cache.Manager, w worker.Worker) (Op, error) {
|
|
|
|
return &execOp{
|
2017-07-06 20:15:54 +00:00
|
|
|
op: op.Exec,
|
2017-07-06 04:25:51 +00:00
|
|
|
cm: cm,
|
|
|
|
w: w,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-07-06 20:15:54 +00:00
|
|
|
func (e *execOp) CacheKey(ctx context.Context, inputs []string) (string, error) {
|
|
|
|
dt, err := json.Marshal(struct {
|
|
|
|
Inputs []string
|
|
|
|
Exec *pb.ExecOp
|
|
|
|
}{
|
|
|
|
Inputs: inputs,
|
|
|
|
Exec: e.op,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return digest.FromBytes(dt).String(), nil
|
|
|
|
}
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
func (e *execOp) Run(ctx context.Context, inputs []Reference) ([]Reference, error) {
|
2017-07-11 02:07:47 +00:00
|
|
|
var mounts []worker.Mount
|
2017-07-02 19:16:09 +00:00
|
|
|
var outputs []cache.MutableRef
|
2017-07-11 02:07:47 +00:00
|
|
|
var root cache.Mountable
|
2017-07-02 19:16:09 +00:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
for _, o := range outputs {
|
|
|
|
if o != nil {
|
|
|
|
s, err := o.Freeze() // TODO: log error
|
|
|
|
if err == nil {
|
|
|
|
go s.Release(ctx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-07-06 20:15:54 +00:00
|
|
|
for _, m := range e.op.Mounts {
|
2017-07-02 19:16:09 +00:00
|
|
|
var mountable cache.Mountable
|
|
|
|
if int(m.Input) > len(inputs) {
|
|
|
|
return nil, errors.Errorf("missing input %d", m.Input)
|
|
|
|
}
|
2017-07-06 04:25:51 +00:00
|
|
|
inp := inputs[int(m.Input)]
|
|
|
|
if sys, ok := inp.(interface {
|
|
|
|
Sys() Reference
|
|
|
|
}); ok {
|
|
|
|
inp = sys.Sys()
|
|
|
|
}
|
|
|
|
ref, ok := inp.(cache.ImmutableRef)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("invalid reference for exec %T", inputs[int(m.Input)])
|
|
|
|
}
|
2017-07-02 19:16:09 +00:00
|
|
|
mountable = ref
|
|
|
|
if m.Output != -1 {
|
2017-07-06 04:25:51 +00:00
|
|
|
active, err := e.cm.New(ctx, ref) // TODO: should be method
|
2017-07-02 19:16:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
outputs = append(outputs, active)
|
|
|
|
mountable = active
|
|
|
|
}
|
2017-07-11 04:54:38 +00:00
|
|
|
if m.Dest == pb.RootMount {
|
2017-07-11 02:07:47 +00:00
|
|
|
root = mountable
|
|
|
|
} else {
|
|
|
|
mounts = append(mounts, worker.Mount{Src: mountable, Dest: m.Dest})
|
|
|
|
}
|
2017-07-02 19:16:09 +00:00
|
|
|
}
|
|
|
|
|
2017-07-11 02:07:47 +00:00
|
|
|
sort.Slice(mounts, func(i, j int) bool {
|
|
|
|
return mounts[i].Dest < mounts[j].Dest
|
|
|
|
})
|
|
|
|
|
2017-07-02 19:16:09 +00:00
|
|
|
meta := worker.Meta{
|
2017-07-06 20:15:54 +00:00
|
|
|
Args: e.op.Meta.Args,
|
|
|
|
Env: e.op.Meta.Env,
|
|
|
|
Cwd: e.op.Meta.Cwd,
|
2017-07-02 19:16:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stdout := newStreamWriter(ctx, 1)
|
|
|
|
defer stdout.Close()
|
|
|
|
stderr := newStreamWriter(ctx, 2)
|
|
|
|
defer stderr.Close()
|
|
|
|
|
2017-07-11 02:07:47 +00:00
|
|
|
if err := e.w.Exec(ctx, meta, root, mounts, stdout, stderr); err != nil {
|
2017-07-02 19:16:09 +00:00
|
|
|
return nil, errors.Wrapf(err, "worker failed running %v", meta.Args)
|
|
|
|
}
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
refs := []Reference{}
|
2017-07-02 19:16:09 +00:00
|
|
|
for i, o := range outputs {
|
|
|
|
ref, err := o.ReleaseAndCommit(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error committing %s", o.ID())
|
|
|
|
}
|
|
|
|
refs = append(refs, ref)
|
|
|
|
outputs[i] = nil
|
|
|
|
}
|
|
|
|
return refs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStreamWriter(ctx context.Context, stream int) io.WriteCloser {
|
|
|
|
pw, _, _ := progress.FromContext(ctx)
|
|
|
|
return &streamWriter{
|
|
|
|
pw: pw,
|
|
|
|
stream: stream,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type streamWriter struct {
|
|
|
|
pw progress.Writer
|
|
|
|
stream int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *streamWriter) Write(dt []byte) (int, error) {
|
|
|
|
sw.pw.Write(identity.NewID(), client.VertexLog{
|
|
|
|
Stream: sw.stream,
|
|
|
|
Data: append([]byte{}, dt...),
|
|
|
|
})
|
|
|
|
// TODO: remove debug
|
|
|
|
switch sw.stream {
|
|
|
|
case 1:
|
|
|
|
return os.Stdout.Write(dt)
|
|
|
|
case 2:
|
|
|
|
return os.Stderr.Write(dt)
|
|
|
|
default:
|
|
|
|
return 0, errors.Errorf("invalid stream %d", sw.stream)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *streamWriter) Close() error {
|
|
|
|
return sw.pw.Close()
|
|
|
|
}
|