• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
29	"github.com/bazelbuild/rules_python/gazelle/manifest"
30	"github.com/bazelbuild/rules_python/gazelle/pythonconfig"
31)
32
33// Configurer satisfies the config.Configurer interface. It's the
34// language-specific configuration extension.
35type Configurer struct{}
36
37// RegisterFlags registers command-line flags used by the extension. This
38// method is called once with the root configuration when Gazelle
39// starts. RegisterFlags may set an initial values in Config.Exts. When flags
40// are set, they should modify these values.
41func (py *Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {}
42
43// CheckFlags validates the configuration after command line flags are parsed.
44// This is called once with the root configuration when Gazelle starts.
45// CheckFlags may set default values in flags or make implied changes.
46func (py *Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
47	return nil
48}
49
50// KnownDirectives returns a list of directive keys that this Configurer can
51// interpret. Gazelle prints errors for directives that are not recoginized by
52// any Configurer.
53func (py *Configurer) KnownDirectives() []string {
54	return []string{
55		pythonconfig.PythonExtensionDirective,
56		pythonconfig.PythonRootDirective,
57		pythonconfig.PythonManifestFileNameDirective,
58		pythonconfig.IgnoreFilesDirective,
59		pythonconfig.IgnoreDependenciesDirective,
60		pythonconfig.ValidateImportStatementsDirective,
61		pythonconfig.GenerationMode,
62		pythonconfig.LibraryNamingConvention,
63		pythonconfig.BinaryNamingConvention,
64		pythonconfig.TestNamingConvention,
65	}
66}
67
68// Configure modifies the configuration using directives and other information
69// extracted from a build file. Configure is called in each directory.
70//
71// c is the configuration for the current directory. It starts out as a copy
72// of the configuration for the parent directory.
73//
74// rel is the slash-separated relative path from the repository root to
75// the current directory. It is "" for the root directory itself.
76//
77// f is the build file for the current directory or nil if there is no
78// existing build file.
79func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
80	// Create the root config.
81	if _, exists := c.Exts[languageName]; !exists {
82		rootConfig := pythonconfig.New(c.RepoRoot, "")
83		c.Exts[languageName] = pythonconfig.Configs{"": rootConfig}
84	}
85
86	configs := c.Exts[languageName].(pythonconfig.Configs)
87
88	config, exists := configs[rel]
89	if !exists {
90		parent := configs.ParentForPackage(rel)
91		config = parent.NewChild()
92		configs[rel] = config
93	}
94
95	if f == nil {
96		return
97	}
98
99	gazelleManifestFilename := "gazelle_python.yaml"
100
101	for _, d := range f.Directives {
102		switch d.Key {
103		case "exclude":
104			// We record the exclude directive for coarse-grained packages
105			// since we do manual tree traversal in this mode.
106			config.AddExcludedPattern(filepath.Join(rel, strings.TrimSpace(d.Value)))
107		case pythonconfig.PythonExtensionDirective:
108			switch d.Value {
109			case "enabled":
110				config.SetExtensionEnabled(true)
111			case "disabled":
112				config.SetExtensionEnabled(false)
113			default:
114				err := fmt.Errorf("invalid value for directive %q: %s: possible values are enabled/disabled",
115					pythonconfig.PythonExtensionDirective, d.Value)
116				log.Fatal(err)
117			}
118		case pythonconfig.PythonRootDirective:
119			config.SetPythonProjectRoot(rel)
120		case pythonconfig.PythonManifestFileNameDirective:
121			gazelleManifestFilename = strings.TrimSpace(d.Value)
122		case pythonconfig.IgnoreFilesDirective:
123			for _, ignoreFile := range strings.Split(d.Value, ",") {
124				config.AddIgnoreFile(ignoreFile)
125			}
126		case pythonconfig.IgnoreDependenciesDirective:
127			for _, ignoreDependency := range strings.Split(d.Value, ",") {
128				config.AddIgnoreDependency(ignoreDependency)
129			}
130		case pythonconfig.ValidateImportStatementsDirective:
131			v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
132			if err != nil {
133				log.Fatal(err)
134			}
135			config.SetValidateImportStatements(v)
136		case pythonconfig.GenerationMode:
137			switch pythonconfig.GenerationModeType(strings.TrimSpace(d.Value)) {
138			case pythonconfig.GenerationModePackage:
139				config.SetCoarseGrainedGeneration(false)
140			case pythonconfig.GenerationModeProject:
141				config.SetCoarseGrainedGeneration(true)
142			default:
143				err := fmt.Errorf("invalid value for directive %q: %s",
144					pythonconfig.GenerationMode, d.Value)
145				log.Fatal(err)
146			}
147		case pythonconfig.LibraryNamingConvention:
148			config.SetLibraryNamingConvention(strings.TrimSpace(d.Value))
149		case pythonconfig.BinaryNamingConvention:
150			config.SetBinaryNamingConvention(strings.TrimSpace(d.Value))
151		case pythonconfig.TestNamingConvention:
152			config.SetTestNamingConvention(strings.TrimSpace(d.Value))
153		}
154	}
155
156	gazelleManifestPath := filepath.Join(c.RepoRoot, rel, gazelleManifestFilename)
157	gazelleManifest, err := py.loadGazelleManifest(gazelleManifestPath)
158	if err != nil {
159		log.Fatal(err)
160	}
161	if gazelleManifest != nil {
162		config.SetGazelleManifest(gazelleManifest)
163	}
164}
165
166func (py *Configurer) loadGazelleManifest(gazelleManifestPath string) (*manifest.Manifest, error) {
167	if _, err := os.Stat(gazelleManifestPath); err != nil {
168		if os.IsNotExist(err) {
169			return nil, nil
170		}
171		return nil, fmt.Errorf("failed to load Gazelle manifest at %q: %w", gazelleManifestPath, err)
172	}
173	manifestFile := new(manifest.File)
174	if err := manifestFile.Decode(gazelleManifestPath); err != nil {
175		return nil, fmt.Errorf("failed to load Gazelle manifest at %q: %w", gazelleManifestPath, err)
176	}
177	return manifestFile.Manifest, nil
178}
179