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 }