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

     1  package model
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/pkg/errors"
     7  )
     8  
     9  // An abstract build graph of targets and their dependencies.
    10  // Each target should have a unique ID.
    11  
    12  type TargetType string
    13  type TargetName string
    14  
    15  func (n TargetName) String() string { return string(n) }
    16  
    17  const (
    18  	// Deployed k8s entities
    19  	TargetTypeK8s TargetType = "k8s"
    20  
    21  	// Image builds
    22  	TargetTypeImage TargetType = "image"
    23  
    24  	// Docker-compose service build and deploy
    25  	// TODO(nick): Currently, build and deploy are represented as a single target.
    26  	// In the future, we might have a separate build target and deploy target.
    27  	TargetTypeDockerCompose TargetType = "docker-compose"
    28  
    29  	// Runs a local command when triggered (manually or via changed dep)
    30  	TargetTypeLocal TargetType = "local"
    31  
    32  	// Aggregation of multiple targets into one UI view.
    33  	// TODO(nick): Currently used as the type for both Manifest and YAMLManifest, though
    34  	// we expect YAMLManifest to go away.
    35  	TargetTypeManifest TargetType = "manifest"
    36  
    37  	// Changes that affect all targets, rebuilding the target graph.
    38  	TargetTypeConfigs TargetType = "configs"
    39  )
    40  
    41  type TargetID struct {
    42  	Type TargetType
    43  	Name TargetName
    44  }
    45  
    46  func (id TargetID) Empty() bool {
    47  	return id.Type == "" || id.Name == ""
    48  }
    49  
    50  func (id TargetID) String() string {
    51  	if id.Empty() {
    52  		return ""
    53  	}
    54  	return fmt.Sprintf("%s:%s", id.Type, id.Name)
    55  }
    56  
    57  func TargetIDSet(tids []TargetID) map[TargetID]bool {
    58  	res := make(map[TargetID]bool)
    59  	for _, id := range tids {
    60  		res[id] = true
    61  	}
    62  	return res
    63  }
    64  
    65  type TargetSpec interface {
    66  	ID() TargetID
    67  
    68  	// Check to make sure the spec is well-formed.
    69  	// All TargetSpecs should throw an error in the case where the ID is empty.
    70  	Validate() error
    71  
    72  	DependencyIDs() []TargetID
    73  }
    74  
    75  type TargetStatus interface {
    76  	TargetID() TargetID
    77  	LastBuild() BuildRecord
    78  }
    79  
    80  type Target interface {
    81  	Spec() TargetSpec
    82  	Status() TargetStatus
    83  }
    84  
    85  // De-duplicate target ids, maintaining the same order.
    86  func DedupeTargetIDs(ids []TargetID) []TargetID {
    87  	result := make([]TargetID, 0, len(ids))
    88  	dupes := make(map[TargetID]bool, len(ids))
    89  	for _, id := range ids {
    90  		if !dupes[id] {
    91  			dupes[id] = true
    92  			result = append(result, id)
    93  		}
    94  	}
    95  	return result
    96  }
    97  
    98  // Map all the targets by their target ID.
    99  func MakeTargetMap(targets []TargetSpec) map[TargetID]TargetSpec {
   100  	result := make(map[TargetID]TargetSpec, len(targets))
   101  	for _, target := range targets {
   102  		result[target.ID()] = target
   103  	}
   104  	return result
   105  }
   106  
   107  // Create a topologically sorted list of targets. Returns an error
   108  // if the targets can't be topologically sorted. (e.g., there's a cycle).
   109  func TopologicalSort(targets []TargetSpec) ([]TargetSpec, error) {
   110  	targetMap := MakeTargetMap(targets)
   111  	result := make([]TargetSpec, 0, len(targets))
   112  	inResult := make(map[TargetID]bool, len(targets))
   113  	searching := make(map[TargetID]bool, len(targets))
   114  
   115  	var ensureInResult func(id TargetID) error
   116  	ensureInResult = func(id TargetID) error {
   117  		if inResult[id] {
   118  			return nil
   119  		}
   120  		if searching[id] {
   121  			return fmt.Errorf("Found a cycle at target: %s", id.Name)
   122  		}
   123  		searching[id] = true
   124  
   125  		current, ok := targetMap[id]
   126  		if !ok {
   127  			return fmt.Errorf("Missing target dependency: %s", id.Name)
   128  		}
   129  
   130  		for _, depID := range current.DependencyIDs() {
   131  			err := ensureInResult(depID)
   132  			if err != nil {
   133  				return err
   134  			}
   135  		}
   136  		result = append(result, current)
   137  		inResult[id] = true
   138  		return nil
   139  	}
   140  
   141  	for _, target := range targets {
   142  		err := ensureInResult(target.ID())
   143  		if err != nil {
   144  			return nil, errors.Wrap(err, "Internal error")
   145  		}
   146  	}
   147  	return result, nil
   148  }