github.com/wolfd/bazel-gazelle@v0.14.0/internal/config/config.go (about)

     1  /* Copyright 2017 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  */
    15  
    16  package config
    17  
    18  import (
    19  	"flag"
    20  	"fmt"
    21  	"go/build"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/bazelbuild/bazel-gazelle/internal/rule"
    26  	"github.com/bazelbuild/bazel-gazelle/internal/wspace"
    27  )
    28  
    29  // Config holds information about how Gazelle should run. This is based on
    30  // command line arguments, directives, other hints in build files.
    31  //
    32  // A Config applies to a single directory. A Config is created for the
    33  // repository root directory, then copied and modified for each subdirectory.
    34  //
    35  // Config itself contains only general information. Most configuration
    36  // information is language-specific and is stored in Exts. This information
    37  // is modified by extensions that implement Configurer.
    38  type Config struct {
    39  	// Dirs is a list of absolute, canonical paths to directories where Gazelle
    40  	// should run.
    41  	Dirs []string
    42  
    43  	// RepoRoot is the absolute, canonical path to the root directory of the
    44  	// repository with all symlinks resolved.
    45  	RepoRoot string
    46  
    47  	// RepoName is the name of the repository.
    48  	RepoName string
    49  
    50  	// ReadBuildFilesDir is the absolute path to a directory where
    51  	// build files should be read from instead of RepoRoot.
    52  	ReadBuildFilesDir string
    53  
    54  	// WriteBuildFilesDir is the absolute path to a directory where
    55  	// build files should be written to instead of RepoRoot.
    56  	WriteBuildFilesDir string
    57  
    58  	// ValidBuildFileNames is a list of base names that are considered valid
    59  	// build files. Some repositories may have files named "BUILD" that are not
    60  	// used by Bazel and should be ignored. Must contain at least one string.
    61  	ValidBuildFileNames []string
    62  
    63  	// ShouldFix determines whether Gazelle attempts to remove and replace
    64  	// usage of deprecated rules.
    65  	ShouldFix bool
    66  
    67  	// Exts is a set of configurable extensions. Generally, each language
    68  	// has its own set of extensions, but other modules may provide their own
    69  	// extensions as well. Values in here may be populated by command line
    70  	// arguments, directives in build files, or other mechanisms.
    71  	Exts map[string]interface{}
    72  }
    73  
    74  func New() *Config {
    75  	return &Config{
    76  		ValidBuildFileNames: DefaultValidBuildFileNames,
    77  		Exts:                make(map[string]interface{}),
    78  	}
    79  }
    80  
    81  // Clone creates a copy of the configuration for use in a subdirectory.
    82  // Note that the Exts map is copied, but its contents are not.
    83  // Configurer.Configure should do this, if needed.
    84  func (c *Config) Clone() *Config {
    85  	cc := *c
    86  	cc.Exts = make(map[string]interface{})
    87  	for k, v := range c.Exts {
    88  		cc.Exts[k] = v
    89  	}
    90  	return &cc
    91  }
    92  
    93  var DefaultValidBuildFileNames = []string{"BUILD.bazel", "BUILD"}
    94  
    95  func (c *Config) IsValidBuildFileName(name string) bool {
    96  	for _, n := range c.ValidBuildFileNames {
    97  		if name == n {
    98  			return true
    99  		}
   100  	}
   101  	return false
   102  }
   103  
   104  func (c *Config) DefaultBuildFileName() string {
   105  	return c.ValidBuildFileNames[0]
   106  }
   107  
   108  // Configurer is the interface for language or library-specific configuration
   109  // extensions. Most (ideally all) modifications to Config should happen
   110  // via this interface.
   111  type Configurer interface {
   112  	// RegisterFlags registers command-line flags used by the extension. This
   113  	// method is called once with the root configuration when Gazelle
   114  	// starts. RegisterFlags may set an initial values in Config.Exts. When flags
   115  	// are set, they should modify these values.
   116  	RegisterFlags(fs *flag.FlagSet, cmd string, c *Config)
   117  
   118  	// CheckFlags validates the configuration after command line flags are parsed.
   119  	// This is called once with the root configuration when Gazelle starts.
   120  	// CheckFlags may set default values in flags or make implied changes.
   121  	CheckFlags(fs *flag.FlagSet, c *Config) error
   122  
   123  	// KnownDirectives returns a list of directive keys that this Configurer can
   124  	// interpret. Gazelle prints errors for directives that are not recoginized by
   125  	// any Configurer.
   126  	KnownDirectives() []string
   127  
   128  	// Configure modifies the configuration using directives and other information
   129  	// extracted from a build file. Configure is called in each directory.
   130  	//
   131  	// c is the configuration for the current directory. It starts out as a copy
   132  	// of the configuration for the parent directory.
   133  	//
   134  	// rel is the slash-separated relative path from the repository root to
   135  	// the current directory. It is "" for the root directory itself.
   136  	//
   137  	// f is the build file for the current directory or nil if there is no
   138  	// existing build file.
   139  	Configure(c *Config, rel string, f *rule.File)
   140  }
   141  
   142  // CommonConfigurer handles language-agnostic command-line flags and directives,
   143  // i.e., those that apply to Config itself and not to Config.Exts.
   144  type CommonConfigurer struct {
   145  	repoRoot, buildFileNames, readBuildFilesDir, writeBuildFilesDir string
   146  }
   147  
   148  func (cc *CommonConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *Config) {
   149  	fs.StringVar(&cc.repoRoot, "repo_root", "", "path to a directory which corresponds to go_prefix, otherwise gazelle searches for it.")
   150  	fs.StringVar(&cc.buildFileNames, "build_file_name", strings.Join(DefaultValidBuildFileNames, ","), "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.")
   151  	fs.StringVar(&cc.readBuildFilesDir, "experimental_read_build_files_dir", "", "path to a directory where build files should be read from (instead of -repo_root)")
   152  	fs.StringVar(&cc.writeBuildFilesDir, "experimental_write_build_files_dir", "", "path to a directory where build files should be written to (instead of -repo_root)")
   153  }
   154  
   155  func (cc *CommonConfigurer) CheckFlags(fs *flag.FlagSet, c *Config) error {
   156  	var err error
   157  	if cc.repoRoot == "" {
   158  		cc.repoRoot, err = wspace.Find(".")
   159  		if err != nil {
   160  			return fmt.Errorf("-repo_root not specified, and WORKSPACE cannot be found: %v", err)
   161  		}
   162  	}
   163  	c.RepoRoot, err = filepath.Abs(cc.repoRoot)
   164  	if err != nil {
   165  		return fmt.Errorf("%s: failed to find absolute path of repo root: %v", cc.repoRoot, err)
   166  	}
   167  	c.RepoRoot, err = filepath.EvalSymlinks(c.RepoRoot)
   168  	if err != nil {
   169  		return fmt.Errorf("%s: failed to resolve symlinks: %v", cc.repoRoot, err)
   170  	}
   171  	c.ValidBuildFileNames = strings.Split(cc.buildFileNames, ",")
   172  	if cc.readBuildFilesDir != "" {
   173  		c.ReadBuildFilesDir, err = filepath.Abs(cc.readBuildFilesDir)
   174  		if err != nil {
   175  			return fmt.Errorf("%s: failed to find absolute path of -read_build_files_dir: %v", cc.readBuildFilesDir, err)
   176  		}
   177  	}
   178  	if cc.writeBuildFilesDir != "" {
   179  		c.WriteBuildFilesDir, err = filepath.Abs(cc.writeBuildFilesDir)
   180  		if err != nil {
   181  			return fmt.Errorf("%s: failed to find absolute path of -write_build_files_dir: %v", cc.writeBuildFilesDir, err)
   182  		}
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func (cc *CommonConfigurer) KnownDirectives() []string {
   189  	return []string{"build_file_name"}
   190  }
   191  
   192  func (cc *CommonConfigurer) Configure(c *Config, rel string, f *rule.File) {
   193  	if f == nil {
   194  		return
   195  	}
   196  	for _, d := range f.Directives {
   197  		if d.Key == "build_file_name" {
   198  			c.ValidBuildFileNames = strings.Split(d.Value, ",")
   199  		}
   200  	}
   201  }
   202  
   203  // CheckPrefix checks that a string may be used as a prefix. We forbid local
   204  // (relative) imports and those beginning with "/". We allow the empty string,
   205  // but generated rules must not have an empty importpath.
   206  func CheckPrefix(prefix string) error {
   207  	if strings.HasPrefix(prefix, "/") || build.IsLocalImport(prefix) {
   208  		return fmt.Errorf("invalid prefix: %q", prefix)
   209  	}
   210  	return nil
   211  }
   212  
   213  // DependencyMode determines how imports of packages outside of the prefix
   214  // are resolved.
   215  type DependencyMode int
   216  
   217  const (
   218  	// ExternalMode indicates imports should be resolved to external dependencies
   219  	// (declared in WORKSPACE).
   220  	ExternalMode DependencyMode = iota
   221  
   222  	// VendorMode indicates imports should be resolved to libraries in the
   223  	// vendor directory.
   224  	VendorMode
   225  )
   226  
   227  // DependencyModeFromString converts a string from the command line
   228  // to a DependencyMode. Valid strings are "external", "vendor". An error will
   229  // be returned for an invalid string.
   230  func DependencyModeFromString(s string) (DependencyMode, error) {
   231  	switch s {
   232  	case "external":
   233  		return ExternalMode, nil
   234  	case "vendored":
   235  		return VendorMode, nil
   236  	default:
   237  		return 0, fmt.Errorf("unrecognized dependency mode: %q", s)
   238  	}
   239  }
   240  
   241  // ProtoMode determines how proto rules are generated.
   242  type ProtoMode int
   243  
   244  const (
   245  	// DefaultProtoMode generates proto_library and new grpc_proto_library rules.
   246  	// .pb.go files are excluded when there is a .proto file with a similar name.
   247  	DefaultProtoMode ProtoMode = iota
   248  
   249  	// DisableProtoMode ignores .proto files. .pb.go files are treated
   250  	// as normal sources.
   251  	DisableProtoMode
   252  
   253  	// LegacyProtoMode generates filegroups for .proto files if .pb.go files
   254  	// are present in the same directory.
   255  	LegacyProtoMode
   256  )
   257  
   258  func ProtoModeFromString(s string) (ProtoMode, error) {
   259  	switch s {
   260  	case "default":
   261  		return DefaultProtoMode, nil
   262  	case "disable":
   263  		return DisableProtoMode, nil
   264  	case "legacy":
   265  		return LegacyProtoMode, nil
   266  	default:
   267  		return 0, fmt.Errorf("unrecognized proto mode: %q", s)
   268  	}
   269  }