This is the 19th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021
After combing through the data tripartite of State and Definition, Edge is now the only thing missing. Yuan Xiaobai back to the location of the code to read before: client/LLB/llbtest/platform_test. Go – TestDefaultPlatform, just saw:
e, err := llbsolver.Load(def.ToPB())
Copy the code
The result returned is exactly Edge.
def.ToPB()
func (def *Definition) ToPB(a) *pb.Definition {
md := make(map[digest.Digest]pb.OpMetadata, len(def.Metadata))
for k, v := range def.Metadata {
md[k] = v
}
return &pb.Definition{
Def: def.Def,
Source: def.Source,
Metadata: md,
}
}
Copy the code
There is nothing complicated to do but convert Definition to pb.definition. It’s even a little strange, because the objects are so much alike, they’re almost identical. So the question is, why create two instead of one? With this problem, Yuan Xiaobai continued to look at the key content: llbsolver.load.
Load
func Load(def *pb.Definition, opts ... LoadOpt) (solver.Edge, error) {
return loadLLB(def, func(dgst digest.Digest, pbOp *pb.Op, load func(digest.Digest) (solver.Vertex, error)) (solver.Vertex, error) {
opMetadata := def.Metadata[dgst]
vtx, err := newVertex(dgst, pbOp, &opMetadata, load, opts...)
iferr ! =nil {
return nil, err
}
return vtx, nil})}Copy the code
Basically, take pb.Definition and call the loadLLB function.
loadLLB
// loadLLB loads LLB.
// fn is executed sequentially.
func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Digest) (solver.Vertex, error)) (solver.Vertex, error)) (solver.Edge, error) {
if len(def.Def) == 0 {
return solver.Edge{}, errors.New("invalid empty definition")
}
allOps := make(map[digest.Digest]*pb.Op)
var dgst digest.Digest
for _, dt := range def.Def {
var op pb.Op
iferr := (&op).Unmarshal(dt); err ! =nil {
return solver.Edge{}, errors.Wrap(err, "failed to parse llb proto op")
}
dgst = digest.FromBytes(dt)
allOps[dgst] = &op
}
if len(allOps) < 2 {
return solver.Edge{}, errors.Errorf("invalid LLB with %d vertexes".len(allOps))
}
...
}
Copy the code
Def is converted to pb.Op according to pb.Definition and stored in allOps.
// loadLLB loads LLB.
// fn is executed sequentially.
func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Digest) (solver.Vertex, error)) (solver.Vertex, error)) (solver.Edge, error){... lastOp := allOps[dgst]delete(allOps, dgst)
if len(lastOp.Inputs) == 0 {
return solver.Edge{}, errors.Errorf("invalid LLB with no inputs on last vertex")
}
dgst = lastOp.Inputs[0].Digest
...
}
Copy the code
Input dependency -lastop.inputs [0].Digest Check that the last Op in Definition is an aid to logic, and does not correspond to the root of the tree: Inputs[0] :
func (s State) Marshal(ctx context.Context, co ... ConstraintsOpt) (*Definition, error){... def, err := marshal(ctx, s.Output().Vertex(ctx, c), def, smc,map[digest.Digest]struct{} {},map[Vertex]struct{}{}, c)
iferr ! =nil {
return def, err
}
inp, err := s.Output().ToInput(ctx, c)
iferr ! =nil {
return def, err
}
proto := &pb.Op{Inputs: []*pb.Input{inp}}
dt, err := proto.Marshal()
iferr ! =nil {
return def, err
}
def.Def = append(def.Def, dt)
...
}
Copy the code
Inputs: proto := &pb.Op{Inputs: []*pb.Input{inp}}
LoadLLB:
// loadLLB loads LLB.
// fn is executed sequentially.
func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Digest) (solver.Vertex, error)) (solver.Vertex, error)) (solver.Edge, error){... cache :=make(map[digest.Digest]solver.Vertex)
var rec func(dgst digest.Digest) (solver.Vertex, error)
rec = func(dgst digest.Digest) (solver.Vertex, error) {
if v, ok := cache[dgst]; ok {
return v, nil
}
op, ok := allOps[dgst]
if! ok {return nil, errors.Errorf("invalid missing input digest %s", dgst)
}
iferr := ValidateOp(op); err ! =nil {
return nil, err
}
v, err := fn(dgst, op, rec)
iferr ! =nil {
return nil, err
}
cache[dgst] = v
return v, nil
}
v, err := rec(dgst)
iferr ! =nil {
return solver.Edge{}, err
}
return solver.Edge{Vertex: v, Index: solver.Index(lastOp.Inputs[0].Index)}, nil
}
Copy the code
Finally, we call the fn function passed in as an argument, using the built-in function rec. Fn is passed in as an argument:
func(dgst digest.Digest, pbOp *pb.Op, load func(digest.Digest) (solver.Vertex, error)) (solver.Vertex, error) {
opMetadata := def.Metadata[dgst]
vtx, err := newVertex(dgst, pbOp, &opMetadata, load, opts...)
iferr ! =nil {
return nil, err
}
return vtx, nil
}
Copy the code
As you can see, this function is the context needed to pass in the newVertex call.
newVertex
func newVertex(dgst digest.Digest, op *pb.Op, opMeta *pb.OpMetadata, load func(digest.Digest) (solver.Vertex, error).opts.LoadOpt) (*vertex, error) {
opt := solver.VertexOptions{}
ifopMeta ! =nil {
opt.IgnoreCache = opMeta.IgnoreCache
opt.Description = opMeta.Description
ifopMeta.ExportCache ! =nil {
opt.ExportCache = &opMeta.ExportCache.Value
}
}
for _, fn := range opts {
iferr := fn(op, opMeta, &opt); err ! =nil {
return nil, err
}
}
vtx := &vertex{sys: op, options: opt, digest: dgst, name: llbOpName(op)}
for _, in := range op.Inputs {
sub, err := load(in.Digest)
iferr ! =nil {
return nil, err
}
vtx.inputs = append(vtx.inputs, solver.Edge{Index: solver.Index(in.Index), Vertex: sub})
}
return vtx, nil
}
Copy the code
The primary purpose is to create vertex, but note that the op.Inputs loop also calls the load() passed in, which is the reC above.
Yeah, there’s recursion again. As with recursion, we need to pay attention to two things:
- The purpose of recursive functions is to create vertex
- The recursive exit is the op without input, which is our SourceOp
// loadLLB loads LLB.
// fn is executed sequentially.
func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Digest) (solver.Vertex, error)) (solver.Vertex, error)) (solver.Edge, error){... v, err := rec(dgst)iferr ! =nil {
return solver.Edge{}, err
}
return solver.Edge{Vertex: v, Index: solver.Index(lastOp.Inputs[0].Index)}, nil
}
Copy the code
Solver. Edge = solver.Edge = solver.Edge = solver.Edge = solver.Edge = solver.Edge = solver.Edge = solver.Edge = solver.Edge
Finally, we have State, Definition, and Edge. So is it time to summon the dragon? Yuan Xiaobai took a breath full of sense of achievement.