mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			728 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			728 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package llb
 | 
						|
 | 
						|
import (
 | 
						|
	_ "crypto/sha256"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/moby/buildkit/solver/pb"
 | 
						|
	digest "github.com/opencontainers/go-digest"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
// Examples:
 | 
						|
// local := llb.Local(...)
 | 
						|
// llb.Image().Dir("/abc").File(Mkdir("./foo").Mkfile("/abc/foo/bar", []byte("data")))
 | 
						|
// llb.Image().File(Mkdir("/foo").Mkfile("/foo/bar", []byte("data")))
 | 
						|
// llb.Image().File(Copy(local, "/foo", "/bar")).File(Copy(local, "/foo2", "/bar2"))
 | 
						|
//
 | 
						|
// a := Mkdir("./foo")  // *FileAction /ced/foo
 | 
						|
// b := Mkdir("./bar") // /abc/bar
 | 
						|
// c := b.Copy(a.WithState(llb.Scratch().Dir("/ced")), "./foo", "./baz") // /abc/baz
 | 
						|
// llb.Image().Dir("/abc").File(c)
 | 
						|
//
 | 
						|
// In future this can be extended to multiple outputs with:
 | 
						|
// a := Mkdir("./foo")
 | 
						|
// b, id := a.GetSelector()
 | 
						|
// c := b.Mkdir("./bar")
 | 
						|
// filestate = state.File(c)
 | 
						|
// filestate.GetOutput(id).Exec()
 | 
						|
 | 
						|
func NewFileOp(s State, action *FileAction, c Constraints) *FileOp {
 | 
						|
	action = action.bind(s)
 | 
						|
 | 
						|
	f := &FileOp{
 | 
						|
		action:      action,
 | 
						|
		constraints: c,
 | 
						|
	}
 | 
						|
 | 
						|
	f.output = &output{vertex: f, getIndex: func() (pb.OutputIndex, error) {
 | 
						|
		return pb.OutputIndex(0), nil
 | 
						|
	}}
 | 
						|
 | 
						|
	return f
 | 
						|
}
 | 
						|
 | 
						|
// CopyInput is either llb.State or *FileActionWithState
 | 
						|
type CopyInput interface {
 | 
						|
	isFileOpCopyInput()
 | 
						|
}
 | 
						|
 | 
						|
type subAction interface {
 | 
						|
	toProtoAction(string, pb.InputIndex) pb.IsFileAction
 | 
						|
}
 | 
						|
 | 
						|
type FileAction struct {
 | 
						|
	state  *State
 | 
						|
	prev   *FileAction
 | 
						|
	action subAction
 | 
						|
	err    error
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
 | 
						|
	a := Mkdir(p, m, opt...)
 | 
						|
	a.prev = fa
 | 
						|
	return a
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) Mkfile(p string, m os.FileMode, dt []byte, opt ...MkfileOption) *FileAction {
 | 
						|
	a := Mkfile(p, m, dt, opt...)
 | 
						|
	a.prev = fa
 | 
						|
	return a
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) Rm(p string, opt ...RmOption) *FileAction {
 | 
						|
	a := Rm(p, opt...)
 | 
						|
	a.prev = fa
 | 
						|
	return a
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) Copy(input CopyInput, src, dest string, opt ...CopyOption) *FileAction {
 | 
						|
	a := Copy(input, src, dest, opt...)
 | 
						|
	a.prev = fa
 | 
						|
	return a
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) allOutputs(m map[Output]struct{}) {
 | 
						|
	if fa == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if fa.state != nil && fa.state.Output() != nil {
 | 
						|
		m[fa.state.Output()] = struct{}{}
 | 
						|
	}
 | 
						|
 | 
						|
	if a, ok := fa.action.(*fileActionCopy); ok {
 | 
						|
		if a.state != nil {
 | 
						|
			if out := a.state.Output(); out != nil {
 | 
						|
				m[out] = struct{}{}
 | 
						|
			}
 | 
						|
		} else if a.fas != nil {
 | 
						|
			a.fas.allOutputs(m)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	fa.prev.allOutputs(m)
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) bind(s State) *FileAction {
 | 
						|
	if fa == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	fa2 := *fa
 | 
						|
	fa2.prev = fa.prev.bind(s)
 | 
						|
	fa2.state = &s
 | 
						|
	return &fa2
 | 
						|
}
 | 
						|
 | 
						|
func (fa *FileAction) WithState(s State) CopyInput {
 | 
						|
	return &fileActionWithState{FileAction: fa.bind(s)}
 | 
						|
}
 | 
						|
 | 
						|
type fileActionWithState struct {
 | 
						|
	*FileAction
 | 
						|
}
 | 
						|
 | 
						|
func (fas *fileActionWithState) isFileOpCopyInput() {}
 | 
						|
 | 
						|
func Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
 | 
						|
	var mi MkdirInfo
 | 
						|
	for _, o := range opt {
 | 
						|
		o.SetMkdirOption(&mi)
 | 
						|
	}
 | 
						|
	return &FileAction{
 | 
						|
		action: &fileActionMkdir{
 | 
						|
			file: p,
 | 
						|
			mode: m,
 | 
						|
			info: mi,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type fileActionMkdir struct {
 | 
						|
	file string
 | 
						|
	mode os.FileMode
 | 
						|
	info MkdirInfo
 | 
						|
}
 | 
						|
 | 
						|
func (a *fileActionMkdir) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
 | 
						|
	return &pb.FileAction_Mkdir{
 | 
						|
		Mkdir: &pb.FileActionMkDir{
 | 
						|
			Path:        normalizePath(parent, a.file, false),
 | 
						|
			Mode:        int32(a.mode & 0777),
 | 
						|
			MakeParents: a.info.MakeParents,
 | 
						|
			Owner:       a.info.ChownOpt.marshal(base),
 | 
						|
			Timestamp:   marshalTime(a.info.CreatedTime),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type MkdirOption interface {
 | 
						|
	SetMkdirOption(*MkdirInfo)
 | 
						|
}
 | 
						|
 | 
						|
type ChownOption interface {
 | 
						|
	MkdirOption
 | 
						|
	MkfileOption
 | 
						|
	CopyOption
 | 
						|
}
 | 
						|
 | 
						|
type mkdirOptionFunc func(*MkdirInfo)
 | 
						|
 | 
						|
func (fn mkdirOptionFunc) SetMkdirOption(mi *MkdirInfo) {
 | 
						|
	fn(mi)
 | 
						|
}
 | 
						|
 | 
						|
var _ MkdirOption = &MkdirInfo{}
 | 
						|
 | 
						|
func WithParents(b bool) MkdirOption {
 | 
						|
	return mkdirOptionFunc(func(mi *MkdirInfo) {
 | 
						|
		mi.MakeParents = b
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
type MkdirInfo struct {
 | 
						|
	MakeParents bool
 | 
						|
	ChownOpt    *ChownOpt
 | 
						|
	CreatedTime *time.Time
 | 
						|
}
 | 
						|
 | 
						|
func (mi *MkdirInfo) SetMkdirOption(mi2 *MkdirInfo) {
 | 
						|
	*mi2 = *mi
 | 
						|
}
 | 
						|
 | 
						|
func WithUser(name string) ChownOption {
 | 
						|
	opt := ChownOpt{}
 | 
						|
 | 
						|
	parts := strings.SplitN(name, ":", 2)
 | 
						|
	for i, v := range parts {
 | 
						|
		switch i {
 | 
						|
		case 0:
 | 
						|
			uid, err := parseUID(v)
 | 
						|
			if err != nil {
 | 
						|
				opt.User = &UserOpt{Name: v}
 | 
						|
			} else {
 | 
						|
				opt.User = &UserOpt{UID: uid}
 | 
						|
			}
 | 
						|
		case 1:
 | 
						|
			gid, err := parseUID(v)
 | 
						|
			if err != nil {
 | 
						|
				opt.Group = &UserOpt{Name: v}
 | 
						|
			} else {
 | 
						|
				opt.Group = &UserOpt{UID: gid}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return opt
 | 
						|
}
 | 
						|
 | 
						|
func parseUID(str string) (int, error) {
 | 
						|
	if str == "root" {
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
	uid, err := strconv.ParseInt(str, 10, 32)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
	return int(uid), nil
 | 
						|
}
 | 
						|
 | 
						|
func WithUIDGID(uid, gid int) ChownOption {
 | 
						|
	return ChownOpt{
 | 
						|
		User:  &UserOpt{UID: uid},
 | 
						|
		Group: &UserOpt{UID: gid},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type ChownOpt struct {
 | 
						|
	User  *UserOpt
 | 
						|
	Group *UserOpt
 | 
						|
}
 | 
						|
 | 
						|
func (co ChownOpt) SetMkdirOption(mi *MkdirInfo) {
 | 
						|
	mi.ChownOpt = &co
 | 
						|
}
 | 
						|
func (co ChownOpt) SetMkfileOption(mi *MkfileInfo) {
 | 
						|
	mi.ChownOpt = &co
 | 
						|
}
 | 
						|
func (co ChownOpt) SetCopyOption(mi *CopyInfo) {
 | 
						|
	mi.ChownOpt = &co
 | 
						|
}
 | 
						|
 | 
						|
func (cp *ChownOpt) marshal(base pb.InputIndex) *pb.ChownOpt {
 | 
						|
	if cp == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return &pb.ChownOpt{
 | 
						|
		User:  cp.User.marshal(base),
 | 
						|
		Group: cp.Group.marshal(base),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type UserOpt struct {
 | 
						|
	UID  int
 | 
						|
	Name string
 | 
						|
}
 | 
						|
 | 
						|
func (up *UserOpt) marshal(base pb.InputIndex) *pb.UserOpt {
 | 
						|
	if up == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if up.Name != "" {
 | 
						|
		return &pb.UserOpt{User: &pb.UserOpt_ByName{ByName: &pb.NamedUserOpt{
 | 
						|
			Name: up.Name, Input: base}}}
 | 
						|
	}
 | 
						|
	return &pb.UserOpt{User: &pb.UserOpt_ByID{ByID: uint32(up.UID)}}
 | 
						|
}
 | 
						|
 | 
						|
func Mkfile(p string, m os.FileMode, dt []byte, opts ...MkfileOption) *FileAction {
 | 
						|
	var mi MkfileInfo
 | 
						|
	for _, o := range opts {
 | 
						|
		o.SetMkfileOption(&mi)
 | 
						|
	}
 | 
						|
 | 
						|
	return &FileAction{
 | 
						|
		action: &fileActionMkfile{
 | 
						|
			file: p,
 | 
						|
			mode: m,
 | 
						|
			dt:   dt,
 | 
						|
			info: mi,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type MkfileOption interface {
 | 
						|
	SetMkfileOption(*MkfileInfo)
 | 
						|
}
 | 
						|
 | 
						|
type MkfileInfo struct {
 | 
						|
	ChownOpt    *ChownOpt
 | 
						|
	CreatedTime *time.Time
 | 
						|
}
 | 
						|
 | 
						|
func (mi *MkfileInfo) SetMkfileOption(mi2 *MkfileInfo) {
 | 
						|
	*mi2 = *mi
 | 
						|
}
 | 
						|
 | 
						|
var _ MkfileOption = &MkfileInfo{}
 | 
						|
 | 
						|
type fileActionMkfile struct {
 | 
						|
	file string
 | 
						|
	mode os.FileMode
 | 
						|
	dt   []byte
 | 
						|
	info MkfileInfo
 | 
						|
}
 | 
						|
 | 
						|
func (a *fileActionMkfile) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
 | 
						|
	return &pb.FileAction_Mkfile{
 | 
						|
		Mkfile: &pb.FileActionMkFile{
 | 
						|
			Path:      normalizePath(parent, a.file, false),
 | 
						|
			Mode:      int32(a.mode & 0777),
 | 
						|
			Data:      a.dt,
 | 
						|
			Owner:     a.info.ChownOpt.marshal(base),
 | 
						|
			Timestamp: marshalTime(a.info.CreatedTime),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Rm(p string, opts ...RmOption) *FileAction {
 | 
						|
	var mi RmInfo
 | 
						|
	for _, o := range opts {
 | 
						|
		o.SetRmOption(&mi)
 | 
						|
	}
 | 
						|
 | 
						|
	return &FileAction{
 | 
						|
		action: &fileActionRm{
 | 
						|
			file: p,
 | 
						|
			info: mi,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type RmOption interface {
 | 
						|
	SetRmOption(*RmInfo)
 | 
						|
}
 | 
						|
 | 
						|
type rmOptionFunc func(*RmInfo)
 | 
						|
 | 
						|
func (fn rmOptionFunc) SetRmOption(mi *RmInfo) {
 | 
						|
	fn(mi)
 | 
						|
}
 | 
						|
 | 
						|
type RmInfo struct {
 | 
						|
	AllowNotFound bool
 | 
						|
	AllowWildcard bool
 | 
						|
}
 | 
						|
 | 
						|
func (mi *RmInfo) SetRmOption(mi2 *RmInfo) {
 | 
						|
	*mi2 = *mi
 | 
						|
}
 | 
						|
 | 
						|
var _ RmOption = &RmInfo{}
 | 
						|
 | 
						|
func WithAllowNotFound(b bool) RmOption {
 | 
						|
	return rmOptionFunc(func(mi *RmInfo) {
 | 
						|
		mi.AllowNotFound = b
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func WithAllowWildcard(b bool) RmOption {
 | 
						|
	return rmOptionFunc(func(mi *RmInfo) {
 | 
						|
		mi.AllowWildcard = b
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
type fileActionRm struct {
 | 
						|
	file string
 | 
						|
	info RmInfo
 | 
						|
}
 | 
						|
 | 
						|
func (a *fileActionRm) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
 | 
						|
	return &pb.FileAction_Rm{
 | 
						|
		Rm: &pb.FileActionRm{
 | 
						|
			Path:          normalizePath(parent, a.file, false),
 | 
						|
			AllowNotFound: a.info.AllowNotFound,
 | 
						|
			AllowWildcard: a.info.AllowWildcard,
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Copy(input CopyInput, src, dest string, opts ...CopyOption) *FileAction {
 | 
						|
	var state *State
 | 
						|
	var fas *fileActionWithState
 | 
						|
	var err error
 | 
						|
	if st, ok := input.(State); ok {
 | 
						|
		state = &st
 | 
						|
	} else if v, ok := input.(*fileActionWithState); ok {
 | 
						|
		fas = v
 | 
						|
	} else {
 | 
						|
		err = errors.Errorf("invalid input type %T for copy", input)
 | 
						|
	}
 | 
						|
 | 
						|
	var mi CopyInfo
 | 
						|
	for _, o := range opts {
 | 
						|
		o.SetCopyOption(&mi)
 | 
						|
	}
 | 
						|
 | 
						|
	return &FileAction{
 | 
						|
		action: &fileActionCopy{
 | 
						|
			state: state,
 | 
						|
			fas:   fas,
 | 
						|
			src:   src,
 | 
						|
			dest:  dest,
 | 
						|
			info:  mi,
 | 
						|
		},
 | 
						|
		err: err,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type CopyOption interface {
 | 
						|
	SetCopyOption(*CopyInfo)
 | 
						|
}
 | 
						|
 | 
						|
type CopyInfo struct {
 | 
						|
	Mode                *os.FileMode
 | 
						|
	FollowSymlinks      bool
 | 
						|
	CopyDirContentsOnly bool
 | 
						|
	AttemptUnpack       bool
 | 
						|
	CreateDestPath      bool
 | 
						|
	AllowWildcard       bool
 | 
						|
	AllowEmptyWildcard  bool
 | 
						|
	ChownOpt            *ChownOpt
 | 
						|
	CreatedTime         *time.Time
 | 
						|
}
 | 
						|
 | 
						|
func (mi *CopyInfo) SetCopyOption(mi2 *CopyInfo) {
 | 
						|
	*mi2 = *mi
 | 
						|
}
 | 
						|
 | 
						|
var _ CopyOption = &CopyInfo{}
 | 
						|
 | 
						|
type fileActionCopy struct {
 | 
						|
	state *State
 | 
						|
	fas   *fileActionWithState
 | 
						|
	src   string
 | 
						|
	dest  string
 | 
						|
	info  CopyInfo
 | 
						|
}
 | 
						|
 | 
						|
func (a *fileActionCopy) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
 | 
						|
	c := &pb.FileActionCopy{
 | 
						|
		Src:                              a.sourcePath(),
 | 
						|
		Dest:                             normalizePath(parent, a.dest, true),
 | 
						|
		Owner:                            a.info.ChownOpt.marshal(base),
 | 
						|
		AllowWildcard:                    a.info.AllowWildcard,
 | 
						|
		AllowEmptyWildcard:               a.info.AllowEmptyWildcard,
 | 
						|
		FollowSymlink:                    a.info.FollowSymlinks,
 | 
						|
		DirCopyContents:                  a.info.CopyDirContentsOnly,
 | 
						|
		AttemptUnpackDockerCompatibility: a.info.AttemptUnpack,
 | 
						|
		CreateDestPath:                   a.info.CreateDestPath,
 | 
						|
		Timestamp:                        marshalTime(a.info.CreatedTime),
 | 
						|
	}
 | 
						|
	if a.info.Mode != nil {
 | 
						|
		c.Mode = int32(*a.info.Mode)
 | 
						|
	} else {
 | 
						|
		c.Mode = -1
 | 
						|
	}
 | 
						|
	return &pb.FileAction_Copy{
 | 
						|
		Copy: c,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *fileActionCopy) sourcePath() string {
 | 
						|
	p := path.Clean(c.src)
 | 
						|
	if !path.IsAbs(p) {
 | 
						|
		if c.state != nil {
 | 
						|
			p = path.Join("/", c.state.GetDir(), p)
 | 
						|
		} else if c.fas != nil {
 | 
						|
			p = path.Join("/", c.fas.state.GetDir(), p)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
type CreatedTime time.Time
 | 
						|
 | 
						|
func WithCreatedTime(t time.Time) CreatedTime {
 | 
						|
	return CreatedTime(t)
 | 
						|
}
 | 
						|
 | 
						|
func (c CreatedTime) SetMkdirOption(mi *MkdirInfo) {
 | 
						|
	mi.CreatedTime = (*time.Time)(&c)
 | 
						|
}
 | 
						|
 | 
						|
func (c CreatedTime) SetMkfileOption(mi *MkfileInfo) {
 | 
						|
	mi.CreatedTime = (*time.Time)(&c)
 | 
						|
}
 | 
						|
 | 
						|
func (c CreatedTime) SetCopyOption(mi *CopyInfo) {
 | 
						|
	mi.CreatedTime = (*time.Time)(&c)
 | 
						|
}
 | 
						|
 | 
						|
func marshalTime(t *time.Time) int64 {
 | 
						|
	if t == nil {
 | 
						|
		return -1
 | 
						|
	}
 | 
						|
	return t.UnixNano()
 | 
						|
}
 | 
						|
 | 
						|
type FileOp struct {
 | 
						|
	MarshalCache
 | 
						|
	action *FileAction
 | 
						|
	output Output
 | 
						|
 | 
						|
	constraints Constraints
 | 
						|
	isValidated bool
 | 
						|
}
 | 
						|
 | 
						|
func (f *FileOp) Validate() error {
 | 
						|
	if f.isValidated {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if f.action == nil {
 | 
						|
		return errors.Errorf("action is required")
 | 
						|
	}
 | 
						|
	f.isValidated = true
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
type marshalState struct {
 | 
						|
	visited map[*FileAction]*fileActionState
 | 
						|
	inputs  []*pb.Input
 | 
						|
	actions []*fileActionState
 | 
						|
}
 | 
						|
 | 
						|
func newMarshalState() *marshalState {
 | 
						|
	return &marshalState{
 | 
						|
		visited: map[*FileAction]*fileActionState{},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type fileActionState struct {
 | 
						|
	base           pb.InputIndex
 | 
						|
	input          pb.InputIndex
 | 
						|
	inputRelative  *int
 | 
						|
	input2         pb.InputIndex
 | 
						|
	input2Relative *int
 | 
						|
	target         int
 | 
						|
	action         subAction
 | 
						|
	fa             *FileAction
 | 
						|
}
 | 
						|
 | 
						|
func (ms *marshalState) addInput(st *fileActionState, c *Constraints, o Output) (pb.InputIndex, error) {
 | 
						|
	inp, err := o.ToInput(c)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
	for i, inp2 := range ms.inputs {
 | 
						|
		if *inp == *inp2 {
 | 
						|
			return pb.InputIndex(i), nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	i := pb.InputIndex(len(ms.inputs))
 | 
						|
	ms.inputs = append(ms.inputs, inp)
 | 
						|
	return i, nil
 | 
						|
}
 | 
						|
 | 
						|
func (ms *marshalState) add(fa *FileAction, c *Constraints) (*fileActionState, error) {
 | 
						|
	if st, ok := ms.visited[fa]; ok {
 | 
						|
		return st, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if fa.err != nil {
 | 
						|
		return nil, fa.err
 | 
						|
	}
 | 
						|
 | 
						|
	var prevState *fileActionState
 | 
						|
	if parent := fa.prev; parent != nil {
 | 
						|
		var err error
 | 
						|
		prevState, err = ms.add(parent, c)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	st := &fileActionState{
 | 
						|
		action: fa.action,
 | 
						|
		input:  -1,
 | 
						|
		input2: -1,
 | 
						|
		base:   -1,
 | 
						|
		fa:     fa,
 | 
						|
	}
 | 
						|
 | 
						|
	if source := fa.state.Output(); source != nil {
 | 
						|
		inp, err := ms.addInput(st, c, source)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		st.base = inp
 | 
						|
	}
 | 
						|
 | 
						|
	if fa.prev == nil {
 | 
						|
		st.input = st.base
 | 
						|
	} else {
 | 
						|
		st.inputRelative = &prevState.target
 | 
						|
	}
 | 
						|
 | 
						|
	if a, ok := fa.action.(*fileActionCopy); ok {
 | 
						|
		if a.state != nil {
 | 
						|
			if out := a.state.Output(); out != nil {
 | 
						|
				inp, err := ms.addInput(st, c, out)
 | 
						|
				if err != nil {
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
				st.input2 = inp
 | 
						|
			}
 | 
						|
		} else if a.fas != nil {
 | 
						|
			src, err := ms.add(a.fas.FileAction, c)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			st.input2Relative = &src.target
 | 
						|
		} else {
 | 
						|
			return nil, errors.Errorf("invalid empty source for copy")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	st.target = len(ms.actions)
 | 
						|
 | 
						|
	ms.visited[fa] = st
 | 
						|
	ms.actions = append(ms.actions, st)
 | 
						|
 | 
						|
	return st, nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *FileOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, error) {
 | 
						|
	if f.Cached(c) {
 | 
						|
		return f.Load()
 | 
						|
	}
 | 
						|
	if err := f.Validate(); err != nil {
 | 
						|
		return "", nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	addCap(&f.constraints, pb.CapFileBase)
 | 
						|
 | 
						|
	pfo := &pb.FileOp{}
 | 
						|
 | 
						|
	pop, md := MarshalConstraints(c, &f.constraints)
 | 
						|
	pop.Op = &pb.Op_File{
 | 
						|
		File: pfo,
 | 
						|
	}
 | 
						|
 | 
						|
	state := newMarshalState()
 | 
						|
	_, err := state.add(f.action, c)
 | 
						|
	if err != nil {
 | 
						|
		return "", nil, nil, err
 | 
						|
	}
 | 
						|
	pop.Inputs = state.inputs
 | 
						|
 | 
						|
	for i, st := range state.actions {
 | 
						|
		output := pb.OutputIndex(-1)
 | 
						|
		if i+1 == len(state.actions) {
 | 
						|
			output = 0
 | 
						|
		}
 | 
						|
 | 
						|
		var parent string
 | 
						|
		if st.fa.state != nil {
 | 
						|
			parent = st.fa.state.GetDir()
 | 
						|
		}
 | 
						|
 | 
						|
		pfo.Actions = append(pfo.Actions, &pb.FileAction{
 | 
						|
			Input:          getIndex(st.input, len(state.inputs), st.inputRelative),
 | 
						|
			SecondaryInput: getIndex(st.input2, len(state.inputs), st.input2Relative),
 | 
						|
			Output:         output,
 | 
						|
			Action:         st.action.toProtoAction(parent, st.base),
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	dt, err := pop.Marshal()
 | 
						|
	if err != nil {
 | 
						|
		return "", nil, nil, err
 | 
						|
	}
 | 
						|
	f.Store(dt, md, c)
 | 
						|
	return f.Load()
 | 
						|
}
 | 
						|
 | 
						|
func normalizePath(parent, p string, keepSlash bool) string {
 | 
						|
	origPath := p
 | 
						|
	p = path.Clean(p)
 | 
						|
	if !path.IsAbs(p) {
 | 
						|
		p = path.Join("/", parent, p)
 | 
						|
	}
 | 
						|
	if keepSlash {
 | 
						|
		if strings.HasSuffix(origPath, "/") && !strings.HasSuffix(p, "/") {
 | 
						|
			p += "/"
 | 
						|
		} else if strings.HasSuffix(origPath, "/.") {
 | 
						|
			if p != "/" {
 | 
						|
				p += "/"
 | 
						|
			}
 | 
						|
			p += "."
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (f *FileOp) Output() Output {
 | 
						|
	return f.output
 | 
						|
}
 | 
						|
 | 
						|
func (f *FileOp) Inputs() (inputs []Output) {
 | 
						|
	mm := map[Output]struct{}{}
 | 
						|
 | 
						|
	f.action.allOutputs(mm)
 | 
						|
 | 
						|
	for o := range mm {
 | 
						|
		inputs = append(inputs, o)
 | 
						|
	}
 | 
						|
	return inputs
 | 
						|
}
 | 
						|
 | 
						|
func getIndex(input pb.InputIndex, len int, relative *int) pb.InputIndex {
 | 
						|
	if relative != nil {
 | 
						|
		return pb.InputIndex(len + *relative)
 | 
						|
	}
 | 
						|
	return input
 | 
						|
}
 |