github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/pkg/model/target_graph.go (about)

     1  package model
     2  
     3  import "fmt"
     4  
     5  type TargetGraph struct {
     6  	// Targets in topological order.
     7  	sortedTargets []TargetSpec
     8  	byID          map[TargetID]TargetSpec
     9  }
    10  
    11  func NewTargetGraph(targets []TargetSpec) (TargetGraph, error) {
    12  	sortedTargets, err := TopologicalSort(targets)
    13  	if err != nil {
    14  		return TargetGraph{}, err
    15  	}
    16  
    17  	return TargetGraph{
    18  		sortedTargets: sortedTargets,
    19  		byID:          MakeTargetMap(sortedTargets),
    20  	}, nil
    21  }
    22  
    23  // In Tilt, Manifests should always be DAGs with a single root node
    24  // (the deploy target). This is just a quick sanity check to make sure
    25  // that's true, because many of our graph-traversal algorithms won't work if
    26  // it's not true.
    27  func (g TargetGraph) IsSingleSourceDAG() bool {
    28  	seenIDs := make(map[TargetID]bool, len(g.sortedTargets))
    29  	lastIdx := len(g.sortedTargets) - 1
    30  	for i := lastIdx; i >= 0; i-- {
    31  		t := g.sortedTargets[i]
    32  		id := t.ID()
    33  		isLastTarget := i == lastIdx
    34  		if !isLastTarget && !seenIDs[id] {
    35  			return false
    36  		}
    37  
    38  		for _, depID := range t.DependencyIDs() {
    39  			seenIDs[depID] = true
    40  		}
    41  	}
    42  	return true
    43  }
    44  
    45  // Visit t and its transitive dependencies in post-order (aka depth-first)
    46  func (g TargetGraph) VisitTree(root TargetSpec, visit func(dep TargetSpec) error) error {
    47  	visitedIDs := make(map[TargetID]bool)
    48  
    49  	// pre-declare the variable, so that this function can recurse
    50  	var helper func(current TargetSpec) error
    51  	helper = func(current TargetSpec) error {
    52  		deps, err := g.DepsOf(current)
    53  		if err != nil {
    54  			return err
    55  		}
    56  
    57  		for _, dep := range deps {
    58  			if visitedIDs[dep.ID()] {
    59  				continue
    60  			}
    61  
    62  			err := helper(dep)
    63  			if err != nil {
    64  				return err
    65  			}
    66  		}
    67  
    68  		err = visit(current)
    69  		if err != nil {
    70  			return err
    71  		}
    72  		visitedIDs[current.ID()] = true
    73  		return nil
    74  	}
    75  
    76  	return helper(root)
    77  }
    78  
    79  // Return the direct dependency targets.
    80  func (g TargetGraph) DepsOf(t TargetSpec) ([]TargetSpec, error) {
    81  	depIDs := t.DependencyIDs()
    82  	result := make([]TargetSpec, len(depIDs))
    83  	for i, depID := range depIDs {
    84  		dep, ok := g.byID[depID]
    85  		if !ok {
    86  			return nil, fmt.Errorf("Dep %q not found in graph", depID)
    87  		}
    88  		result[i] = dep
    89  	}
    90  	return result, nil
    91  }
    92  
    93  // Is this image directly deployed a container?
    94  func (g TargetGraph) IsDeployedImage(iTarget ImageTarget) bool {
    95  	id := iTarget.ID()
    96  	for _, t := range g.sortedTargets {
    97  		switch t := t.(type) {
    98  		case K8sTarget, DockerComposeTarget:
    99  			// Returns true if a K8s or DC target directly depends on this image.
   100  			for _, depID := range t.DependencyIDs() {
   101  				if depID == id {
   102  					return true
   103  				}
   104  			}
   105  		}
   106  	}
   107  	return false
   108  }
   109  
   110  func (g TargetGraph) Images() []ImageTarget {
   111  	return ExtractImageTargets(g.sortedTargets)
   112  }
   113  
   114  // Returns all the images in the graph that are directly deployed to a container.
   115  func (g TargetGraph) DeployedImages() []ImageTarget {
   116  	result := []ImageTarget{}
   117  	for _, iTarget := range g.Images() {
   118  		if g.IsDeployedImage(iTarget) {
   119  			result = append(result, iTarget)
   120  		}
   121  	}
   122  	return result
   123  }