2017-06-14 00:15:55 +00:00
|
|
|
package solver
|
|
|
|
|
|
|
|
import (
|
2017-07-06 20:15:54 +00:00
|
|
|
"github.com/Sirupsen/logrus"
|
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"
|
|
|
|
"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 {
|
|
|
|
return New(func(v Vertex) (Op, error) {
|
|
|
|
switch op := v.Sys().(type) {
|
|
|
|
case *pb.Op_Source:
|
|
|
|
return newSourceOp(op, opt.SourceManager)
|
|
|
|
case *pb.Op_Exec:
|
|
|
|
return newExecOp(op, opt.CacheManager, opt.Worker)
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("invalid op type %T", op)
|
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
}, opt.InstructionCache)
|
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-06 20:15:54 +00:00
|
|
|
CacheKey(context.Context, []string) (string, 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 {
|
|
|
|
Lookup(ctx context.Context, key string) ([]interface{}, error) // TODO: regular ref
|
|
|
|
Set(key string, refs []interface{}) error
|
|
|
|
}
|
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-06 04:25:51 +00:00
|
|
|
if len(v.Inputs()) > 0 { // TODO: detect op_return better
|
|
|
|
v = v.Inputs()[0].Vertex
|
2017-06-21 21:48:21 +00:00
|
|
|
}
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
vv := toInternalVertex(v)
|
|
|
|
|
|
|
|
j, err := s.jobs.new(ctx, id, vv, pr)
|
2017-06-14 00:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-06-28 20:59:07 +00:00
|
|
|
refs, err := s.getRefs(ctx, j, j.g)
|
2017-06-14 00:15:55 +00:00
|
|
|
closeProgressWriter()
|
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())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if exp != nil {
|
|
|
|
immutable, ok := toImmutableRef(refs[0])
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("invalid reference for exporting: %T", refs[0])
|
|
|
|
}
|
|
|
|
if err := exp.Export(ctx, immutable); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-28 20:59:07 +00:00
|
|
|
}
|
2017-07-10 20:03:38 +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-06 20:15:54 +00:00
|
|
|
func (s *Solver) getCacheKey(ctx context.Context, j *job, g *vertex) (cacheKey string, retErr error) {
|
|
|
|
state, err := s.activeState.vertexState(j, g.digest, func() (Op, error) {
|
|
|
|
return s.resolve(g)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
inputs := make([]string, len(g.inputs))
|
|
|
|
if len(g.inputs) > 0 {
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
|
|
for i, in := range g.inputs {
|
|
|
|
func(i int, in *vertex) {
|
|
|
|
eg.Go(func() error {
|
|
|
|
k, err := s.getCacheKey(ctx, j, in)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
inputs[i] = k
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}(i, in.vertex)
|
|
|
|
}
|
|
|
|
if err := eg.Wait(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
return state.GetCacheKey(ctx, func(ctx context.Context, op Op) (string, error) {
|
|
|
|
return op.CacheKey(ctx, inputs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-07-06 04:25:51 +00:00
|
|
|
func (s *Solver) getRefs(ctx context.Context, j *job, g *vertex) (retRef []Reference, retErr error) {
|
2017-07-06 20:15:54 +00:00
|
|
|
state, err := s.activeState.vertexState(j, g.digest, func() (Op, error) {
|
|
|
|
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
|
|
|
|
cacheKey, err = s.getCacheKey(ctx, j, g)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cacheRefsAny, err := s.cache.Lookup(ctx, cacheKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(cacheRefsAny) > 0 {
|
|
|
|
cacheRefs, err := toReferenceArray(cacheRefsAny)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
g.recursiveMarkCached(ctx)
|
|
|
|
return cacheRefs, nil
|
|
|
|
}
|
|
|
|
}
|
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-06 04:25:51 +00:00
|
|
|
func(i int, in *vertex) {
|
2017-06-15 23:08:20 +00:00
|
|
|
eg.Go(func() error {
|
2017-06-28 20:59:07 +00:00
|
|
|
r, err := s.getRefs(ctx, j, in)
|
|
|
|
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-06-15 23:08:20 +00:00
|
|
|
return nil
|
|
|
|
})
|
2017-07-06 04:25:51 +00:00
|
|
|
}(i, in.vertex)
|
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 {
|
|
|
|
go r.Release(context.TODO())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
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 {
|
|
|
|
if err := s.cache.Set(cacheKey, toAny(refs)); err != nil {
|
|
|
|
logrus.Errorf("failed to save cache for %s: %v", cacheKey, err)
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func toReferenceArray(in []interface{}) ([]Reference, error) {
|
|
|
|
out := make([]Reference, 0, len(in))
|
|
|
|
for _, i := range in {
|
|
|
|
r, ok := i.(Reference)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("invalid reference")
|
|
|
|
}
|
|
|
|
out = append(out, r)
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func toAny(in []Reference) []interface{} {
|
|
|
|
out := make([]interface{}, 0, len(in))
|
|
|
|
for _, i := range in {
|
|
|
|
out = append(out, i)
|
2017-06-28 20:59:07 +00:00
|
|
|
}
|
2017-07-06 20:15:54 +00:00
|
|
|
return out
|
2017-06-14 00:15:55 +00:00
|
|
|
}
|
2017-07-10 20:03:38 +00:00
|
|
|
|
|
|
|
func toImmutableRef(ref Reference) (cache.ImmutableRef, bool) {
|
|
|
|
sysRef := ref
|
|
|
|
if sys, ok := ref.(interface {
|
|
|
|
Sys() Reference
|
|
|
|
}); ok {
|
|
|
|
sysRef = sys.Sys()
|
|
|
|
}
|
|
|
|
immutable, ok := sysRef.(cache.ImmutableRef)
|
|
|
|
if !ok {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
return &immutableRef{immutable, ref.Release}, true
|
|
|
|
}
|
|
|
|
|
|
|
|
type immutableRef struct {
|
|
|
|
cache.ImmutableRef
|
|
|
|
release func(context.Context) error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ir *immutableRef) Release(ctx context.Context) error {
|
|
|
|
return ir.release(ctx)
|
|
|
|
}
|