1package solver
2
3import (
4	"context"
5
6	digest "github.com/opencontainers/go-digest"
7)
8
9type exporter struct {
10	k       *CacheKey
11	records []*CacheRecord
12	record  *CacheRecord
13
14	res      []CacheExporterRecord
15	edge     *edge // for secondaryExporters
16	override *bool
17}
18
19func addBacklinks(t CacheExporterTarget, rec CacheExporterRecord, cm *cacheManager, id string, bkm map[string]CacheExporterRecord) (CacheExporterRecord, error) {
20	if rec == nil {
21		var ok bool
22		rec, ok = bkm[id]
23		if ok && rec != nil {
24			return rec, nil
25		}
26		_ = ok
27	}
28	bkm[id] = nil
29	if err := cm.backend.WalkBacklinks(id, func(id string, link CacheInfoLink) error {
30		if rec == nil {
31			rec = t.Add(link.Digest)
32		}
33		r, ok := bkm[id]
34		if !ok {
35			var err error
36			r, err = addBacklinks(t, nil, cm, id, bkm)
37			if err != nil {
38				return err
39			}
40		}
41		if r != nil {
42			rec.LinkFrom(r, int(link.Input), link.Selector.String())
43		}
44		return nil
45	}); err != nil {
46		return nil, err
47	}
48	if rec == nil {
49		rec = t.Add(digest.Digest(id))
50	}
51	bkm[id] = rec
52	return rec, nil
53}
54
55type backlinkT struct{}
56
57var backlinkKey = backlinkT{}
58
59func (e *exporter) ExportTo(ctx context.Context, t CacheExporterTarget, opt CacheExportOpt) ([]CacheExporterRecord, error) {
60	var bkm map[string]CacheExporterRecord
61
62	if bk := ctx.Value(backlinkKey); bk == nil {
63		bkm = map[string]CacheExporterRecord{}
64		ctx = context.WithValue(ctx, backlinkKey, bkm)
65	} else {
66		bkm = bk.(map[string]CacheExporterRecord)
67	}
68
69	if t.Visited(e) {
70		return e.res, nil
71	}
72	t.Visit(e)
73
74	deps := e.k.Deps()
75
76	type expr struct {
77		r        CacheExporterRecord
78		selector digest.Digest
79	}
80
81	rec := t.Add(rootKey(e.k.Digest(), e.k.Output()))
82	allRec := []CacheExporterRecord{rec}
83
84	addRecord := true
85
86	if e.override != nil {
87		addRecord = *e.override
88	}
89
90	if e.record == nil && len(e.k.Deps()) > 0 {
91		e.record = getBestResult(e.records)
92	}
93
94	var remote *Remote
95	if v := e.record; v != nil && len(e.k.Deps()) > 0 && addRecord {
96		cm := v.cacheManager
97		key := cm.getID(v.key)
98		res, err := cm.backend.Load(key, v.ID)
99		if err != nil {
100			return nil, err
101		}
102
103		remote, err = cm.results.LoadRemote(ctx, res, opt.Session)
104		if err != nil {
105			return nil, err
106		}
107
108		if remote == nil && opt.Mode != CacheExportModeRemoteOnly {
109			res, err := cm.results.Load(ctx, res)
110			if err != nil {
111				return nil, err
112			}
113			remote, err = opt.Convert(ctx, res)
114			if err != nil {
115				return nil, err
116			}
117			res.Release(context.TODO())
118		}
119
120		if remote != nil {
121			for _, rec := range allRec {
122				rec.AddResult(v.CreatedAt, remote)
123			}
124		}
125	}
126
127	if remote != nil && opt.Mode == CacheExportModeMin {
128		opt.Mode = CacheExportModeRemoteOnly
129	}
130
131	srcs := make([][]expr, len(deps))
132
133	for i, deps := range deps {
134		for _, dep := range deps {
135			recs, err := dep.CacheKey.Exporter.ExportTo(ctx, t, opt)
136			if err != nil {
137				return nil, nil
138			}
139			for _, r := range recs {
140				srcs[i] = append(srcs[i], expr{r: r, selector: dep.Selector})
141			}
142		}
143	}
144
145	if e.edge != nil {
146		for _, de := range e.edge.secondaryExporters {
147			recs, err := de.cacheKey.CacheKey.Exporter.ExportTo(ctx, t, opt)
148			if err != nil {
149				return nil, nil
150			}
151			for _, r := range recs {
152				srcs[de.index] = append(srcs[de.index], expr{r: r, selector: de.cacheKey.Selector})
153			}
154		}
155	}
156
157	for i, srcs := range srcs {
158		for _, src := range srcs {
159			rec.LinkFrom(src.r, i, src.selector.String())
160		}
161	}
162
163	for cm, id := range e.k.ids {
164		if _, err := addBacklinks(t, rec, cm, id, bkm); err != nil {
165			return nil, err
166		}
167	}
168
169	if v := e.record; v != nil && len(deps) == 0 {
170		cm := v.cacheManager
171		key := cm.getID(v.key)
172		if err := cm.backend.WalkIDsByResult(v.ID, func(id string) error {
173			if id == key {
174				return nil
175			}
176			allRec = append(allRec, t.Add(digest.Digest(id)))
177			return nil
178		}); err != nil {
179			return nil, err
180		}
181	}
182
183	e.res = allRec
184
185	return e.res, nil
186}
187
188func getBestResult(records []*CacheRecord) *CacheRecord {
189	var rec *CacheRecord
190	for _, r := range records {
191		if rec == nil || rec.CreatedAt.Before(r.CreatedAt) || (rec.CreatedAt.Equal(r.CreatedAt) && rec.Priority < r.Priority) {
192			rec = r
193		}
194	}
195	return rec
196}
197
198type mergedExporter struct {
199	exporters []CacheExporter
200}
201
202func (e *mergedExporter) ExportTo(ctx context.Context, t CacheExporterTarget, opt CacheExportOpt) (er []CacheExporterRecord, err error) {
203	for _, e := range e.exporters {
204		r, err := e.ExportTo(ctx, t, opt)
205		if err != nil {
206			return nil, err
207		}
208		er = append(er, r...)
209	}
210	return
211}
212