You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package llb
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/moby/buildkit/solver/pb"
|
|
digest "github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type MergeOp struct {
|
|
MarshalCache
|
|
inputs []Output
|
|
output Output
|
|
constraints Constraints
|
|
}
|
|
|
|
func NewMerge(inputs []State, c Constraints) *MergeOp {
|
|
op := &MergeOp{constraints: c}
|
|
for _, input := range inputs {
|
|
op.inputs = append(op.inputs, input.Output())
|
|
}
|
|
op.output = &output{vertex: op}
|
|
return op
|
|
}
|
|
|
|
func (m *MergeOp) Validate(ctx context.Context, constraints *Constraints) error {
|
|
if len(m.inputs) < 2 {
|
|
return errors.Errorf("merge must have at least 2 inputs")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MergeOp) Marshal(ctx context.Context, constraints *Constraints) (digest.Digest, []byte, *pb.OpMetadata, []*SourceLocation, error) {
|
|
if m.Cached(constraints) {
|
|
return m.Load()
|
|
}
|
|
if err := m.Validate(ctx, constraints); err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
|
|
pop, md := MarshalConstraints(constraints, &m.constraints)
|
|
pop.Platform = nil // merge op is not platform specific
|
|
|
|
op := &pb.MergeOp{}
|
|
for _, input := range m.inputs {
|
|
op.Inputs = append(op.Inputs, &pb.MergeInput{Input: pb.InputIndex(len(pop.Inputs))})
|
|
pbInput, err := input.ToInput(ctx, constraints)
|
|
if err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
pop.Inputs = append(pop.Inputs, pbInput)
|
|
}
|
|
pop.Op = &pb.Op_Merge{Merge: op}
|
|
|
|
dt, err := pop.Marshal()
|
|
if err != nil {
|
|
return "", nil, nil, nil, err
|
|
}
|
|
|
|
m.Store(dt, md, m.constraints.SourceLocations, constraints)
|
|
return m.Load()
|
|
}
|
|
|
|
func (m *MergeOp) Output() Output {
|
|
return m.output
|
|
}
|
|
|
|
func (m *MergeOp) Inputs() []Output {
|
|
return m.inputs
|
|
}
|
|
|
|
// Merge merges multiple states into a single state. This is useful in
|
|
// conjunction with [Diff] to create set of patches which are independent of
|
|
// each other to a base state without affecting the cache of other merged
|
|
// states.
|
|
// As an example, lets say you have a rootfs with the following directories:
|
|
//
|
|
// / /bin /etc /opt /tmp
|
|
//
|
|
// Now lets say you want to copy a directory /etc/foo from one state and a
|
|
// binary /bin/bar from another state.
|
|
// [Copy] makes a duplicate of file on top of another directory.
|
|
// Merge creates a directory whose contents is an overlay of 2 states on top of each other.
|
|
//
|
|
// With "Merge" you can do this:
|
|
//
|
|
// fooState := Diff(rootfs, fooState)
|
|
// barState := Diff(rootfs, barState)
|
|
//
|
|
// Then merge the results with:
|
|
//
|
|
// Merge(rootfs, fooDiff, barDiff)
|
|
//
|
|
// The resulting state will have both /etc/foo and /bin/bar, but because Merge
|
|
// was used, changing the contents of "fooDiff" does not require copying
|
|
// "barDiff" again.
|
|
func Merge(inputs []State, opts ...ConstraintsOpt) State {
|
|
// filter out any scratch inputs, which have no effect when merged
|
|
var filteredInputs []State
|
|
for _, input := range inputs {
|
|
if input.Output() != nil {
|
|
filteredInputs = append(filteredInputs, input)
|
|
}
|
|
}
|
|
if len(filteredInputs) == 0 {
|
|
// a merge of only scratch results in scratch
|
|
return Scratch()
|
|
}
|
|
if len(filteredInputs) == 1 {
|
|
// a merge of a single non-empty input results in that non-empty input
|
|
return filteredInputs[0]
|
|
}
|
|
|
|
var c Constraints
|
|
for _, o := range opts {
|
|
o.SetConstraintsOption(&c)
|
|
}
|
|
addCap(&c, pb.CapMergeOp)
|
|
return filteredInputs[0].WithOutput(NewMerge(filteredInputs, c).Output())
|
|
}
|