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  }