xref: /aosp_15_r20/external/bazelbuild-rules_python/gazelle/python/configure.go (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1// Copyright 2023 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package python
16
17import (
18	"flag"
19	"fmt"
20	"log"
21	"os"
22	"path/filepath"
23	"strconv"
24	"strings"
25
26	"github.com/bazelbuild/bazel-gazelle/config"
27	"github.com/bazelbuild/bazel-gazelle/rule"
28	"github.com/bmatcuk/doublestar/v4"
29
30	"github.com/bazelbuild/rules_python/gazelle/manifest"
31	"github.com/bazelbuild/rules_python/gazelle/pythonconfig"
32)
33
34// Configurer satisfies the config.Configurer interface. It's the
35// language-specific configuration extension.
36type Configurer struct{}
37
38// RegisterFlags registers command-line flags used by the extension. This
39// method is called once with the root configuration when Gazelle
40// starts. RegisterFlags may set an initial values in Config.Exts. When flags
41// are set, they should modify these values.
42func (py *Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {}
43
44// CheckFlags validates the configuration after command line flags are parsed.
45// This is called once with the root configuration when Gazelle starts.
46// CheckFlags may set default values in flags or make implied changes.
47func (py *Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
48	return nil
49}
50
51// KnownDirectives returns a list of directive keys that this Configurer can
52// interpret. Gazelle prints errors for directives that are not recoginized by
53// any Configurer.
54func (py *Configurer) KnownDirectives() []string {
55	return []string{
56		pythonconfig.PythonExtensionDirective,
57		pythonconfig.PythonRootDirective,
58		pythonconfig.PythonManifestFileNameDirective,
59		pythonconfig.IgnoreFilesDirective,
60		pythonconfig.IgnoreDependenciesDirective,
61		pythonconfig.ValidateImportStatementsDirective,
62		pythonconfig.GenerationMode,
63		pythonconfig.GenerationModePerFileIncludeInit,
64		pythonconfig.GenerationModePerPackageRequireTestEntryPoint,
65		pythonconfig.LibraryNamingConvention,
66		pythonconfig.BinaryNamingConvention,
67		pythonconfig.TestNamingConvention,
68		pythonconfig.DefaultVisibilty,
69		pythonconfig.Visibility,
70		pythonconfig.TestFilePattern,
71		pythonconfig.LabelConvention,
72		pythonconfig.LabelNormalization,
73	}
74}
75
76// Configure modifies the configuration using directives and other information
77// extracted from a build file. Configure is called in each directory.
78//
79// c is the configuration for the current directory. It starts out as a copy
80// of the configuration for the parent directory.
81//
82// rel is the slash-separated relative path from the repository root to
83// the current directory. It is "" for the root directory itself.
84//
85// f is the build file for the current directory or nil if there is no
86// existing build file.
87func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
88	// Create the root config.
89	if _, exists := c.Exts[languageName]; !exists {
90		rootConfig := pythonconfig.New(c.RepoRoot, "")
91		c.Exts[languageName] = pythonconfig.Configs{"": rootConfig}
92	}
93
94	configs := c.Exts[languageName].(pythonconfig.Configs)
95
96	config, exists := configs[rel]
97	if !exists {
98		parent := configs.ParentForPackage(rel)
99		config = parent.NewChild()
100		configs[rel] = config
101	}
102
103	if f == nil {
104		return
105	}
106
107	gazelleManifestFilename := "gazelle_python.yaml"
108
109	for _, d := range f.Directives {
110		switch d.Key {
111		case "exclude":
112			// We record the exclude directive for coarse-grained packages
113			// since we do manual tree traversal in this mode.
114			config.AddExcludedPattern(filepath.Join(rel, strings.TrimSpace(d.Value)))
115		case pythonconfig.PythonExtensionDirective:
116			switch d.Value {
117			case "enabled":
118				config.SetExtensionEnabled(true)
119			case "disabled":
120				config.SetExtensionEnabled(false)
121			default:
122				err := fmt.Errorf("invalid value for directive %q: %s: possible values are enabled/disabled",
123					pythonconfig.PythonExtensionDirective, d.Value)
124				log.Fatal(err)
125			}
126		case pythonconfig.PythonRootDirective:
127			config.SetPythonProjectRoot(rel)
128			config.SetDefaultVisibility([]string{fmt.Sprintf(pythonconfig.DefaultVisibilityFmtString, rel)})
129		case pythonconfig.PythonManifestFileNameDirective:
130			gazelleManifestFilename = strings.TrimSpace(d.Value)
131		case pythonconfig.IgnoreFilesDirective:
132			for _, ignoreFile := range strings.Split(d.Value, ",") {
133				config.AddIgnoreFile(ignoreFile)
134			}
135		case pythonconfig.IgnoreDependenciesDirective:
136			for _, ignoreDependency := range strings.Split(d.Value, ",") {
137				config.AddIgnoreDependency(ignoreDependency)
138			}
139		case pythonconfig.ValidateImportStatementsDirective:
140			v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
141			if err != nil {
142				log.Fatal(err)
143			}
144			config.SetValidateImportStatements(v)
145		case pythonconfig.GenerationMode:
146			switch pythonconfig.GenerationModeType(strings.TrimSpace(d.Value)) {
147			case pythonconfig.GenerationModePackage:
148				config.SetCoarseGrainedGeneration(false)
149				config.SetPerFileGeneration(false)
150			case pythonconfig.GenerationModeFile:
151				config.SetCoarseGrainedGeneration(false)
152				config.SetPerFileGeneration(true)
153			case pythonconfig.GenerationModeProject:
154				config.SetCoarseGrainedGeneration(true)
155				config.SetPerFileGeneration(false)
156			default:
157				err := fmt.Errorf("invalid value for directive %q: %s",
158					pythonconfig.GenerationMode, d.Value)
159				log.Fatal(err)
160			}
161		case pythonconfig.GenerationModePerFileIncludeInit:
162			v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
163			if err != nil {
164				log.Fatal(err)
165			}
166			config.SetPerFileGenerationIncludeInit(v)
167		case pythonconfig.GenerationModePerPackageRequireTestEntryPoint:
168			v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
169			if err != nil {
170				log.Printf("invalid value for gazelle:%s in %q: %q",
171					pythonconfig.GenerationModePerPackageRequireTestEntryPoint, rel, d.Value)
172			} else {
173				config.SetPerPackageGenerationRequireTestEntryPoint(v)
174			}
175		case pythonconfig.LibraryNamingConvention:
176			config.SetLibraryNamingConvention(strings.TrimSpace(d.Value))
177		case pythonconfig.BinaryNamingConvention:
178			config.SetBinaryNamingConvention(strings.TrimSpace(d.Value))
179		case pythonconfig.TestNamingConvention:
180			config.SetTestNamingConvention(strings.TrimSpace(d.Value))
181		case pythonconfig.DefaultVisibilty:
182			switch directiveArg := strings.TrimSpace(d.Value); directiveArg {
183			case "NONE":
184				config.SetDefaultVisibility([]string{})
185			case "DEFAULT":
186				pythonProjectRoot := config.PythonProjectRoot()
187				defaultVisibility := fmt.Sprintf(pythonconfig.DefaultVisibilityFmtString, pythonProjectRoot)
188				config.SetDefaultVisibility([]string{defaultVisibility})
189			default:
190				// Handle injecting the python root. Assume that the user used the
191				// exact string "$python_root$".
192				labels := strings.ReplaceAll(directiveArg, "$python_root$", config.PythonProjectRoot())
193				config.SetDefaultVisibility(strings.Split(labels, ","))
194			}
195		case pythonconfig.Visibility:
196			labels := strings.ReplaceAll(strings.TrimSpace(d.Value), "$python_root$", config.PythonProjectRoot())
197			config.AppendVisibility(labels)
198		case pythonconfig.TestFilePattern:
199			value := strings.TrimSpace(d.Value)
200			if value == "" {
201				log.Fatal("directive 'python_test_file_pattern' requires a value")
202			}
203			globStrings := strings.Split(value, ",")
204			for _, g := range globStrings {
205				if !doublestar.ValidatePattern(g) {
206					log.Fatalf("invalid glob pattern '%s'", g)
207				}
208			}
209			config.SetTestFilePattern(globStrings)
210		case pythonconfig.LabelConvention:
211			value := strings.TrimSpace(d.Value)
212			if value == "" {
213				log.Fatalf("directive '%s' requires a value", pythonconfig.LabelConvention)
214			}
215			config.SetLabelConvention(value)
216		case pythonconfig.LabelNormalization:
217			switch directiveArg := strings.ToLower(strings.TrimSpace(d.Value)); directiveArg {
218			case "pep503":
219				config.SetLabelNormalization(pythonconfig.Pep503LabelNormalizationType)
220			case "none":
221				config.SetLabelNormalization(pythonconfig.NoLabelNormalizationType)
222			case "snake_case":
223				config.SetLabelNormalization(pythonconfig.SnakeCaseLabelNormalizationType)
224			default:
225				config.SetLabelNormalization(pythonconfig.DefaultLabelNormalizationType)
226			}
227		}
228	}
229
230	gazelleManifestPath := filepath.Join(c.RepoRoot, rel, gazelleManifestFilename)
231	gazelleManifest, err := py.loadGazelleManifest(gazelleManifestPath)
232	if err != nil {
233		log.Fatal(err)
234	}
235	if gazelleManifest != nil {
236		config.SetGazelleManifest(gazelleManifest)
237	}
238}
239
240func (py *Configurer) loadGazelleManifest(gazelleManifestPath string) (*manifest.Manifest, error) {
241	if _, err := os.Stat(gazelleManifestPath); err != nil {
242		if os.IsNotExist(err) {
243			return nil, nil
244		}
245		return nil, fmt.Errorf("failed to load Gazelle manifest at %q: %w", gazelleManifestPath, err)
246	}
247	manifestFile := new(manifest.File)
248	if err := manifestFile.Decode(gazelleManifestPath); err != nil {
249		return nil, fmt.Errorf("failed to load Gazelle manifest at %q: %w", gazelleManifestPath, err)
250	}
251	return manifestFile.Manifest, nil
252}
253