github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/dyndep.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package nin 16 17 import ( 18 "errors" 19 "fmt" 20 ) 21 22 // Dyndeps stores dynamically-discovered dependency information for one edge. 23 type Dyndeps struct { 24 used bool 25 restat bool 26 implicitInputs []*Node 27 implicitOutputs []*Node 28 } 29 30 func (d *Dyndeps) String() string { 31 out := "Dyndeps{in:" 32 for i, n := range d.implicitInputs { 33 if i != 0 { 34 out += "," 35 } 36 out += n.Path 37 } 38 out += "; out:" 39 for i, n := range d.implicitOutputs { 40 if i != 0 { 41 out += "," 42 } 43 out += n.Path 44 } 45 return out + "}" 46 } 47 48 // DyndepFile stores data loaded from one dyndep file. 49 // 50 // Map from an edge to its dynamically-discovered dependency information. 51 type DyndepFile map[*Edge]*Dyndeps 52 53 // DyndepLoader loads dynamically discovered dependencies, as 54 // referenced via the "dyndep" attribute in build files. 55 type DyndepLoader struct { 56 state *State 57 di DiskInterface 58 } 59 60 // NewDyndepLoader returns an initialized DyndepLoader. 61 func NewDyndepLoader(state *State, di DiskInterface) DyndepLoader { 62 return DyndepLoader{ 63 state: state, 64 di: di, 65 } 66 } 67 68 // LoadDyndeps loads a dyndep file from the given node's path and update the 69 // build graph with the new information. 70 // 71 // Caller can optionally provide a 'DyndepFile' object in which to store the 72 // information loaded from the dyndep file. 73 func (d *DyndepLoader) LoadDyndeps(node *Node, ddf DyndepFile) error { 74 // We are loading the dyndep file now so it is no longer pending. 75 node.DyndepPending = false 76 77 // Load the dyndep information from the file. 78 explain("loading dyndep file '%s'", node.Path) 79 if err := d.loadDyndepFile(node, ddf); err != nil { 80 return err 81 } 82 83 // Update each edge that specified this node as its dyndep binding. 84 outEdges := node.OutEdges 85 for _, oe := range outEdges { 86 edge := oe 87 if edge.Dyndep != node { 88 continue 89 } 90 91 ddi, ok := ddf[edge] 92 if !ok { 93 // TODO(maruel): Use %q for real quoting. 94 return fmt.Errorf("'%s' not mentioned in its dyndep file '%s'", edge.Outputs[0].Path, node.Path) 95 } 96 97 ddi.used = true 98 dyndeps := ddi 99 if err := d.updateEdge(edge, dyndeps); err != nil { 100 return err 101 } 102 } 103 104 // Reject extra outputs in dyndep file. 105 for edge, oe := range ddf { 106 if !oe.used { 107 // TODO(maruel): Use %q for real quoting. 108 return fmt.Errorf("dyndep file '%s' mentions output '%s' whose build statement does not have a dyndep binding for the file", node.Path, edge.Outputs[0].Path) 109 } 110 } 111 return nil 112 } 113 114 func (d *DyndepLoader) updateEdge(edge *Edge, dyndeps *Dyndeps) error { 115 // Add dyndep-discovered bindings to the edge. 116 // We know the edge already has its own binding 117 // scope because it has a "dyndep" binding. 118 if dyndeps.restat { 119 edge.Env.Bindings["restat"] = "1" 120 } 121 122 // Add the dyndep-discovered outputs to the edge. 123 edge.Outputs = append(edge.Outputs, dyndeps.implicitOutputs...) 124 edge.ImplicitOuts += int32(len(dyndeps.implicitOutputs)) 125 126 // Add this edge as incoming to each new output. 127 for _, i := range dyndeps.implicitOutputs { 128 if oldInEdge := i.InEdge; oldInEdge != nil { 129 // This node already has an edge producing it. Fail with an error 130 // unless the edge was generated by ImplicitDepLoader, in which 131 // case we can replace it with the now-known real producer. 132 if !oldInEdge.GeneratedByDepLoader { 133 return errors.New("multiple rules generate " + i.Path) 134 } 135 oldInEdge.Outputs = nil 136 } 137 i.InEdge = edge 138 } 139 140 // Add the dyndep-discovered inputs to the edge. 141 old := edge.Inputs 142 offset := len(edge.Inputs) - int(edge.OrderOnlyDeps) 143 edge.Inputs = make([]*Node, len(edge.Inputs)+len(dyndeps.implicitInputs)) 144 copy(edge.Inputs, old[:offset]) 145 copy(edge.Inputs[offset:], dyndeps.implicitInputs) 146 copy(edge.Inputs[offset+len(dyndeps.implicitInputs):], old[offset:]) 147 edge.ImplicitDeps += int32(len(dyndeps.implicitInputs)) 148 149 // Add this edge as outgoing from each new input. 150 for _, n := range dyndeps.implicitInputs { 151 n.OutEdges = append(n.OutEdges, edge) 152 } 153 return nil 154 } 155 156 func (d *DyndepLoader) loadDyndepFile(file *Node, ddf DyndepFile) error { 157 contents, err := d.di.ReadFile(file.Path) 158 if err != nil { 159 return fmt.Errorf("loading '%s': %w", file.Path, err) 160 } 161 return ParseDyndep(d.state, ddf, file.Path, contents) 162 }