2017-06-14 00:15:55 +00:00
|
|
|
package solver
|
|
|
|
|
|
|
|
import (
|
2017-07-14 18:59:31 +00:00
|
|
|
"fmt"
|
|
|
|
|
2017-06-22 20:15:46 +00:00
|
|
|
"github.com/moby/buildkit/cache"
|
|
|
|
"github.com/moby/buildkit/client"
|
2017-07-10 20:03:38 +00:00
|
|
|
"github.com/moby/buildkit/exporter"
|
2017-06-22 20:15:46 +00:00
|
|
|
"github.com/moby/buildkit/solver/pb"
|
|
|
|
"github.com/moby/buildkit/source"
|
|
|
|
"github.com/moby/buildkit/util/progress"
|
|
|
|
"github.com/moby/buildkit/worker"
|
2017-06-14 00:15:55 +00:00
|
|
|
"github.com/pkg/errors"
|
2017-07-19 01:05:19 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-06-14 00:15:55 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
)
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
type LLBOpt struct {
|
2017-07-06 20:15:54 +00:00
|
|
|
SourceManager *source.Manager
|
|
|
|
CacheManager cache.Manager // TODO: this shouldn't be needed before instruction cache
|
|
|
|
Worker worker.Worker
|
|
|
|
InstructionCache InstructionCache
|
2017-06-14 00:15:55 +00:00
|
|
|
}
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
func NewLLBSolver(opt LLBOpt) *Solver {
|
2017-07-21 17:58:24 +00:00
|
|
|
var s *Solver
|
|
|
|
s = New(func(v Vertex) (Op, error) {
|
2017-07-06 04:25:51 +00:00
|
|
|
switch op := v.Sys().(type) {
|
|
|
|
case *pb.Op_Source:
|
2017-07-21 17:58:24 +00:00
|
|
|
return newSourceOp(v, op, opt.SourceManager)
|
2017-07-06 04:25:51 +00:00
|
|
|
case *pb.Op_Exec:
|
2017-07-21 17:58:24 +00:00
|
|
|
return newExecOp(v, op, opt.CacheManager, opt.Worker)
|
|
|
|
case *pb.Op_Build:
|
|
|
|
return newBuildOp(v, op, s)
|
2017-07-06 04:25:51 +00:00
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("invalid op type %T", op)
|
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
}, opt.InstructionCache)
|
2017-07-21 17:58:24 +00:00
|
|
|
return s
|
2017-07-06 04:25:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ResolveOpFunc finds an Op implementation for a vertex
|
|
|
|
type ResolveOpFunc func(Vertex) (Op, error)
|
|
|
|
|
|
|
|
// Reference is a reference to the object passed through the build steps.
|
|
|
|
type Reference interface {
|
|
|
|
Release(context.Context) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Op is an implementation for running a vertex
|
|
|
|
type Op interface {
|
2017-07-14 18:59:31 +00:00
|
|
|
CacheKey(context.Context, []string) (string, int, error)
|
2017-07-06 04:25:51 +00:00
|
|
|
Run(ctx context.Context, inputs []Reference) (outputs []Reference, err error)
|
|
|
|
}
|
|
|
|
|
2017-07-06 20:15:54 +00:00
|
|
|
type InstructionCache interface {
|
2017-07-14 18:59:31 +00:00
|
|
|
Lookup(ctx context.Context, key string) (interface{}, error) // TODO: regular ref
|
|
|
|
Set(key string, ref interface{}) error
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|
2017-07-06 04:25:51 +00:00
|
|
|
|
2017-06-14 00:15:55 +00:00
|
|
|
type Solver struct {
|
2017-07-06 20:15:54 +00:00
|
|
|
resolve ResolveOpFunc
|
|
|
|
jobs *jobList
|
|
|
|
activeState activeState
|
|
|
|
cache InstructionCache
|
2017-06-14 00:15:55 +00:00
|
|
|
}
|
|
|
|
|
2017-07-06 20:15:54 +00:00
|
|
|
func New(resolve ResolveOpFunc, cache InstructionCache) *Solver {
|
|
|
|
return &Solver{resolve: resolve, jobs: newJobList(), cache: cache}
|
2017-06-14 00:15:55 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 20:03:38 +00:00
|
|
|
func (s *Solver) Solve(ctx context.Context, id string, v Vertex, exp exporter.ExporterInstance) error {
|
2017-06-14 00:15:55 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
pr, ctx, closeProgressWriter := progress.NewContext(ctx)
|
|
|
|
|
2017-07-11 00:56:04 +00:00
|
|
|
origVertex := v
|
|
|
|
|
|
|
|
defer closeProgressWriter()
|
|
|
|
|
2017-07-21 17:58:24 +00:00
|
|
|
if len(v.Inputs()) == 0 {
|
|
|
|
return errors.New("required vertex needs to have inputs")
|
2017-06-21 21:48:21 +00:00
|
|
|
}
|
|
|
|
|
2017-07-21 17:58:24 +00:00
|
|
|
index := v.Inputs()[0].Index
|
|
|
|
v = v.Inputs()[0].Vertex
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
vv := toInternalVertex(v)
|
2017-07-11 00:56:04 +00:00
|
|
|
solveVertex := vv
|
|
|
|
|
|
|
|
if exp != nil {
|
|
|
|
vv = &vertex{digest: origVertex.Digest(), name: exp.Name()}
|
|
|
|
vv.inputs = []*input{{index: 0, vertex: solveVertex}}
|
|
|
|
vv.initClientVertex()
|
|
|
|
}
|
2017-07-06 04:25:51 +00:00
|
|
|
|
2017-07-21 17:58:24 +00:00
|
|
|
ctx, j, err := s.jobs.new(ctx, id, vv, pr)
|
2017-06-14 00:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-07-21 17:58:24 +00:00
|
|
|
refs, err := s.getRefs(ctx, solveVertex)
|
2017-07-06 20:15:54 +00:00
|
|
|
s.activeState.cancel(j)
|
2017-06-14 00:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-07-10 20:03:38 +00:00
|
|
|
defer func() {
|
|
|
|
for _, r := range refs {
|
|
|
|
r.Release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-07-14 18:59:31 +00:00
|
|
|
for _, ref := range refs {
|
|
|
|
immutable, ok := toImmutableRef(ref)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("invalid reference for exporting: %T", ref)
|
|
|
|
}
|
|
|
|
if err := immutable.Finalize(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-10 20:03:38 +00:00
|
|
|
if exp != nil {
|
2017-07-21 17:58:24 +00:00
|
|
|
r := refs[int(index)]
|
|
|
|
immutable, ok := toImmutableRef(r)
|
2017-07-10 20:03:38 +00:00
|
|
|
if !ok {
|
2017-07-21 17:58:24 +00:00
|
|
|
return errors.Errorf("invalid reference for exporting: %T", r)
|
2017-07-10 20:03:38 +00:00
|
|
|
}
|
2017-07-11 00:56:04 +00:00
|
|
|
vv.notifyStarted(ctx)
|
|
|
|
pw, _, ctx := progress.FromContext(ctx, progress.WithMetadata("vertex", vv.Digest()))
|
|
|
|
defer pw.Close()
|
|
|
|
err := exp.Export(ctx, immutable)
|
|
|
|
vv.notifyCompleted(ctx, false, err)
|
|
|
|
if err != nil {
|
2017-07-10 20:03:38 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-06-28 20:59:07 +00:00
|
|
|
}
|
2017-06-14 00:15:55 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Solver) Status(ctx context.Context, id string, statusChan chan *client.SolveStatus) error {
|
|
|
|
j, err := s.jobs.get(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer close(statusChan)
|
|
|
|
return j.pipe(ctx, statusChan)
|
|
|
|
}
|
|
|
|
|
2017-07-21 17:58:24 +00:00
|
|
|
func (s *Solver) getCacheKey(ctx context.Context, g *vertex) (cacheKey string, numRefs int, retErr error) {
|
|
|
|
state, err := s.activeState.vertexState(ctx, g.digest, func() (Op, error) {
|
2017-07-06 20:15:54 +00:00
|
|
|
return s.resolve(g)
|
|
|
|
})
|
|
|
|
if err != nil {
|
2017-07-14 18:59:31 +00:00
|
|
|
return "", 0, err
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inputs := make([]string, len(g.inputs))
|
|
|
|
if len(g.inputs) > 0 {
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
|
|
for i, in := range g.inputs {
|
2017-07-14 18:59:31 +00:00
|
|
|
func(i int, in *vertex, index int) {
|
2017-07-06 20:15:54 +00:00
|
|
|
eg.Go(func() error {
|
2017-07-21 17:58:24 +00:00
|
|
|
k, _, err := s.getCacheKey(ctx, in)
|
2017-07-06 20:15:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-14 18:59:31 +00:00
|
|
|
inputs[i] = fmt.Sprintf("%s.%d", k, index)
|
2017-07-06 20:15:54 +00:00
|
|
|
return nil
|
|
|
|
})
|
2017-07-14 18:59:31 +00:00
|
|
|
}(i, in.vertex, in.index)
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|
|
|
|
if err := eg.Wait(); err != nil {
|
2017-07-14 18:59:31 +00:00
|
|
|
return "", 0, err
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pw, _, ctx := progress.FromContext(ctx, progress.WithMetadata("vertex", g.Digest()))
|
|
|
|
defer pw.Close()
|
|
|
|
|
|
|
|
if len(g.inputs) == 0 {
|
|
|
|
g.notifyStarted(ctx)
|
|
|
|
defer func() {
|
|
|
|
g.notifyCompleted(ctx, false, retErr)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2017-07-14 18:59:31 +00:00
|
|
|
return state.GetCacheKey(ctx, func(ctx context.Context, op Op) (string, int, error) {
|
2017-07-06 20:15:54 +00:00
|
|
|
return op.CacheKey(ctx, inputs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-07-21 17:58:24 +00:00
|
|
|
func (s *Solver) getRefs(ctx context.Context, g *vertex) (retRef []Reference, retErr error) {
|
|
|
|
state, err := s.activeState.vertexState(ctx, g.digest, func() (Op, error) {
|
2017-07-06 20:15:54 +00:00
|
|
|
return s.resolve(g)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-06-17 05:12:27 +00:00
|
|
|
|
2017-07-06 20:15:54 +00:00
|
|
|
var cacheKey string
|
|
|
|
if s.cache != nil {
|
|
|
|
var err error
|
2017-07-14 18:59:31 +00:00
|
|
|
var numRefs int
|
2017-07-21 17:58:24 +00:00
|
|
|
cacheKey, numRefs, err = s.getCacheKey(ctx, g)
|
2017-07-06 20:15:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-14 18:59:31 +00:00
|
|
|
cacheRefs := make([]Reference, 0, numRefs)
|
|
|
|
// check if all current refs are already cached
|
|
|
|
for i := 0; i < numRefs; i++ {
|
|
|
|
ref, err := s.cache.Lookup(ctx, fmt.Sprintf("%s.%d", cacheKey, i))
|
2017-07-06 20:15:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-14 18:59:31 +00:00
|
|
|
if ref == nil { // didn't find ref, release all
|
|
|
|
for _, ref := range cacheRefs {
|
|
|
|
ref.Release(context.TODO())
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
cacheRefs = append(cacheRefs, ref.(Reference))
|
|
|
|
if len(cacheRefs) == numRefs { // last item
|
|
|
|
g.recursiveMarkCached(ctx)
|
|
|
|
return cacheRefs, nil
|
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-28 20:59:07 +00:00
|
|
|
|
|
|
|
// refs contains all outputs for all input vertexes
|
|
|
|
refs := make([][]*sharedRef, len(g.inputs))
|
2017-06-14 00:15:55 +00:00
|
|
|
if len(g.inputs) > 0 {
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
2017-06-28 20:59:07 +00:00
|
|
|
for i, in := range g.inputs {
|
2017-07-14 18:59:31 +00:00
|
|
|
func(i int, in *vertex, index int) {
|
2017-06-15 23:08:20 +00:00
|
|
|
eg.Go(func() error {
|
2017-07-14 18:59:31 +00:00
|
|
|
if s.cache != nil {
|
2017-07-21 17:58:24 +00:00
|
|
|
k, numRefs, err := s.getCacheKey(ctx, in)
|
2017-07-14 18:59:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ref, err := s.cache.Lookup(ctx, fmt.Sprintf("%s.%d", k, index))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if ref != nil {
|
|
|
|
if ref, ok := toImmutableRef(ref.(Reference)); ok {
|
|
|
|
refs[i] = make([]*sharedRef, numRefs)
|
|
|
|
refs[i][index] = newSharedRef(ref)
|
|
|
|
in.recursiveMarkCached(ctx)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// execute input vertex
|
2017-07-21 17:58:24 +00:00
|
|
|
r, err := s.getRefs(ctx, in)
|
2017-06-28 20:59:07 +00:00
|
|
|
if err != nil {
|
2017-06-15 23:08:20 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-06-28 20:59:07 +00:00
|
|
|
for _, r := range r {
|
|
|
|
refs[i] = append(refs[i], newSharedRef(r))
|
|
|
|
}
|
2017-07-20 22:55:24 +00:00
|
|
|
if ref, ok := toImmutableRef(r[index].(Reference)); ok {
|
|
|
|
// make sure input that is required by next step does not get released in case build is cancelled
|
|
|
|
if err := cache.CachePolicyRetain(ref); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-06-15 23:08:20 +00:00
|
|
|
return nil
|
|
|
|
})
|
2017-07-14 18:59:31 +00:00
|
|
|
}(i, in.vertex, in.index)
|
2017-06-14 00:15:55 +00:00
|
|
|
}
|
|
|
|
err := eg.Wait()
|
|
|
|
if err != nil {
|
2017-06-28 20:59:07 +00:00
|
|
|
for _, r := range refs {
|
|
|
|
for _, r := range r {
|
2017-07-14 18:59:31 +00:00
|
|
|
if r != nil {
|
|
|
|
go r.Release(context.TODO())
|
|
|
|
}
|
2017-06-28 20:59:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// determine the inputs that were needed
|
2017-07-06 04:25:51 +00:00
|
|
|
inputRefs := make([]Reference, 0, len(g.inputs))
|
|
|
|
for i, inp := range g.inputs {
|
|
|
|
inputRefs = append(inputRefs, refs[i][inp.index].Clone())
|
2017-06-28 20:59:07 +00:00
|
|
|
}
|
|
|
|
|
2017-07-01 01:09:29 +00:00
|
|
|
defer func() {
|
2017-07-06 04:25:51 +00:00
|
|
|
for _, r := range inputRefs {
|
2017-07-01 01:09:29 +00:00
|
|
|
go r.Release(context.TODO())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-06-28 20:59:07 +00:00
|
|
|
// release anything else
|
|
|
|
for _, r := range refs {
|
|
|
|
for _, r := range r {
|
2017-07-14 18:59:31 +00:00
|
|
|
if r != nil {
|
|
|
|
go r.Release(context.TODO())
|
|
|
|
}
|
2017-06-14 00:15:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
pw, _, ctx := progress.FromContext(ctx, progress.WithMetadata("vertex", g.Digest()))
|
2017-06-30 03:34:29 +00:00
|
|
|
defer pw.Close()
|
|
|
|
|
2017-06-19 03:20:07 +00:00
|
|
|
g.notifyStarted(ctx)
|
2017-06-29 22:31:08 +00:00
|
|
|
defer func() {
|
2017-07-06 20:15:54 +00:00
|
|
|
g.notifyCompleted(ctx, false, retErr)
|
2017-06-29 22:31:08 +00:00
|
|
|
}()
|
2017-06-14 00:15:55 +00:00
|
|
|
|
2017-07-06 20:15:54 +00:00
|
|
|
return state.GetRefs(ctx, func(ctx context.Context, op Op) ([]Reference, error) {
|
|
|
|
refs, err := op.Run(ctx, inputRefs)
|
2017-06-28 20:59:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
if s.cache != nil {
|
2017-07-14 18:59:31 +00:00
|
|
|
for i, ref := range refs {
|
2017-07-19 18:08:49 +00:00
|
|
|
if err := s.cache.Set(fmt.Sprintf("%s.%d", cacheKey, i), originRef(ref)); err != nil {
|
|
|
|
logrus.Errorf("failed to save cache for %s: %v", cacheKey, err)
|
2017-07-14 18:59:31 +00:00
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|
2017-07-06 04:25:51 +00:00
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
return refs, nil
|
2017-06-28 20:59:07 +00:00
|
|
|
})
|
2017-07-06 20:15:54 +00:00
|
|
|
}
|