1*60517a1eSAndroid Build Coastguard Worker// Copyright 2023 The Bazel Authors. All rights reserved. 2*60517a1eSAndroid Build Coastguard Worker// 3*60517a1eSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*60517a1eSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*60517a1eSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*60517a1eSAndroid Build Coastguard Worker// 7*60517a1eSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*60517a1eSAndroid Build Coastguard Worker// 9*60517a1eSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*60517a1eSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*60517a1eSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*60517a1eSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*60517a1eSAndroid Build Coastguard Worker// limitations under the License. 14*60517a1eSAndroid Build Coastguard Worker 15*60517a1eSAndroid Build Coastguard Workerpackage python 16*60517a1eSAndroid Build Coastguard Worker 17*60517a1eSAndroid Build Coastguard Workerimport ( 18*60517a1eSAndroid Build Coastguard Worker "fmt" 19*60517a1eSAndroid Build Coastguard Worker "io/fs" 20*60517a1eSAndroid Build Coastguard Worker "log" 21*60517a1eSAndroid Build Coastguard Worker "os" 22*60517a1eSAndroid Build Coastguard Worker "path/filepath" 23*60517a1eSAndroid Build Coastguard Worker "sort" 24*60517a1eSAndroid Build Coastguard Worker "strings" 25*60517a1eSAndroid Build Coastguard Worker 26*60517a1eSAndroid Build Coastguard Worker "github.com/bazelbuild/bazel-gazelle/config" 27*60517a1eSAndroid Build Coastguard Worker "github.com/bazelbuild/bazel-gazelle/label" 28*60517a1eSAndroid Build Coastguard Worker "github.com/bazelbuild/bazel-gazelle/language" 29*60517a1eSAndroid Build Coastguard Worker "github.com/bazelbuild/bazel-gazelle/rule" 30*60517a1eSAndroid Build Coastguard Worker "github.com/bmatcuk/doublestar/v4" 31*60517a1eSAndroid Build Coastguard Worker "github.com/emirpasic/gods/lists/singlylinkedlist" 32*60517a1eSAndroid Build Coastguard Worker "github.com/emirpasic/gods/sets/treeset" 33*60517a1eSAndroid Build Coastguard Worker godsutils "github.com/emirpasic/gods/utils" 34*60517a1eSAndroid Build Coastguard Worker 35*60517a1eSAndroid Build Coastguard Worker "github.com/bazelbuild/rules_python/gazelle/pythonconfig" 36*60517a1eSAndroid Build Coastguard Worker) 37*60517a1eSAndroid Build Coastguard Worker 38*60517a1eSAndroid Build Coastguard Workerconst ( 39*60517a1eSAndroid Build Coastguard Worker pyLibraryEntrypointFilename = "__init__.py" 40*60517a1eSAndroid Build Coastguard Worker pyBinaryEntrypointFilename = "__main__.py" 41*60517a1eSAndroid Build Coastguard Worker pyTestEntrypointFilename = "__test__.py" 42*60517a1eSAndroid Build Coastguard Worker pyTestEntrypointTargetname = "__test__" 43*60517a1eSAndroid Build Coastguard Worker conftestFilename = "conftest.py" 44*60517a1eSAndroid Build Coastguard Worker conftestTargetname = "conftest" 45*60517a1eSAndroid Build Coastguard Worker) 46*60517a1eSAndroid Build Coastguard Worker 47*60517a1eSAndroid Build Coastguard Workervar ( 48*60517a1eSAndroid Build Coastguard Worker buildFilenames = []string{"BUILD", "BUILD.bazel"} 49*60517a1eSAndroid Build Coastguard Worker) 50*60517a1eSAndroid Build Coastguard Worker 51*60517a1eSAndroid Build Coastguard Workerfunc GetActualKindName(kind string, args language.GenerateArgs) string { 52*60517a1eSAndroid Build Coastguard Worker if kindOverride, ok := args.Config.KindMap[kind]; ok { 53*60517a1eSAndroid Build Coastguard Worker return kindOverride.KindName 54*60517a1eSAndroid Build Coastguard Worker } 55*60517a1eSAndroid Build Coastguard Worker return kind 56*60517a1eSAndroid Build Coastguard Worker} 57*60517a1eSAndroid Build Coastguard Worker 58*60517a1eSAndroid Build Coastguard Workerfunc matchesAnyGlob(s string, globs []string) bool { 59*60517a1eSAndroid Build Coastguard Worker // This function assumes that the globs have already been validated. If a glob is 60*60517a1eSAndroid Build Coastguard Worker // invalid, it's considered a non-match and we move on to the next pattern. 61*60517a1eSAndroid Build Coastguard Worker for _, g := range globs { 62*60517a1eSAndroid Build Coastguard Worker if ok, _ := doublestar.Match(g, s); ok { 63*60517a1eSAndroid Build Coastguard Worker return true 64*60517a1eSAndroid Build Coastguard Worker } 65*60517a1eSAndroid Build Coastguard Worker } 66*60517a1eSAndroid Build Coastguard Worker return false 67*60517a1eSAndroid Build Coastguard Worker} 68*60517a1eSAndroid Build Coastguard Worker 69*60517a1eSAndroid Build Coastguard Worker// GenerateRules extracts build metadata from source files in a directory. 70*60517a1eSAndroid Build Coastguard Worker// GenerateRules is called in each directory where an update is requested 71*60517a1eSAndroid Build Coastguard Worker// in depth-first post-order. 72*60517a1eSAndroid Build Coastguard Workerfunc (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateResult { 73*60517a1eSAndroid Build Coastguard Worker cfgs := args.Config.Exts[languageName].(pythonconfig.Configs) 74*60517a1eSAndroid Build Coastguard Worker cfg := cfgs[args.Rel] 75*60517a1eSAndroid Build Coastguard Worker 76*60517a1eSAndroid Build Coastguard Worker if !cfg.ExtensionEnabled() { 77*60517a1eSAndroid Build Coastguard Worker return language.GenerateResult{} 78*60517a1eSAndroid Build Coastguard Worker } 79*60517a1eSAndroid Build Coastguard Worker 80*60517a1eSAndroid Build Coastguard Worker if !isBazelPackage(args.Dir) { 81*60517a1eSAndroid Build Coastguard Worker if cfg.CoarseGrainedGeneration() { 82*60517a1eSAndroid Build Coastguard Worker // Determine if the current directory is the root of the coarse-grained 83*60517a1eSAndroid Build Coastguard Worker // generation. If not, return without generating anything. 84*60517a1eSAndroid Build Coastguard Worker parent := cfg.Parent() 85*60517a1eSAndroid Build Coastguard Worker if parent != nil && parent.CoarseGrainedGeneration() { 86*60517a1eSAndroid Build Coastguard Worker return language.GenerateResult{} 87*60517a1eSAndroid Build Coastguard Worker } 88*60517a1eSAndroid Build Coastguard Worker } else if !hasEntrypointFile(args.Dir) { 89*60517a1eSAndroid Build Coastguard Worker return language.GenerateResult{} 90*60517a1eSAndroid Build Coastguard Worker } 91*60517a1eSAndroid Build Coastguard Worker } 92*60517a1eSAndroid Build Coastguard Worker 93*60517a1eSAndroid Build Coastguard Worker actualPyBinaryKind := GetActualKindName(pyBinaryKind, args) 94*60517a1eSAndroid Build Coastguard Worker actualPyLibraryKind := GetActualKindName(pyLibraryKind, args) 95*60517a1eSAndroid Build Coastguard Worker actualPyTestKind := GetActualKindName(pyTestKind, args) 96*60517a1eSAndroid Build Coastguard Worker 97*60517a1eSAndroid Build Coastguard Worker pythonProjectRoot := cfg.PythonProjectRoot() 98*60517a1eSAndroid Build Coastguard Worker 99*60517a1eSAndroid Build Coastguard Worker packageName := filepath.Base(args.Dir) 100*60517a1eSAndroid Build Coastguard Worker 101*60517a1eSAndroid Build Coastguard Worker pyLibraryFilenames := treeset.NewWith(godsutils.StringComparator) 102*60517a1eSAndroid Build Coastguard Worker pyTestFilenames := treeset.NewWith(godsutils.StringComparator) 103*60517a1eSAndroid Build Coastguard Worker pyFileNames := treeset.NewWith(godsutils.StringComparator) 104*60517a1eSAndroid Build Coastguard Worker 105*60517a1eSAndroid Build Coastguard Worker // hasPyBinaryEntryPointFile controls whether a single py_binary target should be generated for 106*60517a1eSAndroid Build Coastguard Worker // this package or not. 107*60517a1eSAndroid Build Coastguard Worker hasPyBinaryEntryPointFile := false 108*60517a1eSAndroid Build Coastguard Worker 109*60517a1eSAndroid Build Coastguard Worker // hasPyTestEntryPointFile and hasPyTestEntryPointTarget control whether a py_test target should 110*60517a1eSAndroid Build Coastguard Worker // be generated for this package or not. 111*60517a1eSAndroid Build Coastguard Worker hasPyTestEntryPointFile := false 112*60517a1eSAndroid Build Coastguard Worker hasPyTestEntryPointTarget := false 113*60517a1eSAndroid Build Coastguard Worker hasConftestFile := false 114*60517a1eSAndroid Build Coastguard Worker 115*60517a1eSAndroid Build Coastguard Worker testFileGlobs := cfg.TestFilePattern() 116*60517a1eSAndroid Build Coastguard Worker 117*60517a1eSAndroid Build Coastguard Worker for _, f := range args.RegularFiles { 118*60517a1eSAndroid Build Coastguard Worker if cfg.IgnoresFile(filepath.Base(f)) { 119*60517a1eSAndroid Build Coastguard Worker continue 120*60517a1eSAndroid Build Coastguard Worker } 121*60517a1eSAndroid Build Coastguard Worker ext := filepath.Ext(f) 122*60517a1eSAndroid Build Coastguard Worker if ext == ".py" { 123*60517a1eSAndroid Build Coastguard Worker pyFileNames.Add(f) 124*60517a1eSAndroid Build Coastguard Worker if !hasPyBinaryEntryPointFile && f == pyBinaryEntrypointFilename { 125*60517a1eSAndroid Build Coastguard Worker hasPyBinaryEntryPointFile = true 126*60517a1eSAndroid Build Coastguard Worker } else if !hasPyTestEntryPointFile && f == pyTestEntrypointFilename { 127*60517a1eSAndroid Build Coastguard Worker hasPyTestEntryPointFile = true 128*60517a1eSAndroid Build Coastguard Worker } else if f == conftestFilename { 129*60517a1eSAndroid Build Coastguard Worker hasConftestFile = true 130*60517a1eSAndroid Build Coastguard Worker } else if matchesAnyGlob(f, testFileGlobs) { 131*60517a1eSAndroid Build Coastguard Worker pyTestFilenames.Add(f) 132*60517a1eSAndroid Build Coastguard Worker } else { 133*60517a1eSAndroid Build Coastguard Worker pyLibraryFilenames.Add(f) 134*60517a1eSAndroid Build Coastguard Worker } 135*60517a1eSAndroid Build Coastguard Worker } 136*60517a1eSAndroid Build Coastguard Worker } 137*60517a1eSAndroid Build Coastguard Worker 138*60517a1eSAndroid Build Coastguard Worker // If a __test__.py file was not found on disk, search for targets that are 139*60517a1eSAndroid Build Coastguard Worker // named __test__. 140*60517a1eSAndroid Build Coastguard Worker if !hasPyTestEntryPointFile && args.File != nil { 141*60517a1eSAndroid Build Coastguard Worker for _, rule := range args.File.Rules { 142*60517a1eSAndroid Build Coastguard Worker if rule.Name() == pyTestEntrypointTargetname { 143*60517a1eSAndroid Build Coastguard Worker hasPyTestEntryPointTarget = true 144*60517a1eSAndroid Build Coastguard Worker break 145*60517a1eSAndroid Build Coastguard Worker } 146*60517a1eSAndroid Build Coastguard Worker } 147*60517a1eSAndroid Build Coastguard Worker } 148*60517a1eSAndroid Build Coastguard Worker 149*60517a1eSAndroid Build Coastguard Worker // Add files from subdirectories if they meet the criteria. 150*60517a1eSAndroid Build Coastguard Worker for _, d := range args.Subdirs { 151*60517a1eSAndroid Build Coastguard Worker // boundaryPackages represents child Bazel packages that are used as a 152*60517a1eSAndroid Build Coastguard Worker // boundary to stop processing under that tree. 153*60517a1eSAndroid Build Coastguard Worker boundaryPackages := make(map[string]struct{}) 154*60517a1eSAndroid Build Coastguard Worker err := filepath.WalkDir( 155*60517a1eSAndroid Build Coastguard Worker filepath.Join(args.Dir, d), 156*60517a1eSAndroid Build Coastguard Worker func(path string, entry fs.DirEntry, err error) error { 157*60517a1eSAndroid Build Coastguard Worker if err != nil { 158*60517a1eSAndroid Build Coastguard Worker return err 159*60517a1eSAndroid Build Coastguard Worker } 160*60517a1eSAndroid Build Coastguard Worker // Ignore the path if it crosses any boundary package. Walking 161*60517a1eSAndroid Build Coastguard Worker // the tree is still important because subsequent paths can 162*60517a1eSAndroid Build Coastguard Worker // represent files that have not crossed any boundaries. 163*60517a1eSAndroid Build Coastguard Worker for bp := range boundaryPackages { 164*60517a1eSAndroid Build Coastguard Worker if strings.HasPrefix(path, bp) { 165*60517a1eSAndroid Build Coastguard Worker return nil 166*60517a1eSAndroid Build Coastguard Worker } 167*60517a1eSAndroid Build Coastguard Worker } 168*60517a1eSAndroid Build Coastguard Worker if entry.IsDir() { 169*60517a1eSAndroid Build Coastguard Worker // If we are visiting a directory, we determine if we should 170*60517a1eSAndroid Build Coastguard Worker // halt digging the tree based on a few criterias: 171*60517a1eSAndroid Build Coastguard Worker // 1. We are using per-file generation. 172*60517a1eSAndroid Build Coastguard Worker // 2. The directory has a BUILD or BUILD.bazel files. Then 173*60517a1eSAndroid Build Coastguard Worker // it doesn't matter at all what it has since it's a 174*60517a1eSAndroid Build Coastguard Worker // separate Bazel package. 175*60517a1eSAndroid Build Coastguard Worker // 3. (only for package generation) The directory has an 176*60517a1eSAndroid Build Coastguard Worker // __init__.py, __main__.py or __test__.py, meaning a 177*60517a1eSAndroid Build Coastguard Worker // BUILD file will be generated. 178*60517a1eSAndroid Build Coastguard Worker if cfg.PerFileGeneration() { 179*60517a1eSAndroid Build Coastguard Worker return fs.SkipDir 180*60517a1eSAndroid Build Coastguard Worker } 181*60517a1eSAndroid Build Coastguard Worker 182*60517a1eSAndroid Build Coastguard Worker if isBazelPackage(path) { 183*60517a1eSAndroid Build Coastguard Worker boundaryPackages[path] = struct{}{} 184*60517a1eSAndroid Build Coastguard Worker return nil 185*60517a1eSAndroid Build Coastguard Worker } 186*60517a1eSAndroid Build Coastguard Worker 187*60517a1eSAndroid Build Coastguard Worker if !cfg.CoarseGrainedGeneration() && hasEntrypointFile(path) { 188*60517a1eSAndroid Build Coastguard Worker return fs.SkipDir 189*60517a1eSAndroid Build Coastguard Worker } 190*60517a1eSAndroid Build Coastguard Worker 191*60517a1eSAndroid Build Coastguard Worker return nil 192*60517a1eSAndroid Build Coastguard Worker } 193*60517a1eSAndroid Build Coastguard Worker if filepath.Ext(path) == ".py" { 194*60517a1eSAndroid Build Coastguard Worker if cfg.CoarseGrainedGeneration() || !isEntrypointFile(path) { 195*60517a1eSAndroid Build Coastguard Worker srcPath, _ := filepath.Rel(args.Dir, path) 196*60517a1eSAndroid Build Coastguard Worker repoPath := filepath.Join(args.Rel, srcPath) 197*60517a1eSAndroid Build Coastguard Worker excludedPatterns := cfg.ExcludedPatterns() 198*60517a1eSAndroid Build Coastguard Worker if excludedPatterns != nil { 199*60517a1eSAndroid Build Coastguard Worker it := excludedPatterns.Iterator() 200*60517a1eSAndroid Build Coastguard Worker for it.Next() { 201*60517a1eSAndroid Build Coastguard Worker excludedPattern := it.Value().(string) 202*60517a1eSAndroid Build Coastguard Worker isExcluded, err := doublestar.Match(excludedPattern, repoPath) 203*60517a1eSAndroid Build Coastguard Worker if err != nil { 204*60517a1eSAndroid Build Coastguard Worker return err 205*60517a1eSAndroid Build Coastguard Worker } 206*60517a1eSAndroid Build Coastguard Worker if isExcluded { 207*60517a1eSAndroid Build Coastguard Worker return nil 208*60517a1eSAndroid Build Coastguard Worker } 209*60517a1eSAndroid Build Coastguard Worker } 210*60517a1eSAndroid Build Coastguard Worker } 211*60517a1eSAndroid Build Coastguard Worker baseName := filepath.Base(path) 212*60517a1eSAndroid Build Coastguard Worker if matchesAnyGlob(baseName, testFileGlobs) { 213*60517a1eSAndroid Build Coastguard Worker pyTestFilenames.Add(srcPath) 214*60517a1eSAndroid Build Coastguard Worker } else { 215*60517a1eSAndroid Build Coastguard Worker pyLibraryFilenames.Add(srcPath) 216*60517a1eSAndroid Build Coastguard Worker } 217*60517a1eSAndroid Build Coastguard Worker } 218*60517a1eSAndroid Build Coastguard Worker } 219*60517a1eSAndroid Build Coastguard Worker return nil 220*60517a1eSAndroid Build Coastguard Worker }, 221*60517a1eSAndroid Build Coastguard Worker ) 222*60517a1eSAndroid Build Coastguard Worker if err != nil { 223*60517a1eSAndroid Build Coastguard Worker log.Printf("ERROR: %v\n", err) 224*60517a1eSAndroid Build Coastguard Worker return language.GenerateResult{} 225*60517a1eSAndroid Build Coastguard Worker } 226*60517a1eSAndroid Build Coastguard Worker } 227*60517a1eSAndroid Build Coastguard Worker 228*60517a1eSAndroid Build Coastguard Worker parser := newPython3Parser(args.Config.RepoRoot, args.Rel, cfg.IgnoresDependency) 229*60517a1eSAndroid Build Coastguard Worker visibility := cfg.Visibility() 230*60517a1eSAndroid Build Coastguard Worker 231*60517a1eSAndroid Build Coastguard Worker var result language.GenerateResult 232*60517a1eSAndroid Build Coastguard Worker result.Gen = make([]*rule.Rule, 0) 233*60517a1eSAndroid Build Coastguard Worker 234*60517a1eSAndroid Build Coastguard Worker collisionErrors := singlylinkedlist.New() 235*60517a1eSAndroid Build Coastguard Worker 236*60517a1eSAndroid Build Coastguard Worker appendPyLibrary := func(srcs *treeset.Set, pyLibraryTargetName string) { 237*60517a1eSAndroid Build Coastguard Worker allDeps, mainModules, annotations, err := parser.parse(srcs) 238*60517a1eSAndroid Build Coastguard Worker if err != nil { 239*60517a1eSAndroid Build Coastguard Worker log.Fatalf("ERROR: %v\n", err) 240*60517a1eSAndroid Build Coastguard Worker } 241*60517a1eSAndroid Build Coastguard Worker 242*60517a1eSAndroid Build Coastguard Worker if !hasPyBinaryEntryPointFile { 243*60517a1eSAndroid Build Coastguard Worker // Creating one py_binary target per main module when __main__.py doesn't exist. 244*60517a1eSAndroid Build Coastguard Worker mainFileNames := make([]string, 0, len(mainModules)) 245*60517a1eSAndroid Build Coastguard Worker for name := range mainModules { 246*60517a1eSAndroid Build Coastguard Worker mainFileNames = append(mainFileNames, name) 247*60517a1eSAndroid Build Coastguard Worker 248*60517a1eSAndroid Build Coastguard Worker // Remove the file from srcs if we're doing per-file library generation so 249*60517a1eSAndroid Build Coastguard Worker // that we don't also generate a py_library target for it. 250*60517a1eSAndroid Build Coastguard Worker if cfg.PerFileGeneration() { 251*60517a1eSAndroid Build Coastguard Worker srcs.Remove(name) 252*60517a1eSAndroid Build Coastguard Worker } 253*60517a1eSAndroid Build Coastguard Worker } 254*60517a1eSAndroid Build Coastguard Worker sort.Strings(mainFileNames) 255*60517a1eSAndroid Build Coastguard Worker for _, filename := range mainFileNames { 256*60517a1eSAndroid Build Coastguard Worker pyBinaryTargetName := strings.TrimSuffix(filepath.Base(filename), ".py") 257*60517a1eSAndroid Build Coastguard Worker if err := ensureNoCollision(args.File, pyBinaryTargetName, actualPyBinaryKind); err != nil { 258*60517a1eSAndroid Build Coastguard Worker fqTarget := label.New("", args.Rel, pyBinaryTargetName) 259*60517a1eSAndroid Build Coastguard Worker log.Printf("failed to generate target %q of kind %q: %v", 260*60517a1eSAndroid Build Coastguard Worker fqTarget.String(), actualPyBinaryKind, err) 261*60517a1eSAndroid Build Coastguard Worker continue 262*60517a1eSAndroid Build Coastguard Worker } 263*60517a1eSAndroid Build Coastguard Worker pyBinary := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames). 264*60517a1eSAndroid Build Coastguard Worker addVisibility(visibility). 265*60517a1eSAndroid Build Coastguard Worker addSrc(filename). 266*60517a1eSAndroid Build Coastguard Worker addModuleDependencies(mainModules[filename]). 267*60517a1eSAndroid Build Coastguard Worker addResolvedDependencies(annotations.includeDeps). 268*60517a1eSAndroid Build Coastguard Worker generateImportsAttribute().build() 269*60517a1eSAndroid Build Coastguard Worker result.Gen = append(result.Gen, pyBinary) 270*60517a1eSAndroid Build Coastguard Worker result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey)) 271*60517a1eSAndroid Build Coastguard Worker } 272*60517a1eSAndroid Build Coastguard Worker } 273*60517a1eSAndroid Build Coastguard Worker 274*60517a1eSAndroid Build Coastguard Worker // If we're doing per-file generation, srcs could be empty at this point, meaning we shouldn't make a py_library. 275*60517a1eSAndroid Build Coastguard Worker // If there is already a package named py_library target before, we should generate an empty py_library. 276*60517a1eSAndroid Build Coastguard Worker if srcs.Empty() { 277*60517a1eSAndroid Build Coastguard Worker if args.File == nil { 278*60517a1eSAndroid Build Coastguard Worker return 279*60517a1eSAndroid Build Coastguard Worker } 280*60517a1eSAndroid Build Coastguard Worker generateEmptyLibrary := false 281*60517a1eSAndroid Build Coastguard Worker for _, r := range args.File.Rules { 282*60517a1eSAndroid Build Coastguard Worker if r.Kind() == actualPyLibraryKind && r.Name() == pyLibraryTargetName { 283*60517a1eSAndroid Build Coastguard Worker generateEmptyLibrary = true 284*60517a1eSAndroid Build Coastguard Worker } 285*60517a1eSAndroid Build Coastguard Worker } 286*60517a1eSAndroid Build Coastguard Worker if !generateEmptyLibrary { 287*60517a1eSAndroid Build Coastguard Worker return 288*60517a1eSAndroid Build Coastguard Worker } 289*60517a1eSAndroid Build Coastguard Worker } 290*60517a1eSAndroid Build Coastguard Worker 291*60517a1eSAndroid Build Coastguard Worker // Check if a target with the same name we are generating already 292*60517a1eSAndroid Build Coastguard Worker // exists, and if it is of a different kind from the one we are 293*60517a1eSAndroid Build Coastguard Worker // generating. If so, we have to throw an error since Gazelle won't 294*60517a1eSAndroid Build Coastguard Worker // generate it correctly. 295*60517a1eSAndroid Build Coastguard Worker if err := ensureNoCollision(args.File, pyLibraryTargetName, actualPyLibraryKind); err != nil { 296*60517a1eSAndroid Build Coastguard Worker fqTarget := label.New("", args.Rel, pyLibraryTargetName) 297*60517a1eSAndroid Build Coastguard Worker err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+ 298*60517a1eSAndroid Build Coastguard Worker "Use the '# gazelle:%s' directive to change the naming convention.", 299*60517a1eSAndroid Build Coastguard Worker fqTarget.String(), actualPyLibraryKind, err, pythonconfig.LibraryNamingConvention) 300*60517a1eSAndroid Build Coastguard Worker collisionErrors.Add(err) 301*60517a1eSAndroid Build Coastguard Worker } 302*60517a1eSAndroid Build Coastguard Worker 303*60517a1eSAndroid Build Coastguard Worker pyLibrary := newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames). 304*60517a1eSAndroid Build Coastguard Worker addVisibility(visibility). 305*60517a1eSAndroid Build Coastguard Worker addSrcs(srcs). 306*60517a1eSAndroid Build Coastguard Worker addModuleDependencies(allDeps). 307*60517a1eSAndroid Build Coastguard Worker addResolvedDependencies(annotations.includeDeps). 308*60517a1eSAndroid Build Coastguard Worker generateImportsAttribute(). 309*60517a1eSAndroid Build Coastguard Worker build() 310*60517a1eSAndroid Build Coastguard Worker 311*60517a1eSAndroid Build Coastguard Worker if pyLibrary.IsEmpty(py.Kinds()[pyLibrary.Kind()]) { 312*60517a1eSAndroid Build Coastguard Worker result.Empty = append(result.Gen, pyLibrary) 313*60517a1eSAndroid Build Coastguard Worker } else { 314*60517a1eSAndroid Build Coastguard Worker result.Gen = append(result.Gen, pyLibrary) 315*60517a1eSAndroid Build Coastguard Worker result.Imports = append(result.Imports, pyLibrary.PrivateAttr(config.GazelleImportsKey)) 316*60517a1eSAndroid Build Coastguard Worker } 317*60517a1eSAndroid Build Coastguard Worker } 318*60517a1eSAndroid Build Coastguard Worker if cfg.PerFileGeneration() { 319*60517a1eSAndroid Build Coastguard Worker hasInit, nonEmptyInit := hasLibraryEntrypointFile(args.Dir) 320*60517a1eSAndroid Build Coastguard Worker pyLibraryFilenames.Each(func(index int, filename interface{}) { 321*60517a1eSAndroid Build Coastguard Worker pyLibraryTargetName := strings.TrimSuffix(filepath.Base(filename.(string)), ".py") 322*60517a1eSAndroid Build Coastguard Worker if filename == pyLibraryEntrypointFilename && !nonEmptyInit { 323*60517a1eSAndroid Build Coastguard Worker return // ignore empty __init__.py. 324*60517a1eSAndroid Build Coastguard Worker } 325*60517a1eSAndroid Build Coastguard Worker srcs := treeset.NewWith(godsutils.StringComparator, filename) 326*60517a1eSAndroid Build Coastguard Worker if cfg.PerFileGenerationIncludeInit() && hasInit && nonEmptyInit { 327*60517a1eSAndroid Build Coastguard Worker srcs.Add(pyLibraryEntrypointFilename) 328*60517a1eSAndroid Build Coastguard Worker } 329*60517a1eSAndroid Build Coastguard Worker appendPyLibrary(srcs, pyLibraryTargetName) 330*60517a1eSAndroid Build Coastguard Worker }) 331*60517a1eSAndroid Build Coastguard Worker } else { 332*60517a1eSAndroid Build Coastguard Worker appendPyLibrary(pyLibraryFilenames, cfg.RenderLibraryName(packageName)) 333*60517a1eSAndroid Build Coastguard Worker } 334*60517a1eSAndroid Build Coastguard Worker 335*60517a1eSAndroid Build Coastguard Worker if hasPyBinaryEntryPointFile { 336*60517a1eSAndroid Build Coastguard Worker deps, _, annotations, err := parser.parseSingle(pyBinaryEntrypointFilename) 337*60517a1eSAndroid Build Coastguard Worker if err != nil { 338*60517a1eSAndroid Build Coastguard Worker log.Fatalf("ERROR: %v\n", err) 339*60517a1eSAndroid Build Coastguard Worker } 340*60517a1eSAndroid Build Coastguard Worker 341*60517a1eSAndroid Build Coastguard Worker pyBinaryTargetName := cfg.RenderBinaryName(packageName) 342*60517a1eSAndroid Build Coastguard Worker 343*60517a1eSAndroid Build Coastguard Worker // Check if a target with the same name we are generating already 344*60517a1eSAndroid Build Coastguard Worker // exists, and if it is of a different kind from the one we are 345*60517a1eSAndroid Build Coastguard Worker // generating. If so, we have to throw an error since Gazelle won't 346*60517a1eSAndroid Build Coastguard Worker // generate it correctly. 347*60517a1eSAndroid Build Coastguard Worker if err := ensureNoCollision(args.File, pyBinaryTargetName, actualPyBinaryKind); err != nil { 348*60517a1eSAndroid Build Coastguard Worker fqTarget := label.New("", args.Rel, pyBinaryTargetName) 349*60517a1eSAndroid Build Coastguard Worker err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+ 350*60517a1eSAndroid Build Coastguard Worker "Use the '# gazelle:%s' directive to change the naming convention.", 351*60517a1eSAndroid Build Coastguard Worker fqTarget.String(), actualPyBinaryKind, err, pythonconfig.BinaryNamingConvention) 352*60517a1eSAndroid Build Coastguard Worker collisionErrors.Add(err) 353*60517a1eSAndroid Build Coastguard Worker } 354*60517a1eSAndroid Build Coastguard Worker 355*60517a1eSAndroid Build Coastguard Worker pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames). 356*60517a1eSAndroid Build Coastguard Worker setMain(pyBinaryEntrypointFilename). 357*60517a1eSAndroid Build Coastguard Worker addVisibility(visibility). 358*60517a1eSAndroid Build Coastguard Worker addSrc(pyBinaryEntrypointFilename). 359*60517a1eSAndroid Build Coastguard Worker addModuleDependencies(deps). 360*60517a1eSAndroid Build Coastguard Worker addResolvedDependencies(annotations.includeDeps). 361*60517a1eSAndroid Build Coastguard Worker generateImportsAttribute() 362*60517a1eSAndroid Build Coastguard Worker 363*60517a1eSAndroid Build Coastguard Worker pyBinary := pyBinaryTarget.build() 364*60517a1eSAndroid Build Coastguard Worker 365*60517a1eSAndroid Build Coastguard Worker result.Gen = append(result.Gen, pyBinary) 366*60517a1eSAndroid Build Coastguard Worker result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey)) 367*60517a1eSAndroid Build Coastguard Worker } 368*60517a1eSAndroid Build Coastguard Worker 369*60517a1eSAndroid Build Coastguard Worker var conftest *rule.Rule 370*60517a1eSAndroid Build Coastguard Worker if hasConftestFile { 371*60517a1eSAndroid Build Coastguard Worker deps, _, annotations, err := parser.parseSingle(conftestFilename) 372*60517a1eSAndroid Build Coastguard Worker if err != nil { 373*60517a1eSAndroid Build Coastguard Worker log.Fatalf("ERROR: %v\n", err) 374*60517a1eSAndroid Build Coastguard Worker } 375*60517a1eSAndroid Build Coastguard Worker 376*60517a1eSAndroid Build Coastguard Worker // Check if a target with the same name we are generating already 377*60517a1eSAndroid Build Coastguard Worker // exists, and if it is of a different kind from the one we are 378*60517a1eSAndroid Build Coastguard Worker // generating. If so, we have to throw an error since Gazelle won't 379*60517a1eSAndroid Build Coastguard Worker // generate it correctly. 380*60517a1eSAndroid Build Coastguard Worker if err := ensureNoCollision(args.File, conftestTargetname, actualPyLibraryKind); err != nil { 381*60517a1eSAndroid Build Coastguard Worker fqTarget := label.New("", args.Rel, conftestTargetname) 382*60517a1eSAndroid Build Coastguard Worker err := fmt.Errorf("failed to generate target %q of kind %q: %w. ", 383*60517a1eSAndroid Build Coastguard Worker fqTarget.String(), actualPyLibraryKind, err) 384*60517a1eSAndroid Build Coastguard Worker collisionErrors.Add(err) 385*60517a1eSAndroid Build Coastguard Worker } 386*60517a1eSAndroid Build Coastguard Worker 387*60517a1eSAndroid Build Coastguard Worker conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames). 388*60517a1eSAndroid Build Coastguard Worker addSrc(conftestFilename). 389*60517a1eSAndroid Build Coastguard Worker addModuleDependencies(deps). 390*60517a1eSAndroid Build Coastguard Worker addResolvedDependencies(annotations.includeDeps). 391*60517a1eSAndroid Build Coastguard Worker addVisibility(visibility). 392*60517a1eSAndroid Build Coastguard Worker setTestonly(). 393*60517a1eSAndroid Build Coastguard Worker generateImportsAttribute() 394*60517a1eSAndroid Build Coastguard Worker 395*60517a1eSAndroid Build Coastguard Worker conftest = conftestTarget.build() 396*60517a1eSAndroid Build Coastguard Worker 397*60517a1eSAndroid Build Coastguard Worker result.Gen = append(result.Gen, conftest) 398*60517a1eSAndroid Build Coastguard Worker result.Imports = append(result.Imports, conftest.PrivateAttr(config.GazelleImportsKey)) 399*60517a1eSAndroid Build Coastguard Worker } 400*60517a1eSAndroid Build Coastguard Worker 401*60517a1eSAndroid Build Coastguard Worker var pyTestTargets []*targetBuilder 402*60517a1eSAndroid Build Coastguard Worker newPyTestTargetBuilder := func(srcs *treeset.Set, pyTestTargetName string) *targetBuilder { 403*60517a1eSAndroid Build Coastguard Worker deps, _, annotations, err := parser.parse(srcs) 404*60517a1eSAndroid Build Coastguard Worker if err != nil { 405*60517a1eSAndroid Build Coastguard Worker log.Fatalf("ERROR: %v\n", err) 406*60517a1eSAndroid Build Coastguard Worker } 407*60517a1eSAndroid Build Coastguard Worker // Check if a target with the same name we are generating already 408*60517a1eSAndroid Build Coastguard Worker // exists, and if it is of a different kind from the one we are 409*60517a1eSAndroid Build Coastguard Worker // generating. If so, we have to throw an error since Gazelle won't 410*60517a1eSAndroid Build Coastguard Worker // generate it correctly. 411*60517a1eSAndroid Build Coastguard Worker if err := ensureNoCollision(args.File, pyTestTargetName, actualPyTestKind); err != nil { 412*60517a1eSAndroid Build Coastguard Worker fqTarget := label.New("", args.Rel, pyTestTargetName) 413*60517a1eSAndroid Build Coastguard Worker err := fmt.Errorf("failed to generate target %q of kind %q: %w. "+ 414*60517a1eSAndroid Build Coastguard Worker "Use the '# gazelle:%s' directive to change the naming convention.", 415*60517a1eSAndroid Build Coastguard Worker fqTarget.String(), actualPyTestKind, err, pythonconfig.TestNamingConvention) 416*60517a1eSAndroid Build Coastguard Worker collisionErrors.Add(err) 417*60517a1eSAndroid Build Coastguard Worker } 418*60517a1eSAndroid Build Coastguard Worker return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames). 419*60517a1eSAndroid Build Coastguard Worker addSrcs(srcs). 420*60517a1eSAndroid Build Coastguard Worker addModuleDependencies(deps). 421*60517a1eSAndroid Build Coastguard Worker addResolvedDependencies(annotations.includeDeps). 422*60517a1eSAndroid Build Coastguard Worker generateImportsAttribute() 423*60517a1eSAndroid Build Coastguard Worker } 424*60517a1eSAndroid Build Coastguard Worker if (!cfg.PerPackageGenerationRequireTestEntryPoint() || hasPyTestEntryPointFile || hasPyTestEntryPointTarget || cfg.CoarseGrainedGeneration()) && !cfg.PerFileGeneration() { 425*60517a1eSAndroid Build Coastguard Worker // Create one py_test target per package 426*60517a1eSAndroid Build Coastguard Worker if hasPyTestEntryPointFile { 427*60517a1eSAndroid Build Coastguard Worker // Only add the pyTestEntrypointFilename to the pyTestFilenames if 428*60517a1eSAndroid Build Coastguard Worker // the file exists on disk. 429*60517a1eSAndroid Build Coastguard Worker pyTestFilenames.Add(pyTestEntrypointFilename) 430*60517a1eSAndroid Build Coastguard Worker } 431*60517a1eSAndroid Build Coastguard Worker if hasPyTestEntryPointTarget || !pyTestFilenames.Empty() { 432*60517a1eSAndroid Build Coastguard Worker pyTestTargetName := cfg.RenderTestName(packageName) 433*60517a1eSAndroid Build Coastguard Worker pyTestTarget := newPyTestTargetBuilder(pyTestFilenames, pyTestTargetName) 434*60517a1eSAndroid Build Coastguard Worker 435*60517a1eSAndroid Build Coastguard Worker if hasPyTestEntryPointTarget { 436*60517a1eSAndroid Build Coastguard Worker entrypointTarget := fmt.Sprintf(":%s", pyTestEntrypointTargetname) 437*60517a1eSAndroid Build Coastguard Worker main := fmt.Sprintf(":%s", pyTestEntrypointFilename) 438*60517a1eSAndroid Build Coastguard Worker pyTestTarget. 439*60517a1eSAndroid Build Coastguard Worker addSrc(entrypointTarget). 440*60517a1eSAndroid Build Coastguard Worker addResolvedDependency(entrypointTarget). 441*60517a1eSAndroid Build Coastguard Worker setMain(main) 442*60517a1eSAndroid Build Coastguard Worker } else if hasPyTestEntryPointFile { 443*60517a1eSAndroid Build Coastguard Worker pyTestTarget.setMain(pyTestEntrypointFilename) 444*60517a1eSAndroid Build Coastguard Worker } /* else: 445*60517a1eSAndroid Build Coastguard Worker main is not set, assuming there is a test file with the same name 446*60517a1eSAndroid Build Coastguard Worker as the target name, or there is a macro wrapping py_test and setting its main attribute. 447*60517a1eSAndroid Build Coastguard Worker */ 448*60517a1eSAndroid Build Coastguard Worker pyTestTargets = append(pyTestTargets, pyTestTarget) 449*60517a1eSAndroid Build Coastguard Worker } 450*60517a1eSAndroid Build Coastguard Worker } else { 451*60517a1eSAndroid Build Coastguard Worker // Create one py_test target per file 452*60517a1eSAndroid Build Coastguard Worker pyTestFilenames.Each(func(index int, testFile interface{}) { 453*60517a1eSAndroid Build Coastguard Worker srcs := treeset.NewWith(godsutils.StringComparator, testFile) 454*60517a1eSAndroid Build Coastguard Worker pyTestTargetName := strings.TrimSuffix(filepath.Base(testFile.(string)), ".py") 455*60517a1eSAndroid Build Coastguard Worker pyTestTarget := newPyTestTargetBuilder(srcs, pyTestTargetName) 456*60517a1eSAndroid Build Coastguard Worker 457*60517a1eSAndroid Build Coastguard Worker if hasPyTestEntryPointTarget { 458*60517a1eSAndroid Build Coastguard Worker entrypointTarget := fmt.Sprintf(":%s", pyTestEntrypointTargetname) 459*60517a1eSAndroid Build Coastguard Worker main := fmt.Sprintf(":%s", pyTestEntrypointFilename) 460*60517a1eSAndroid Build Coastguard Worker pyTestTarget. 461*60517a1eSAndroid Build Coastguard Worker addSrc(entrypointTarget). 462*60517a1eSAndroid Build Coastguard Worker addResolvedDependency(entrypointTarget). 463*60517a1eSAndroid Build Coastguard Worker setMain(main) 464*60517a1eSAndroid Build Coastguard Worker } else if hasPyTestEntryPointFile { 465*60517a1eSAndroid Build Coastguard Worker pyTestTarget.addSrc(pyTestEntrypointFilename) 466*60517a1eSAndroid Build Coastguard Worker pyTestTarget.setMain(pyTestEntrypointFilename) 467*60517a1eSAndroid Build Coastguard Worker } 468*60517a1eSAndroid Build Coastguard Worker pyTestTargets = append(pyTestTargets, pyTestTarget) 469*60517a1eSAndroid Build Coastguard Worker }) 470*60517a1eSAndroid Build Coastguard Worker } 471*60517a1eSAndroid Build Coastguard Worker 472*60517a1eSAndroid Build Coastguard Worker for _, pyTestTarget := range pyTestTargets { 473*60517a1eSAndroid Build Coastguard Worker if conftest != nil { 474*60517a1eSAndroid Build Coastguard Worker pyTestTarget.addModuleDependency(module{Name: strings.TrimSuffix(conftestFilename, ".py")}) 475*60517a1eSAndroid Build Coastguard Worker } 476*60517a1eSAndroid Build Coastguard Worker pyTest := pyTestTarget.build() 477*60517a1eSAndroid Build Coastguard Worker 478*60517a1eSAndroid Build Coastguard Worker result.Gen = append(result.Gen, pyTest) 479*60517a1eSAndroid Build Coastguard Worker result.Imports = append(result.Imports, pyTest.PrivateAttr(config.GazelleImportsKey)) 480*60517a1eSAndroid Build Coastguard Worker } 481*60517a1eSAndroid Build Coastguard Worker 482*60517a1eSAndroid Build Coastguard Worker if !collisionErrors.Empty() { 483*60517a1eSAndroid Build Coastguard Worker it := collisionErrors.Iterator() 484*60517a1eSAndroid Build Coastguard Worker for it.Next() { 485*60517a1eSAndroid Build Coastguard Worker log.Printf("ERROR: %v\n", it.Value()) 486*60517a1eSAndroid Build Coastguard Worker } 487*60517a1eSAndroid Build Coastguard Worker os.Exit(1) 488*60517a1eSAndroid Build Coastguard Worker } 489*60517a1eSAndroid Build Coastguard Worker 490*60517a1eSAndroid Build Coastguard Worker return result 491*60517a1eSAndroid Build Coastguard Worker} 492*60517a1eSAndroid Build Coastguard Worker 493*60517a1eSAndroid Build Coastguard Worker// isBazelPackage determines if the directory is a Bazel package by probing for 494*60517a1eSAndroid Build Coastguard Worker// the existence of a known BUILD file name. 495*60517a1eSAndroid Build Coastguard Workerfunc isBazelPackage(dir string) bool { 496*60517a1eSAndroid Build Coastguard Worker for _, buildFilename := range buildFilenames { 497*60517a1eSAndroid Build Coastguard Worker path := filepath.Join(dir, buildFilename) 498*60517a1eSAndroid Build Coastguard Worker if _, err := os.Stat(path); err == nil { 499*60517a1eSAndroid Build Coastguard Worker return true 500*60517a1eSAndroid Build Coastguard Worker } 501*60517a1eSAndroid Build Coastguard Worker } 502*60517a1eSAndroid Build Coastguard Worker return false 503*60517a1eSAndroid Build Coastguard Worker} 504*60517a1eSAndroid Build Coastguard Worker 505*60517a1eSAndroid Build Coastguard Worker// hasEntrypointFile determines if the directory has any of the established 506*60517a1eSAndroid Build Coastguard Worker// entrypoint filenames. 507*60517a1eSAndroid Build Coastguard Workerfunc hasEntrypointFile(dir string) bool { 508*60517a1eSAndroid Build Coastguard Worker for _, entrypointFilename := range []string{ 509*60517a1eSAndroid Build Coastguard Worker pyLibraryEntrypointFilename, 510*60517a1eSAndroid Build Coastguard Worker pyBinaryEntrypointFilename, 511*60517a1eSAndroid Build Coastguard Worker pyTestEntrypointFilename, 512*60517a1eSAndroid Build Coastguard Worker } { 513*60517a1eSAndroid Build Coastguard Worker path := filepath.Join(dir, entrypointFilename) 514*60517a1eSAndroid Build Coastguard Worker if _, err := os.Stat(path); err == nil { 515*60517a1eSAndroid Build Coastguard Worker return true 516*60517a1eSAndroid Build Coastguard Worker } 517*60517a1eSAndroid Build Coastguard Worker } 518*60517a1eSAndroid Build Coastguard Worker return false 519*60517a1eSAndroid Build Coastguard Worker} 520*60517a1eSAndroid Build Coastguard Worker 521*60517a1eSAndroid Build Coastguard Worker// hasLibraryEntrypointFile returns if the given directory has the library 522*60517a1eSAndroid Build Coastguard Worker// entrypoint file, and if it is non-empty. 523*60517a1eSAndroid Build Coastguard Workerfunc hasLibraryEntrypointFile(dir string) (bool, bool) { 524*60517a1eSAndroid Build Coastguard Worker stat, err := os.Stat(filepath.Join(dir, pyLibraryEntrypointFilename)) 525*60517a1eSAndroid Build Coastguard Worker if os.IsNotExist(err) { 526*60517a1eSAndroid Build Coastguard Worker return false, false 527*60517a1eSAndroid Build Coastguard Worker } 528*60517a1eSAndroid Build Coastguard Worker if err != nil { 529*60517a1eSAndroid Build Coastguard Worker log.Fatalf("ERROR: %v\n", err) 530*60517a1eSAndroid Build Coastguard Worker } 531*60517a1eSAndroid Build Coastguard Worker return true, stat.Size() != 0 532*60517a1eSAndroid Build Coastguard Worker} 533*60517a1eSAndroid Build Coastguard Worker 534*60517a1eSAndroid Build Coastguard Worker// isEntrypointFile returns whether the given path is an entrypoint file. The 535*60517a1eSAndroid Build Coastguard Worker// given path can be absolute or relative. 536*60517a1eSAndroid Build Coastguard Workerfunc isEntrypointFile(path string) bool { 537*60517a1eSAndroid Build Coastguard Worker basePath := filepath.Base(path) 538*60517a1eSAndroid Build Coastguard Worker switch basePath { 539*60517a1eSAndroid Build Coastguard Worker case pyLibraryEntrypointFilename, 540*60517a1eSAndroid Build Coastguard Worker pyBinaryEntrypointFilename, 541*60517a1eSAndroid Build Coastguard Worker pyTestEntrypointFilename: 542*60517a1eSAndroid Build Coastguard Worker return true 543*60517a1eSAndroid Build Coastguard Worker default: 544*60517a1eSAndroid Build Coastguard Worker return false 545*60517a1eSAndroid Build Coastguard Worker } 546*60517a1eSAndroid Build Coastguard Worker} 547*60517a1eSAndroid Build Coastguard Worker 548*60517a1eSAndroid Build Coastguard Workerfunc ensureNoCollision(file *rule.File, targetName, kind string) error { 549*60517a1eSAndroid Build Coastguard Worker if file == nil { 550*60517a1eSAndroid Build Coastguard Worker return nil 551*60517a1eSAndroid Build Coastguard Worker } 552*60517a1eSAndroid Build Coastguard Worker for _, t := range file.Rules { 553*60517a1eSAndroid Build Coastguard Worker if t.Name() == targetName && t.Kind() != kind { 554*60517a1eSAndroid Build Coastguard Worker return fmt.Errorf("a target of kind %q with the same name already exists", t.Kind()) 555*60517a1eSAndroid Build Coastguard Worker } 556*60517a1eSAndroid Build Coastguard Worker } 557*60517a1eSAndroid Build Coastguard Worker return nil 558*60517a1eSAndroid Build Coastguard Worker} 559