package llb import ( "io" "slices" "sync" cerrdefs "github.com/containerd/errdefs" "github.com/containerd/platforms" "github.com/moby/buildkit/solver/pb" digest "github.com/opencontainers/go-digest" "google.golang.org/protobuf/proto" ) // Definition is the LLB definition structure with per-vertex metadata entries // Corresponds to the Definition structure defined in solver/pb.Definition. type Definition struct { Def [][]byte Metadata map[digest.Digest]OpMetadata Source *pb.Source Constraints *Constraints } func (def *Definition) ToPB() *pb.Definition { metas := make(map[string]*pb.OpMetadata, len(def.Metadata)) for dgst, meta := range def.Metadata { metas[string(dgst)] = meta.ToPB() } return &pb.Definition{ Def: def.Def, Source: def.Source, Metadata: metas, } } func (def *Definition) FromPB(x *pb.Definition) { def.Def = x.Def def.Source = x.Source def.Metadata = make(map[digest.Digest]OpMetadata, len(x.Metadata)) for dgst, meta := range x.Metadata { def.Metadata[digest.Digest(dgst)] = NewOpMetadata(meta) } } func (def *Definition) Head() (digest.Digest, error) { if len(def.Def) == 0 { return "", nil } last := def.Def[len(def.Def)-1] var pop pb.Op if err := pop.UnmarshalVT(last); err != nil { return "", err } if len(pop.Inputs) == 0 { return "", nil } return digest.Digest(pop.Inputs[0].Digest), nil } func WriteTo(def *Definition, w io.Writer) error { b, err := def.ToPB().Marshal() if err != nil { return err } _, err = w.Write(b) return err } func ReadFrom(r io.Reader) (*Definition, error) { b, err := io.ReadAll(r) if err != nil { return nil, err } var pbDef pb.Definition if err := pbDef.UnmarshalVT(b); err != nil { return nil, err } var def Definition def.FromPB(&pbDef) return &def, nil } func MarshalConstraints(base, override *Constraints) (*pb.Op, *pb.OpMetadata) { c := *base c.WorkerConstraints = slices.Clone(c.WorkerConstraints) if p := override.Platform; p != nil { c.Platform = p } c.WorkerConstraints = append(c.WorkerConstraints, override.WorkerConstraints...) c.Metadata = mergeMetadata(c.Metadata, override.Metadata) if c.Platform == nil { defaultPlatform := platforms.Normalize(platforms.DefaultSpec()) c.Platform = &defaultPlatform } opPlatform := pb.Platform{ OS: c.Platform.OS, Architecture: c.Platform.Architecture, Variant: c.Platform.Variant, OSVersion: c.Platform.OSVersion, } if c.Platform.OSFeatures != nil { opPlatform.OSFeatures = slices.Clone(c.Platform.OSFeatures) } return &pb.Op{ Platform: &opPlatform, Constraints: &pb.WorkerConstraints{ Filter: c.WorkerConstraints, }, }, c.Metadata.ToPB() } type MarshalCache struct { mu sync.Mutex cache map[*Constraints]*marshalCacheResult } type MarshalCacheInstance struct { *MarshalCache } func (mc *MarshalCache) Acquire() *MarshalCacheInstance { mc.mu.Lock() return &MarshalCacheInstance{mc} } func (mc *MarshalCacheInstance) Load(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, []*SourceLocation, error) { res, ok := mc.cache[c] if !ok { return "", nil, nil, nil, cerrdefs.ErrNotFound } return res.digest, res.dt, res.md, res.srcs, nil } func (mc *MarshalCacheInstance) Store(dt []byte, md *pb.OpMetadata, srcs []*SourceLocation, c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, []*SourceLocation, error) { res := &marshalCacheResult{ digest: digest.FromBytes(dt), dt: dt, md: md, srcs: srcs, } if mc.cache == nil { mc.cache = make(map[*Constraints]*marshalCacheResult) } mc.cache[c] = res return res.digest, res.dt, res.md, res.srcs, nil } func (mc *MarshalCacheInstance) Release() { mc.mu.Unlock() } type marshalCacheResult struct { digest digest.Digest dt []byte md *pb.OpMetadata srcs []*SourceLocation } func deterministicMarshal[Message proto.Message](m Message) ([]byte, error) { return proto.MarshalOptions{Deterministic: true}.Marshal(m) }