buildkit/solver/exporter.go

246 lines
5.5 KiB
Go

package solver
import (
"context"
digest "github.com/opencontainers/go-digest"
)
type exporter struct {
k *CacheKey
records []*CacheRecord
record *CacheRecord
edge *edge // for secondaryExporters
override *bool
}
func addBacklinks(t CacheExporterTarget, rec CacheExporterRecord, cm *cacheManager, id string, bkm map[string]CacheExporterRecord) (CacheExporterRecord, error) {
if rec == nil {
var ok bool
rec, ok = bkm[id]
if ok && rec != nil {
return rec, nil
}
_ = ok
}
bkm[id] = nil
if err := cm.backend.WalkBacklinks(id, func(id string, link CacheInfoLink) error {
if rec == nil {
rec = t.Add(link.Digest)
}
r, ok := bkm[id]
if !ok {
var err error
r, err = addBacklinks(t, nil, cm, id, bkm)
if err != nil {
return err
}
}
if r != nil {
rec.LinkFrom(r, int(link.Input), link.Selector.String())
}
return nil
}); err != nil {
return nil, err
}
if rec == nil {
rec = t.Add(digest.Digest(id))
}
bkm[id] = rec
return rec, nil
}
type contextT string
var backlinkKey = contextT("solver/exporter/backlinks")
var resKey = contextT("solver/exporter/res")
func (e *exporter) ExportTo(ctx context.Context, t CacheExporterTarget, opt CacheExportOpt) ([]CacheExporterRecord, error) {
var bkm map[string]CacheExporterRecord
if bk := ctx.Value(backlinkKey); bk == nil {
bkm = map[string]CacheExporterRecord{}
ctx = context.WithValue(ctx, backlinkKey, bkm)
} else {
bkm = bk.(map[string]CacheExporterRecord)
}
var res map[*exporter][]CacheExporterRecord
if r := ctx.Value(resKey); r == nil {
res = map[*exporter][]CacheExporterRecord{}
ctx = context.WithValue(ctx, resKey, res)
} else {
res = r.(map[*exporter][]CacheExporterRecord)
}
if t.Visited(e) {
return res[e], nil
}
t.Visit(e)
deps := e.k.Deps()
type expr struct {
r CacheExporterRecord
selector digest.Digest
}
recKey := rootKey(e.k.Digest(), e.k.Output())
rec := t.Add(recKey)
allRec := []CacheExporterRecord{rec}
addRecord := true
if e.override != nil {
addRecord = *e.override
}
if e.record == nil && len(e.k.Deps()) > 0 {
e.record = getBestResult(e.records)
}
var remote *Remote
if v := e.record; v != nil && len(e.k.Deps()) > 0 && addRecord {
var variants []CacheExporterRecord
cm := v.cacheManager
key := cm.getID(v.key)
res, err := cm.backend.Load(key, v.ID)
if err != nil {
return nil, err
}
remotes, err := cm.results.LoadRemotes(ctx, res, opt.CompressionOpt, opt.Session)
if err != nil {
return nil, err
}
if len(remotes) > 0 {
remote, remotes = remotes[0], remotes[1:] // pop the first element
}
if opt.CompressionOpt != nil {
for _, r := range remotes { // record all remaining remotes as well
rec := t.Add(recKey)
rec.AddResult(v.CreatedAt, r)
variants = append(variants, rec)
}
}
if (remote == nil || opt.CompressionOpt != nil) && opt.Mode != CacheExportModeRemoteOnly {
res, err := cm.results.Load(ctx, res)
if err != nil {
return nil, err
}
remotes, err := opt.ResolveRemotes(ctx, res)
if err != nil {
return nil, err
}
res.Release(context.TODO())
if remote == nil && len(remotes) > 0 {
remote, remotes = remotes[0], remotes[1:] // pop the first element
}
if opt.CompressionOpt != nil {
for _, r := range remotes { // record all remaining remotes as well
rec := t.Add(recKey)
rec.AddResult(v.CreatedAt, r)
variants = append(variants, rec)
}
}
}
if remote != nil {
for _, rec := range allRec {
rec.AddResult(v.CreatedAt, remote)
}
}
allRec = append(allRec, variants...)
}
if remote != nil && opt.Mode == CacheExportModeMin {
opt.Mode = CacheExportModeRemoteOnly
}
srcs := make([][]expr, len(deps))
for i, deps := range deps {
for _, dep := range deps {
recs, err := dep.CacheKey.Exporter.ExportTo(ctx, t, opt)
if err != nil {
return nil, nil
}
for _, r := range recs {
srcs[i] = append(srcs[i], expr{r: r, selector: dep.Selector})
}
}
}
if e.edge != nil {
for _, de := range e.edge.secondaryExporters {
recs, err := de.cacheKey.CacheKey.Exporter.ExportTo(ctx, t, opt)
if err != nil {
return nil, nil
}
for _, r := range recs {
srcs[de.index] = append(srcs[de.index], expr{r: r, selector: de.cacheKey.Selector})
}
}
}
for _, rec := range allRec {
for i, srcs := range srcs {
for _, src := range srcs {
rec.LinkFrom(src.r, i, src.selector.String())
}
}
for cm, id := range e.k.ids {
if _, err := addBacklinks(t, rec, cm, id, bkm); err != nil {
return nil, err
}
}
}
if v := e.record; v != nil && len(deps) == 0 {
cm := v.cacheManager
key := cm.getID(v.key)
if err := cm.backend.WalkIDsByResult(v.ID, func(id string) error {
if id == key {
return nil
}
allRec = append(allRec, t.Add(digest.Digest(id)))
return nil
}); err != nil {
return nil, err
}
}
res[e] = allRec
return allRec, nil
}
func getBestResult(records []*CacheRecord) *CacheRecord {
var rec *CacheRecord
for _, r := range records {
if rec == nil || rec.CreatedAt.Before(r.CreatedAt) || (rec.CreatedAt.Equal(r.CreatedAt) && rec.Priority < r.Priority) {
rec = r
}
}
return rec
}
type mergedExporter struct {
exporters []CacheExporter
}
func (e *mergedExporter) ExportTo(ctx context.Context, t CacheExporterTarget, opt CacheExportOpt) (er []CacheExporterRecord, err error) {
for _, e := range e.exporters {
r, err := e.ExportTo(ctx, t, opt)
if err != nil {
return nil, err
}
er = append(er, r...)
}
return
}