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