xref: /aosp_15_r20/external/bazelbuild-rules_python/gazelle/python/generate.go (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
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