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