github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/please_diff_graphs/diff/plz_diff_graphs.go (about)

     1  // Package diff contains the implementation of the graph differ.
     2  package diff
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"sort"
     9  
    10  	"gopkg.in/op/go-logging.v1"
    11  
    12  	"core"
    13  	"query"
    14  )
    15  
    16  var log = logging.MustGetLogger("diff")
    17  
    18  // ParseGraphOrDie reads a graph file, or dies if anything goes wrong.
    19  func ParseGraphOrDie(filename string) *query.JSONGraph {
    20  	graph := query.JSONGraph{}
    21  	data, err := ioutil.ReadFile(filename)
    22  	if err != nil {
    23  		log.Fatalf("Error reading graph: %s", err)
    24  	}
    25  	if err := json.Unmarshal(data, &graph); err != nil {
    26  		log.Fatalf("Error parsing graph: %s", err)
    27  	}
    28  	return &graph
    29  }
    30  
    31  // Graphs calculates the differences between two graphs.
    32  func Graphs(before, after *query.JSONGraph, changedFiles, include, exclude []string, recurse bool) []core.BuildLabel {
    33  	changedFileMap := toMap(changedFiles)
    34  	allChanges := map[string]bool{}
    35  	for pkgName, afterPkg := range after.Packages {
    36  		beforePkg, present := before.Packages[pkgName]
    37  		if !present {
    38  			// Package didn't exist before, add every target in it.
    39  			for targetName := range afterPkg.Targets {
    40  				label := core.BuildLabel{PackageName: pkgName, Name: targetName}
    41  				allChanges[label.String()] = true
    42  			}
    43  			continue
    44  		}
    45  		for targetName, afterTarget := range afterPkg.Targets {
    46  			beforeTarget := beforePkg.Targets[targetName]
    47  			if targetChanged(&beforeTarget, &afterTarget, pkgName, changedFileMap) {
    48  				label := core.BuildLabel{PackageName: pkgName, Name: targetName}
    49  				allChanges[label.String()] = true
    50  			}
    51  		}
    52  	}
    53  	// Now we have all the targets that are directly changed, we locate all transitive ones
    54  	// in a second pass. We can't do this above because we've got no sensible ordering for it.
    55  	ret := core.BuildLabels{}
    56  	for pkgName, pkg := range after.Packages {
    57  		for targetName, target := range pkg.Targets {
    58  			if depsChanged(after, allChanges, pkgName, targetName, recurse) &&
    59  				shouldInclude(&target, include, exclude) {
    60  				ret = append(ret, core.BuildLabel{PackageName: pkgName, Name: targetName})
    61  			}
    62  		}
    63  	}
    64  	sort.Sort(ret)
    65  	return ret
    66  }
    67  
    68  func targetChanged(before, after *query.JSONTarget, pkgName string, changedFiles map[string]bool) bool {
    69  	if before.Hash != after.Hash {
    70  		return true
    71  	}
    72  	// Note that if the set of sources etc has changed, the hash will have changed also,
    73  	// so here we're only worrying about the content.
    74  	for _, src := range after.Sources {
    75  		if _, present := changedFiles[src]; present {
    76  			return true
    77  		}
    78  	}
    79  	// Same for data files.
    80  	for _, data := range after.Data {
    81  		if _, present := changedFiles[data]; present {
    82  			return true
    83  		}
    84  	}
    85  	return false
    86  }
    87  
    88  // depsChanged returns true if any of the transitive dependencies of this target have changed.
    89  // It marks any changes in allChanges for efficiency.
    90  func depsChanged(graph *query.JSONGraph, allChanges map[string]bool, pkgName, targetName string, recurse bool) bool {
    91  	label := fmt.Sprintf("//%s:%s", pkgName, targetName)
    92  	changed, present := allChanges[label]
    93  	if present {
    94  		return changed
    95  	}
    96  	target := graph.Packages[pkgName].Targets[targetName]
    97  	if !recurse {
    98  		return false
    99  	}
   100  	for _, dep := range target.Deps {
   101  		depLabel := core.ParseBuildLabel(dep, "")
   102  		if depsChanged(graph, allChanges, depLabel.PackageName, depLabel.Name, recurse) {
   103  			allChanges[label] = true
   104  			return true
   105  		}
   106  	}
   107  	allChanges[label] = false
   108  	return false
   109  }
   110  
   111  // toMap is a utility function to convert a slice of strings to a map for faster lookup.
   112  func toMap(in []string) map[string]bool {
   113  	ret := map[string]bool{}
   114  	for _, s := range in {
   115  		ret[s] = true
   116  	}
   117  	return ret
   118  }
   119  
   120  // shouldInclude returns true if the given combination of labels means we should return this target.
   121  func shouldInclude(target *query.JSONTarget, include, exclude []string) bool {
   122  	return (len(include) == 0 || hasAnyLabel(target, include)) && !hasAnyLabel(target, exclude)
   123  }
   124  
   125  func hasAnyLabel(target *query.JSONTarget, labels []string) bool {
   126  	for _, l1 := range labels {
   127  		for _, l2 := range target.Labels {
   128  			if l1 == l2 {
   129  				return true
   130  			}
   131  		}
   132  	}
   133  	return false
   134  }