github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/core/package.go (about)

     1  package core
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"sort"
     7  	"strings"
     8  	"sync"
     9  
    10  	"fs"
    11  )
    12  
    13  // Package is a representation of a package, ie. the part of the system (one or more
    14  // directories) covered by a single build file.
    15  type Package struct {
    16  	// Name of the package, ie. //spam/eggs
    17  	Name string
    18  	// Filename of the build file that defined this package
    19  	Filename string
    20  	// Subincluded build defs files that this package imported
    21  	Subincludes []BuildLabel
    22  	// If the package is in a subrepo, this is the subrepo it belongs to. It's nil if not.
    23  	Subrepo *Subrepo
    24  	// Targets contained within the package
    25  	targets map[string]*BuildTarget
    26  	// Set of output files from rules.
    27  	Outputs map[string]*BuildTarget
    28  	// Protects access to above
    29  	mutex sync.RWMutex
    30  	// Targets whose dependencies got modified during a pre or post-build function.
    31  	modifiedTargets map[*BuildTarget]struct{}
    32  	// Used to arbitrate a single post-build function running at a time.
    33  	// It would be sort of conceptually nice if they ran simultaneously but it'd
    34  	// be far too hard to ensure consistency in any case where they can interact with one another.
    35  	buildCallbackMutex sync.Mutex
    36  }
    37  
    38  // NewPackage constructs a new package with the given name.
    39  func NewPackage(name string) *Package {
    40  	return &Package{
    41  		Name:    name,
    42  		targets: map[string]*BuildTarget{},
    43  		Outputs: map[string]*BuildTarget{},
    44  	}
    45  }
    46  
    47  // Target returns the target with the given name, or nil if this package doesn't have one.
    48  func (pkg *Package) Target(name string) *BuildTarget {
    49  	pkg.mutex.RLock()
    50  	defer pkg.mutex.RUnlock()
    51  	return pkg.targets[name]
    52  }
    53  
    54  // TargetOrDie returns the target with the given name, and dies if this package doesn't have one.
    55  func (pkg *Package) TargetOrDie(name string) *BuildTarget {
    56  	t := pkg.Target(name)
    57  	if t == nil {
    58  		log.Fatalf("Target %s not registered in package %s", name, pkg.Name)
    59  	}
    60  	return t
    61  }
    62  
    63  // SourceRoot returns the root directory of source files for this package.
    64  // This is equivalent to .Name for in-repo packages but differs for those in subrepos.
    65  func (pkg *Package) SourceRoot() string {
    66  	if pkg.Subrepo != nil {
    67  		return pkg.Subrepo.Dir(pkg.Name)
    68  	}
    69  	return pkg.Name
    70  }
    71  
    72  // AddTarget adds a new target to this package with the given name.
    73  // It doesn't check for duplicates.
    74  func (pkg *Package) AddTarget(target *BuildTarget) {
    75  	pkg.mutex.Lock()
    76  	defer pkg.mutex.Unlock()
    77  	pkg.targets[target.Label.Name] = target
    78  }
    79  
    80  // AllTargets returns the current set of targets in this package.
    81  // This is threadsafe, unlike iterating .Targets directly which is not.
    82  func (pkg *Package) AllTargets() []*BuildTarget {
    83  	pkg.mutex.Lock()
    84  	defer pkg.mutex.Unlock()
    85  	ret := make([]*BuildTarget, 0, len(pkg.targets))
    86  	for _, target := range pkg.targets {
    87  		ret = append(ret, target)
    88  	}
    89  	return ret
    90  }
    91  
    92  // NumTargets returns the number of targets currently registered in this package.
    93  func (pkg *Package) NumTargets() int {
    94  	pkg.mutex.Lock()
    95  	defer pkg.mutex.Unlock()
    96  	return len(pkg.targets)
    97  }
    98  
    99  // RegisterSubinclude adds a new subinclude to this package, guaranteeing uniqueness.
   100  func (pkg *Package) RegisterSubinclude(label BuildLabel) {
   101  	if !pkg.HasSubinclude(label) {
   102  		pkg.Subincludes = append(pkg.Subincludes, label)
   103  	}
   104  }
   105  
   106  // HasSubinclude returns true if the package has subincluded the given label.
   107  func (pkg *Package) HasSubinclude(label BuildLabel) bool {
   108  	for _, l := range pkg.Subincludes {
   109  		if l == label {
   110  			return true
   111  		}
   112  	}
   113  	return false
   114  }
   115  
   116  // HasOutput returns true if the package has the given file as an output.
   117  func (pkg *Package) HasOutput(output string) bool {
   118  	pkg.mutex.RLock()
   119  	defer pkg.mutex.RUnlock()
   120  	_, present := pkg.Outputs[output]
   121  	return present
   122  }
   123  
   124  // RegisterOutput registers a new output file in the map.
   125  // Returns an error if the file has already been registered.
   126  func (pkg *Package) RegisterOutput(fileName string, target *BuildTarget) error {
   127  	pkg.mutex.Lock()
   128  	defer pkg.mutex.Unlock()
   129  	originalFileName := fileName
   130  	if target.IsBinary {
   131  		fileName = ":_bin_" + fileName // Add some arbitrary prefix so they don't clash.
   132  	}
   133  	if existing, present := pkg.Outputs[fileName]; present && existing != target {
   134  		if existing.IsFilegroup && !target.IsFilegroup {
   135  			// Update the existing one with this, the registered outputs should prefer non-filegroup targets.
   136  			pkg.Outputs[fileName] = target
   137  		} else if !target.IsFilegroup && !existing.IsFilegroup {
   138  			return fmt.Errorf("rules %s and %s in %s both attempt to output the same file: %s",
   139  				existing.Label, target.Label, pkg.Filename, originalFileName)
   140  		}
   141  	}
   142  	pkg.Outputs[fileName] = target
   143  	return nil
   144  }
   145  
   146  // MustRegisterOutput registers a new output file and panics if it's already been registered.
   147  func (pkg *Package) MustRegisterOutput(fileName string, target *BuildTarget) {
   148  	if err := pkg.RegisterOutput(fileName, target); err != nil {
   149  		panic(err)
   150  	}
   151  }
   152  
   153  // AllChildren returns all child targets of the given one.
   154  // The given target is included as well.
   155  func (pkg *Package) AllChildren(target *BuildTarget) []*BuildTarget {
   156  	ret := BuildTargets{}
   157  	parent := target.Label.Parent()
   158  	for _, t := range pkg.targets {
   159  		if t.Label.Parent() == parent {
   160  			ret = append(ret, t)
   161  		}
   162  	}
   163  	sort.Sort(ret)
   164  	return ret
   165  }
   166  
   167  // IsIncludedIn returns true if the given build label would include this package.
   168  // e.g. //src/... includes the packages src and src/core but not src2.
   169  func (pkg *Package) IsIncludedIn(label BuildLabel) bool {
   170  	return pkg.Name == label.PackageName || strings.HasPrefix(pkg.Name, label.PackageName+"/")
   171  }
   172  
   173  // EnterBuildCallback is used to arbitrate access to build callbacks & track changes to targets.
   174  // The supplied function will be called & a set of modified targets, along with any errors, is returned.
   175  func (pkg *Package) EnterBuildCallback(f func() error) (map[*BuildTarget]struct{}, error) {
   176  	pkg.buildCallbackMutex.Lock()
   177  	defer pkg.buildCallbackMutex.Unlock()
   178  	m := map[*BuildTarget]struct{}{}
   179  	pkg.modifiedTargets = m
   180  	err := f()
   181  	pkg.modifiedTargets = nil
   182  	return m, err
   183  }
   184  
   185  // MarkTargetModified marks a single target as being modified during a pre- or post- build function.
   186  // Correct usage of EnterBuildCallback must have been observed.
   187  func (pkg *Package) MarkTargetModified(target *BuildTarget) {
   188  	if pkg.modifiedTargets != nil {
   189  		pkg.modifiedTargets[target] = struct{}{}
   190  	}
   191  }
   192  
   193  // VerifyOutputs checks all files output from this package and verifies that they're all OK;
   194  // notably it checks that if targets that output into a subdirectory, that subdirectory isn't
   195  // created by another target. That kind of thing can lead to subtle and annoying bugs.
   196  // It logs detected warnings to stdout.
   197  func (pkg *Package) VerifyOutputs() {
   198  	for _, warning := range pkg.verifyOutputs() {
   199  		log.Warning("%s: %s", pkg.Filename, warning)
   200  	}
   201  }
   202  
   203  func (pkg *Package) verifyOutputs() []string {
   204  	pkg.mutex.RLock()
   205  	defer pkg.mutex.RUnlock()
   206  	ret := []string{}
   207  	for filename, target := range pkg.Outputs {
   208  		for dir := path.Dir(filename); dir != "."; dir = path.Dir(dir) {
   209  			if target2, present := pkg.Outputs[dir]; present && target2 != target && !target.HasDependency(target2.Label.Parent()) {
   210  				ret = append(ret, fmt.Sprintf("Target %s outputs files into the directory %s, which is separately output by %s. This can cause errors based on build order - you should add a dependency.", target.Label, dir, target2.Label))
   211  			}
   212  		}
   213  	}
   214  	return ret
   215  }
   216  
   217  // FindOwningPackages returns build labels corresponding to the packages that own each of the given files.
   218  func FindOwningPackages(state *BuildState, files []string) []BuildLabel {
   219  	ret := make([]BuildLabel, len(files))
   220  	for i, file := range files {
   221  		ret[i] = FindOwningPackage(state, file)
   222  	}
   223  	return ret
   224  }
   225  
   226  // FindOwningPackage returns a build label identifying the package that owns a given file.
   227  func FindOwningPackage(state *BuildState, file string) BuildLabel {
   228  	f := file
   229  	for f != "." {
   230  		f = path.Dir(f)
   231  		if fs.IsPackage(state.Config.Parse.BuildFileName, f) {
   232  			return BuildLabel{PackageName: f, Name: "all"}
   233  		}
   234  	}
   235  	log.Fatalf("No BUILD file owns file %s", file)
   236  	return BuildLabel{}
   237  }