blob: 99af7c6879656064bff5f49fb428e865a519edff [file] [log] [blame]
package llb
import (
"context"
"sync"
"github.com/moby/buildkit/solver/pb"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// DefinitionOp implements llb.Vertex using a marshalled definition.
//
// For example, after marshalling a LLB state and sending over the wire, the
// LLB state can be reconstructed from the definition.
type DefinitionOp struct {
MarshalCache
mu sync.Mutex
ops map[digest.Digest]*pb.Op
defs map[digest.Digest][]byte
metas map[digest.Digest]pb.OpMetadata
sources map[digest.Digest][]*SourceLocation
platforms map[digest.Digest]*specs.Platform
dgst digest.Digest
index pb.OutputIndex
inputCache map[digest.Digest][]*DefinitionOp
}
// NewDefinitionOp returns a new operation from a marshalled definition.
func NewDefinitionOp(def *pb.Definition) (*DefinitionOp, error) {
ops := make(map[digest.Digest]*pb.Op)
defs := make(map[digest.Digest][]byte)
platforms := make(map[digest.Digest]*specs.Platform)
var dgst digest.Digest
for _, dt := range def.Def {
var op pb.Op
if err := (&op).Unmarshal(dt); err != nil {
return nil, errors.Wrap(err, "failed to parse llb proto op")
}
dgst = digest.FromBytes(dt)
ops[dgst] = &op
defs[dgst] = dt
var platform *specs.Platform
if op.Platform != nil {
spec := op.Platform.Spec()
platform = &spec
}
platforms[dgst] = platform
}
srcs := map[digest.Digest][]*SourceLocation{}
if def.Source != nil {
sourceMaps := make([]*SourceMap, len(def.Source.Infos))
for i, info := range def.Source.Infos {
var st *State
sdef := info.Definition
if sdef != nil {
op, err := NewDefinitionOp(sdef)
if err != nil {
return nil, err
}
state := NewState(op)
st = &state
}
sourceMaps[i] = NewSourceMap(st, info.Filename, info.Data)
}
for dgst, locs := range def.Source.Locations {
for _, loc := range locs.Locations {
if loc.SourceIndex < 0 || int(loc.SourceIndex) >= len(sourceMaps) {
return nil, errors.Errorf("failed to find source map with index %d", loc.SourceIndex)
}
srcs[digest.Digest(dgst)] = append(srcs[digest.Digest(dgst)], &SourceLocation{
SourceMap: sourceMaps[int(loc.SourceIndex)],
Ranges: loc.Ranges,
})
}
}
}
var index pb.OutputIndex
if dgst != "" {
index = ops[dgst].Inputs[0].Index
dgst = ops[dgst].Inputs[0].Digest
}
return &DefinitionOp{
ops: ops,
defs: defs,
metas: def.Metadata,
sources: srcs,
platforms: platforms,
dgst: dgst,
index: index,
inputCache: make(map[digest.Digest][]*DefinitionOp),
}, nil
}
func (d *DefinitionOp) ToInput(ctx context.Context, c *Constraints) (*pb.Input, error) {
return d.Output().ToInput(ctx, c)
}
func (d *DefinitionOp) Vertex(context.Context) Vertex {
return d
}
func (d *DefinitionOp) Validate(context.Context) error {
// Scratch state has no digest, ops or metas.
if d.dgst == "" {
return nil
}
d.mu.Lock()
defer d.mu.Unlock()
if len(d.ops) == 0 || len(d.defs) == 0 || len(d.metas) == 0 {
return errors.Errorf("invalid definition op with no ops %d %d", len(d.ops), len(d.metas))
}
_, ok := d.ops[d.dgst]
if !ok {
return errors.Errorf("invalid definition op with unknown op %q", d.dgst)
}
_, ok = d.defs[d.dgst]
if !ok {
return errors.Errorf("invalid definition op with unknown def %q", d.dgst)
}
_, ok = d.metas[d.dgst]
if !ok {
return errors.Errorf("invalid definition op with unknown metas %q", d.dgst)
}
// It is possible for d.index >= len(d.ops[d.dgst]) when depending on scratch
// images.
if d.index < 0 {
return errors.Errorf("invalid definition op with invalid index")
}
return nil
}
func (d *DefinitionOp) Marshal(ctx context.Context, c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, []*SourceLocation, error) {
if d.dgst == "" {
return "", nil, nil, nil, errors.Errorf("cannot marshal empty definition op")
}
if err := d.Validate(ctx); err != nil {
return "", nil, nil, nil, err
}
d.mu.Lock()
defer d.mu.Unlock()
meta := d.metas[d.dgst]
return d.dgst, d.defs[d.dgst], &meta, d.sources[d.dgst], nil
}
func (d *DefinitionOp) Output() Output {
if d.dgst == "" {
return nil
}
d.mu.Lock()
platform := d.platforms[d.dgst]
d.mu.Unlock()
return &output{vertex: d, platform: platform, getIndex: func() (pb.OutputIndex, error) {
return d.index, nil
}}
}
func (d *DefinitionOp) Inputs() []Output {
if d.dgst == "" {
return nil
}
var inputs []Output
d.mu.Lock()
op := d.ops[d.dgst]
platform := d.platforms[d.dgst]
d.mu.Unlock()
for _, input := range op.Inputs {
var vtx *DefinitionOp
d.mu.Lock()
if existingIndexes, ok := d.inputCache[input.Digest]; ok {
if int(input.Index) < len(existingIndexes) && existingIndexes[input.Index] != nil {
vtx = existingIndexes[input.Index]
}
}
if vtx == nil {
vtx = &DefinitionOp{
ops: d.ops,
defs: d.defs,
metas: d.metas,
platforms: d.platforms,
dgst: input.Digest,
index: input.Index,
inputCache: d.inputCache,
}
existingIndexes := d.inputCache[input.Digest]
indexDiff := int(input.Index) - len(existingIndexes)
if indexDiff >= 0 {
// make room in the slice for the new index being set
existingIndexes = append(existingIndexes, make([]*DefinitionOp, indexDiff+1)...)
}
existingIndexes[input.Index] = vtx
d.inputCache[input.Digest] = existingIndexes
}
d.mu.Unlock()
inputs = append(inputs, &output{vertex: vtx, platform: platform, getIndex: func() (pb.OutputIndex, error) {
return pb.OutputIndex(vtx.index), nil
}})
}
return inputs
}