github.com/tiagovtristao/plz@v13.4.0+incompatible/src/core/config.go (about)

     1  // Utilities for reading the Please config files.
     2  
     3  package core
     4  
     5  import (
     6  	"crypto/sha1"
     7  	"encoding/gob"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path"
    12  	"reflect"
    13  	"runtime"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/google/shlex"
    21  	"github.com/jessevdk/go-flags"
    22  	"gopkg.in/gcfg.v1"
    23  
    24  	"github.com/thought-machine/please/src/cli"
    25  )
    26  
    27  // OsArch is the os/arch pair, like linux_amd64 etc.
    28  const OsArch = runtime.GOOS + "_" + runtime.GOARCH
    29  
    30  // ConfigFileName is the file name for the typical repo config - this is normally checked in
    31  const ConfigFileName string = ".plzconfig"
    32  
    33  // ArchConfigFileName is the architecture-specific config file which overrides the repo one.
    34  // Also normally checked in if needed.
    35  const ArchConfigFileName string = ".plzconfig_" + OsArch
    36  
    37  // LocalConfigFileName is the file name for the local repo config - this is not normally checked
    38  // in and used to override settings on the local machine.
    39  const LocalConfigFileName string = ".plzconfig.local"
    40  
    41  // MachineConfigFileName is the file name for the machine-level config - can use this to override
    42  // things for a particular machine (eg. build machine with different caching behaviour).
    43  const MachineConfigFileName = "/etc/plzconfig"
    44  
    45  // UserConfigFileName is the file name for user-specific config (for all their repos).
    46  const UserConfigFileName = "~/.config/please/plzconfig"
    47  
    48  const oldUserConfigFileName = "~/.please/plzconfig"
    49  
    50  // The available container implementations that we support.
    51  const (
    52  	ContainerImplementationNone   = "none"
    53  	ContainerImplementationDocker = "docker"
    54  )
    55  
    56  // GithubDownloadLocation is plz's Github repo, which will become the default download location in future.
    57  const GithubDownloadLocation = "https://github.com/thought-machine/please"
    58  
    59  // GithubAPILocation is as above, but for the API endpoints.
    60  const GithubAPILocation = "https://api.github.com/repos/thought-machine/please"
    61  
    62  func readConfigFile(config *Configuration, filename string) error {
    63  	log.Debug("Reading config from %s...", filename)
    64  	if err := gcfg.ReadFileInto(config, filename); err != nil && os.IsNotExist(err) {
    65  		return nil // It's not an error to not have the file at all.
    66  	} else if gcfg.FatalOnly(err) != nil {
    67  		return err
    68  	} else if err != nil {
    69  		log.Warning("Error in config file: %s", err)
    70  	} else if filename == ExpandHomePath(oldUserConfigFileName) {
    71  		log.Warning("Read a config file at %s; this location is deprecated in favour of %s", filename, ExpandHomePath(UserConfigFileName))
    72  	}
    73  	return nil
    74  }
    75  
    76  // ReadDefaultConfigFiles reads all the config files from the default locations and
    77  // merges them into a config object.
    78  // The repo root must have already have been set before calling this.
    79  func ReadDefaultConfigFiles(profile string) (*Configuration, error) {
    80  	return ReadConfigFiles([]string{
    81  		MachineConfigFileName,
    82  		ExpandHomePath(UserConfigFileName),
    83  		ExpandHomePath(oldUserConfigFileName),
    84  		path.Join(RepoRoot, ConfigFileName),
    85  		path.Join(RepoRoot, ArchConfigFileName),
    86  		path.Join(RepoRoot, LocalConfigFileName),
    87  	}, profile)
    88  }
    89  
    90  // ReadConfigFiles reads all the config locations, in order, and merges them into a config object.
    91  // Values are filled in by defaults initially and then overridden by each file in turn.
    92  func ReadConfigFiles(filenames []string, profile string) (*Configuration, error) {
    93  	config := DefaultConfiguration()
    94  	for _, filename := range filenames {
    95  		if err := readConfigFile(config, filename); err != nil {
    96  			return config, err
    97  		}
    98  		if profile != "" {
    99  			if err := readConfigFile(config, filename+"."+profile); err != nil {
   100  				return config, err
   101  			}
   102  		}
   103  	}
   104  	// Set default values for slices. These add rather than overwriting so we can't set
   105  	// them upfront as we would with other config values.
   106  	if usingBazelWorkspace {
   107  		setDefault(&config.Parse.BuildFileName, []string{"BUILD.bazel", "BUILD", "BUILD.plz"})
   108  	} else {
   109  		setDefault(&config.Parse.BuildFileName, []string{"BUILD", "BUILD.plz"})
   110  	}
   111  	setBuildPath(&config.Build.Path, config.Build.PassEnv)
   112  	setDefault(&config.Build.PassEnv, []string{})
   113  	setDefault(&config.Cover.FileExtension, []string{".go", ".py", ".java", ".js", ".cc", ".h", ".c"})
   114  	setDefault(&config.Cover.ExcludeExtension, []string{".pb.go", "_pb2.py", ".pb.cc", ".pb.h", "_test.py", "_test.go", "_pb.go", "_bindata.go", "_test_main.cc"})
   115  	setDefault(&config.Proto.Language, []string{"cc", "py", "java", "go", "js"})
   116  
   117  	// Default values for these guys depend on config.Java.JavaHome if that's been set.
   118  	if config.Java.JavaHome != "" {
   119  		defaultPathIfExists(&config.Java.JlinkTool, config.Java.JavaHome, "bin/jlink")
   120  	}
   121  
   122  	if (config.Cache.RPCPrivateKey == "") != (config.Cache.RPCPublicKey == "") {
   123  		return config, fmt.Errorf("Must pass both rpcprivatekey and rpcpublickey properties for cache")
   124  	}
   125  
   126  	if len(config.Aliases) > 0 {
   127  		log.Warning("The [aliases] section of .plzconfig is deprecated in favour of [alias]. See https://please.build/config.html for more information.")
   128  	}
   129  
   130  	// In a few versions we will deprecate Cpp.Coverage completely in favour of this more generic scheme.
   131  	if !config.Cpp.Coverage {
   132  		config.Test.DisableCoverage = append(config.Test.DisableCoverage, "cc")
   133  	}
   134  
   135  	// We can only verify options by reflection (we need struct tags) so run them quickly through this.
   136  	return config, config.ApplyOverrides(map[string]string{
   137  		"test.defaultcontainer": config.Test.DefaultContainer,
   138  		"python.testrunner":     config.Python.TestRunner,
   139  	})
   140  }
   141  
   142  // setDefault sets a slice of strings in the config if the set one is empty.
   143  func setDefault(conf *[]string, def []string) {
   144  	if len(*conf) == 0 {
   145  		*conf = def
   146  	}
   147  }
   148  
   149  // setDefault checks if "PATH" is in passEnv, if it is set config.build.Path to use the environment variable.
   150  func setBuildPath(conf *[]string, passEnv []string) {
   151  	pathVal := []string{"/usr/local/bin", "/usr/bin", "/bin"}
   152  	for _, i := range passEnv {
   153  		if i == "PATH" {
   154  			pathVal = strings.Split(os.Getenv("PATH"), ":")
   155  		}
   156  	}
   157  
   158  	setDefault(conf, pathVal)
   159  }
   160  
   161  // defaultPath sets a variable to a location in a directory if it's not already set.
   162  func defaultPath(conf *string, dir, file string) {
   163  	if *conf == "" {
   164  		*conf = path.Join(dir, file)
   165  	}
   166  }
   167  
   168  // defaultPathIfExists sets a variable to a location in a directory if it's not already set and if the location exists.
   169  func defaultPathIfExists(conf *string, dir, file string) {
   170  	if *conf == "" {
   171  		location := path.Join(dir, file)
   172  		// check that the location is valid
   173  		if _, err := os.Stat(location); err == nil {
   174  			*conf = location
   175  		}
   176  	}
   177  }
   178  
   179  // DefaultConfiguration returns the default configuration object with no overrides.
   180  func DefaultConfiguration() *Configuration {
   181  	config := Configuration{buildEnvStored: &storedBuildEnv{}}
   182  	config.Please.Location = "~/.please"
   183  	config.Please.SelfUpdate = true
   184  	config.Please.Autoclean = true
   185  	config.Please.DownloadLocation = "https://get.please.build"
   186  	config.Please.NumOldVersions = 10
   187  	config.Parse.BuiltinPleasings = true
   188  	config.Parse.GitFunctions = true
   189  	config.Build.Arch = cli.NewArch(runtime.GOOS, runtime.GOARCH)
   190  	config.Build.Lang = "en_GB.UTF-8" // Not the language of the UI, the language passed to rules.
   191  	config.Build.Nonce = "1402"       // Arbitrary nonce to invalidate config when needed.
   192  	config.Build.Timeout = cli.Duration(10 * time.Minute)
   193  	config.Build.Config = "opt"         // Optimised builds by default
   194  	config.Build.FallbackConfig = "opt" // Optimised builds as a fallback on any target that doesn't have a matching one set
   195  	config.Build.PleaseSandboxTool = "please_sandbox"
   196  	config.BuildConfig = map[string]string{}
   197  	config.BuildEnv = map[string]string{}
   198  	config.Aliases = map[string]string{}
   199  	config.Cache.HTTPTimeout = cli.Duration(5 * time.Second)
   200  	config.Cache.RPCTimeout = cli.Duration(5 * time.Second)
   201  	if dir, err := os.UserCacheDir(); err == nil {
   202  		config.Cache.Dir = path.Join(dir, "please")
   203  	}
   204  	config.Cache.DirCacheHighWaterMark = 10 * cli.GiByte
   205  	config.Cache.DirCacheLowWaterMark = 8 * cli.GiByte
   206  	config.Cache.DirClean = true
   207  	config.Cache.Workers = runtime.NumCPU() + 2 // Mirrors the number of workers in please.go.
   208  	config.Cache.RPCMaxMsgSize.UnmarshalFlag("200MiB")
   209  	config.Metrics.PushFrequency = cli.Duration(400 * time.Millisecond)
   210  	config.Metrics.PushTimeout = cli.Duration(500 * time.Millisecond)
   211  	config.Metrics.PerUser = true
   212  	config.Test.Timeout = cli.Duration(10 * time.Minute)
   213  	config.Test.DefaultContainer = ContainerImplementationDocker
   214  	config.Display.SystemStats = true
   215  	config.Docker.DefaultImage = "ubuntu:trusty"
   216  	config.Docker.AllowLocalFallback = false
   217  	config.Docker.Timeout = cli.Duration(20 * time.Minute)
   218  	config.Docker.ResultsTimeout = cli.Duration(20 * time.Second)
   219  	config.Docker.RemoveTimeout = cli.Duration(20 * time.Second)
   220  	config.Go.GoTool = "go"
   221  	config.Go.CgoCCTool = "gcc"
   222  	config.Go.BuildIDTool = "go_buildid_replacer"
   223  	config.Go.TestTool = "please_go_test"
   224  	config.Go.FilterTool = "please_go_filter"
   225  	config.Go.GoPath = "$TMP_DIR:$TMP_DIR/src:$TMP_DIR/$PKG_DIR:$TMP_DIR/third_party/go:$TMP_DIR/third_party/"
   226  	config.Python.PipTool = "pip3"
   227  	config.Python.PexTool = "please_pex"
   228  	config.Python.DefaultInterpreter = "python3"
   229  	config.Python.TestRunner = "unittest"
   230  	config.Python.UsePyPI = true
   231  
   232  	// Annoyingly pip on OSX doesn't seem to work with this flag (you get the dreaded
   233  	// "must supply either home or prefix/exec-prefix" error). Goodness knows why *adding* this
   234  	// flag - which otherwise seems exactly what we want - provokes that error, but the logic
   235  	// of pip is rather a mystery to me.
   236  	if runtime.GOOS != "darwin" {
   237  		config.Python.PipFlags = "--isolated"
   238  	}
   239  	config.Java.DefaultTestPackage = ""
   240  	config.Java.SourceLevel = "8"
   241  	config.Java.TargetLevel = "8"
   242  	config.Java.ReleaseLevel = ""
   243  	config.Java.DefaultMavenRepo = []cli.URL{"https://repo1.maven.org/maven2"}
   244  	config.Java.JavacFlags = "-Werror -Xlint:-options" // bootstrap class path warnings are pervasive without this.
   245  	config.Java.JlinkTool = "jlink"
   246  	config.Java.JavacWorker = "javac_worker"
   247  	config.Java.JarCatTool = "jarcat"
   248  	config.Java.PleaseMavenTool = "please_maven"
   249  	config.Java.JUnitRunner = "junit_runner.jar"
   250  	config.Java.JavaHome = ""
   251  	config.Cpp.CCTool = "gcc"
   252  	config.Cpp.CppTool = "g++"
   253  	config.Cpp.LdTool = "ld"
   254  	config.Cpp.ArTool = "ar"
   255  	config.Cpp.AsmTool = "nasm"
   256  	config.Cpp.DefaultOptCflags = "--std=c99 -O3 -pipe -DNDEBUG -Wall -Werror"
   257  	config.Cpp.DefaultDbgCflags = "--std=c99 -g3 -pipe -DDEBUG -Wall -Werror"
   258  	config.Cpp.DefaultOptCppflags = "--std=c++11 -O3 -pipe -DNDEBUG -Wall -Werror"
   259  	config.Cpp.DefaultDbgCppflags = "--std=c++11 -g3 -pipe -DDEBUG -Wall -Werror"
   260  	config.Cpp.Coverage = true
   261  	config.Cpp.ClangModules = true
   262  	// At some point in the future it might make sense to remove UnitTest++ as the default
   263  	// test runner - but for now it's still the default for compatibility.
   264  	config.Cpp.TestMain = BuildLabel{
   265  		Subrepo:     "pleasings",
   266  		PackageName: "cc",
   267  		Name:        "unittest_main",
   268  	}
   269  	config.Proto.ProtocTool = "protoc"
   270  	// We're using the most common names for these; typically gRPC installs the builtin plugins
   271  	// as grpc_python_plugin etc.
   272  	config.Proto.ProtocGoPlugin = "protoc-gen-go"
   273  	config.Proto.GrpcPythonPlugin = "grpc_python_plugin"
   274  	config.Proto.GrpcJavaPlugin = "protoc-gen-grpc-java"
   275  	config.Proto.GrpcCCPlugin = "grpc_cpp_plugin"
   276  	config.Proto.PythonDep = "//third_party/python:protobuf"
   277  	config.Proto.JavaDep = "//third_party/java:protobuf"
   278  	config.Proto.GoDep = "//third_party/go:protobuf"
   279  	config.Proto.JsDep = ""
   280  	config.Proto.PythonGrpcDep = "//third_party/python:grpc"
   281  	config.Proto.JavaGrpcDep = "//third_party/java:grpc-all"
   282  	config.Proto.GoGrpcDep = "//third_party/go:grpc"
   283  	config.Bazel.Compatibility = usingBazelWorkspace
   284  	return &config
   285  }
   286  
   287  // A Configuration contains all the settings that can be configured about Please.
   288  // This is parsed from .plzconfig etc; we also auto-generate help messages from its tags.
   289  type Configuration struct {
   290  	Please struct {
   291  		Version          cli.Version `help:"Defines the version of plz that this repo is supposed to use currently. If it's not present or the version matches the currently running version no special action is taken; otherwise if SelfUpdate is set Please will attempt to download an appropriate version, otherwise it will issue a warning and continue.\n\nNote that if this is not set, you can run plz update to update to the latest version available on the server." var:"PLZ_VERSION"`
   292  		Location         string      `help:"Defines the directory Please is installed into.\nDefaults to ~/.please but you might want it to be somewhere else if you're installing via another method (e.g. the debs and install script still use /opt/please)."`
   293  		SelfUpdate       bool        `help:"Sets whether plz will attempt to update itself when the version set in the config file is different."`
   294  		DownloadLocation cli.URL     `help:"Defines the location to download Please from when self-updating. Defaults to the Please web server, but you can point it to some location of your own if you prefer to keep traffic within your network or use home-grown versions."`
   295  		NumOldVersions   int         `help:"Number of old versions to keep from autoupdates."`
   296  		Autoclean        bool        `help:"Automatically clean stale versions without prompting"`
   297  		NumThreads       int         `help:"Number of parallel build operations to run.\nIs overridden by the equivalent command-line flag, if that's passed." example:"6"`
   298  		Motd             []string    `help:"Message of the day; is displayed once at the top during builds. If multiple are given, one is randomly chosen."`
   299  		DefaultRepo      string      `help:"Location of the default repository; this is used if plz is invoked when not inside a repo, it changes to that directory then does its thing."`
   300  	} `help:"The [please] section in the config contains non-language-specific settings defining how Please should operate."`
   301  	Parse struct {
   302  		ExperimentalDir  []string `help:"Directory containing experimental code. This is subject to some extra restrictions:\n - Code in the experimental dir can override normal visibility constraints\n - Code outside the experimental dir can never depend on code inside it\n - Tests are excluded from general detection." example:"experimental"`
   303  		BuildFileName    []string `help:"Sets the names that Please uses instead of BUILD for its build files.\nFor clarity the documentation refers to them simply as BUILD files but you could reconfigure them here to be something else.\nOne case this can be particularly useful is in cases where you have a subdirectory named build on a case-insensitive file system like HFS+." var:"BUILD_FILE_NAMES"`
   304  		BlacklistDirs    []string `help:"Directories to blacklist when recursively searching for BUILD files (e.g. when using plz build ... or similar).\nThis is generally useful when you have large directories within your repo that don't need to be searched, especially things like node_modules that have come from external package managers."`
   305  		PreloadBuildDefs []string `help:"Files to preload by the parser before loading any BUILD files.\nSince this is done before the first package is parsed they must be files in the repository, they cannot be subinclude() paths." example:"build_defs/go_bindata.build_defs"`
   306  		BuiltinPleasings bool     `help:"Adds github.com/thought-machine/pleasings as a default subrepo named pleasings. This makes some builtin extensions available, but is not fully deterministic (it always uses the latest version). You may prefer to disable this and define your own subrepo for it (or not use it at all, of course)."`
   307  		GitFunctions     bool     `help:"Activates built-in functions git_branch, git_commit, git_show and git_state. If disabled they will not be usable at parse time."`
   308  	} `help:"The [parse] section in the config contains settings specific to parsing files."`
   309  	Display struct {
   310  		UpdateTitle bool `help:"Updates the title bar of the shell window Please is running in as the build progresses. This isn't on by default because not everyone's shell is configured to reset it again after and we don't want to alter it forever."`
   311  		SystemStats bool `help:"Whether or not to show basic system resource usage in the interactive display. Has no effect without that configured."`
   312  	} `help:"Please has an animated display mode which shows the currently building targets.\nBy default it will autodetect whether it is using an interactive TTY session and choose whether to use it or not, although you can force it on or off via flags.\n\nThe display is heavily inspired by Buck's SuperConsole."`
   313  	Events struct {
   314  		Port int `help:"Port to start the streaming build event server on."`
   315  	} `help:"The [events] section in the config contains settings relating to the internal build event system & streaming them externally."`
   316  	Build struct {
   317  		Arch              cli.Arch     `help:"Architecture to compile for. Defaults to the host architecture."`
   318  		Timeout           cli.Duration `help:"Default timeout for Dockerised tests, in seconds. Default is twenty minutes."`
   319  		Path              []string     `help:"The PATH variable that will be passed to the build processes.\nDefaults to /usr/local/bin:/usr/bin:/bin but of course can be modified if you need to get binaries from other locations." example:"/usr/local/bin:/usr/bin:/bin"`
   320  		Config            string       `help:"The build config to use when one is not chosen on the command line. Defaults to opt." example:"opt | dbg"`
   321  		FallbackConfig    string       `help:"The build config to use when one is chosen and a required target does not have one by the same name. Also defaults to opt." example:"opt | dbg"`
   322  		Lang              string       `help:"Sets the language passed to build rules when building. This can be important for some tools (although hopefully not many) - we've mostly observed it with Sass."`
   323  		Sandbox           bool         `help:"True to sandbox individual build actions, which isolates them from network access and some aspects of the filesystem. Currently only works on Linux." var:"BUILD_SANDBOX"`
   324  		PleaseSandboxTool string       `help:"The location of the please_sandbox tool to use."`
   325  		Nonce             string       `help:"This is an arbitrary string that is added to the hash of every build target. It provides a way to force a rebuild of everything when it's changed.\nWe will bump the default of this whenever we think it's required - although it's been a pretty long time now and we hope that'll continue."`
   326  		PassEnv           []string     `help:"A list of environment variables to pass from the current environment to build rules. For example\n\nPassEnv = HTTP_PROXY\n\nwould copy your HTTP_PROXY environment variable to the build env for any rules."`
   327  	}
   328  	BuildConfig map[string]string `help:"A section of arbitrary key-value properties that are made available in the BUILD language. These are often useful for writing custom rules that need some configurable property.\n\n[buildconfig]\nandroid-tools-version = 23.0.2\n\nFor example, the above can be accessed as CONFIG.ANDROID_TOOLS_VERSION."`
   329  	BuildEnv    map[string]string `help:"A set of extra environment variables to define for build rules. For example:\n\n[buildenv]\nsecret-passphrase = 12345\n\nThis would become SECRET_PASSPHRASE for any rules. These can be useful for passing secrets into custom rules; any variables containing SECRET or PASSWORD won't be logged.\n\nIt's also useful if you'd like internal tools to honour some external variable."`
   330  	Cache       struct {
   331  		Workers               int          `help:"Number of workers for uploading artifacts to remote caches, which is done asynchronously."`
   332  		Dir                   string       `help:"Sets the directory to use for the dir cache.\nThe default is 'please' under the user's cache dir (i.e. ~/.cache/please, ~/Library/Caches/please, etc), if set to the empty string the dir cache will be disabled."`
   333  		DirCacheHighWaterMark cli.ByteSize `help:"Starts cleaning the directory cache when it is over this number of bytes.\nCan also be given with human-readable suffixes like 10G, 200MB etc."`
   334  		DirCacheLowWaterMark  cli.ByteSize `help:"When cleaning the directory cache, it's reduced to at most this size."`
   335  		DirClean              bool         `help:"Controls whether entries in the dir cache are cleaned or not. If disabled the cache will only grow."`
   336  		DirCompress           bool         `help:"Compresses stored artifacts in the dir cache. They are slower to store & retrieve but more compact."`
   337  		HTTPURL               cli.URL      `help:"Base URL of the HTTP cache.\nNot set to anything by default which means the cache will be disabled."`
   338  		HTTPWriteable         bool         `help:"If True this plz instance will write content back to the HTTP cache.\nBy default it runs in read-only mode."`
   339  		HTTPTimeout           cli.Duration `help:"Timeout for operations contacting the HTTP cache, in seconds."`
   340  		RPCURL                cli.URL      `help:"Base URL of the RPC cache.\nNot set to anything by default which means the cache will be disabled."`
   341  		RPCWriteable          bool         `help:"If True this plz instance will write content back to the RPC cache.\nBy default it runs in read-only mode."`
   342  		RPCTimeout            cli.Duration `help:"Timeout for operations contacting the RPC cache, in seconds."`
   343  		RPCPublicKey          string       `help:"File containing a PEM-encoded private key which is used to authenticate to the RPC cache." example:"my_key.pem"`
   344  		RPCPrivateKey         string       `help:"File containing a PEM-encoded certificate which is used to authenticate to the RPC cache." example:"my_cert.pem"`
   345  		RPCCACert             string       `help:"File containing a PEM-encoded certificate which is used to validate the RPC cache's certificate." example:"ca.pem"`
   346  		RPCSecure             bool         `help:"Forces SSL on for the RPC cache. It will be activated if any of rpcpublickey, rpcprivatekey or rpccacert are set, but this can be used if none of those are needed and SSL is still in use."`
   347  		RPCMaxMsgSize         cli.ByteSize `help:"Maximum size of a single message that we'll send to the RPC server.\nThis should agree with the server's limit, if it's higher the artifacts will be rejected.\nThe value is given as a byte size so can be suffixed with M, GB, KiB, etc."`
   348  	} `help:"Please has several built-in caches that can be configured in its config file.\n\nThe simplest one is the directory cache which by default is written into the .plz-cache directory. This allows for fast retrieval of code that has been built before (for example, when swapping Git branches).\n\nThere is also a remote RPC cache which allows using a centralised server to store artifacts. A typical pattern here is to have your CI system write artifacts into it and give developers read-only access so they can reuse its work.\n\nFinally there's a HTTP cache which is very similar, but a little obsolete now since the RPC cache outperforms it and has some extra features. Otherwise the two have similar semantics and share quite a bit of implementation.\n\nPlease has server implementations for both the RPC and HTTP caches."`
   349  	Metrics struct {
   350  		PushGatewayURL cli.URL      `help:"The URL of the pushgateway to send metrics to."`
   351  		PushFrequency  cli.Duration `help:"The frequency, in milliseconds, to push statistics at." example:"400ms"`
   352  		PushTimeout    cli.Duration `help:"Timeout on pushes to the metrics repository." example:"500ms"`
   353  		PerTest        bool         `help:"Emit per-test duration metrics. Off by default because they generate increased load on Prometheus."`
   354  		PerUser        bool         `help:"Emit per-user metrics. On by default for compatibility, but will generate more load on Prometheus."`
   355  	} `help:"A section of options relating to reporting metrics. Currently only pushing metrics to a Prometheus pushgateway is supported, which is enabled by the pushgatewayurl setting."`
   356  	CustomMetricLabels map[string]string `help:"Allows defining custom labels to be applied to metrics. The key is the name of the label, and the value is a command to be run, the output of which becomes the label's value. For example, to attach the current Git branch to all metrics:\n\n[custommetriclabels]\nbranch = git rev-parse --abbrev-ref HEAD\n\nBe careful when defining new labels, it is quite possible to overwhelm the metric collector by creating metric sets with too high cardinality."`
   357  	Test               struct {
   358  		Timeout          cli.Duration `help:"Default timeout applied to all tests. Can be overridden on a per-rule basis."`
   359  		DefaultContainer string       `help:"Sets the default type of containerisation to use for tests that are given container = True.\nCurrently the only available option is 'docker', we expect to add support for more engines in future." options:"none,docker"`
   360  		Sandbox          bool         `help:"True to sandbox individual tests, which isolates them from network access, IPC and some aspects of the filesystem. Currently only works on Linux." var:"TEST_SANDBOX"`
   361  		DisableCoverage  []string     `help:"Disables coverage for tests that have any of these labels spcified."`
   362  	}
   363  	Cover struct {
   364  		FileExtension    []string `help:"Extensions of files to consider for coverage.\nDefaults to a reasonably obvious set for the builtin rules including .go, .py, .java, etc."`
   365  		ExcludeExtension []string `help:"Extensions of files to exclude from coverage.\nTypically this is for generated code; the default is to exclude protobuf extensions like .pb.go, _pb2.py, etc."`
   366  	}
   367  	Docker struct {
   368  		DefaultImage       string       `help:"The default image used for any test that doesn't specify another."`
   369  		AllowLocalFallback bool         `help:"If True, will attempt to run the test locally if containerised running fails."`
   370  		Timeout            cli.Duration `help:"Default timeout for containerised tests. Can be overridden on a per-rule basis."`
   371  		ResultsTimeout     cli.Duration `help:"Timeout to wait when trying to retrieve results from inside the container. Default is 20 seconds."`
   372  		RemoveTimeout      cli.Duration `help:"Timeout to wait when trying to remove a container after running a test. Defaults to 20 seconds."`
   373  	} `help:"Please supports running individual tests within Docker containers for isolation. This is useful for tests that mutate some global state (such as an embedded database, or open a server on a particular port). To do so, simply mark a test rule with container = True."`
   374  	Gc struct {
   375  		Keep      []BuildLabel `help:"Marks targets that gc should always keep. Can include meta-targets such as //test/... and //docs:all."`
   376  		KeepLabel []string     `help:"Defines a target label to be kept; for example, if you set this to go, no Go targets would ever be considered for deletion." example:"go"`
   377  	} `help:"Please supports a form of 'garbage collection', by which it means identifying targets that are not used for anything. By default binary targets and all their transitive dependencies are always considered non-garbage, as are any tests directly on those. The config options here allow tweaking this behaviour to retain more things.\n\nNote that it's a very good idea that your BUILD files are in the standard format when running this."`
   378  	Go struct {
   379  		GoTool        string `help:"The binary to use to invoke Go & its subtools with." var:"GO_TOOL"`
   380  		BuildIDTool   string `help:"The binary to use to override Go's BuildIds'." var:"BUILDID_TOOL"`
   381  		GoRoot        string `help:"If set, will set the GOROOT environment variable appropriately during build actions."`
   382  		TestTool      string `help:"Sets the location of the please_go_test tool that is used to template the test main for go_test rules." var:"GO_TEST_TOOL"`
   383  		GoPath        string `help:"If set, will set the GOPATH environment variable appropriately during build actions." var:"GOPATH"`
   384  		ImportPath    string `help:"Sets the default Go import path at the root of this repository.\nFor example, in the Please repo, we might set it to github.com/thought-machine/please to allow imports from that package within the repo." var:"GO_IMPORT_PATH"`
   385  		CgoCCTool     string `help:"Sets the location of CC while building cgo_library and cgo_test rules. Defaults to gcc" var:"CGO_CC_TOOL"`
   386  		FilterTool    string `help:"Sets the location of the please_go_filter tool that is used to filter source files against build constraints." var:"GO_FILTER_TOOL"`
   387  		DefaultStatic bool   `help:"Sets Go binaries to default to static linking. Note that enabling this may have negative consequences for some code, including Go's DNS lookup code in the net module." var:"GO_DEFAULT_STATIC"`
   388  	} `help:"Please has built-in support for compiling Go, and of course is written in Go itself.\nSee the config subfields or the Go rules themselves for more information.\n\nNote that Please is a bit more flexible than Go about directory layout - for example, it is possible to have multiple packages in a directory, but it's not a good idea to push this too far since Go's directory layout is inextricably linked with its import paths."`
   389  	Python struct {
   390  		PipTool            string  `help:"The tool that is invoked during pip_library rules." var:"PIP_TOOL"`
   391  		PipFlags           string  `help:"Additional flags to pass to pip invocations in pip_library rules." var:"PIP_FLAGS"`
   392  		PexTool            string  `help:"The tool that's invoked to build pexes. Defaults to please_pex in the install directory." var:"PEX_TOOL"`
   393  		DefaultInterpreter string  `help:"The interpreter used for python_binary and python_test rules when none is specified on the rule itself. Defaults to python but you could of course set it to, say, pypy." var:"DEFAULT_PYTHON_INTERPRETER"`
   394  		TestRunner         string  `help:"The test runner used to discover & run Python tests; one of unittest, pytest or behave." var:"PYTHON_TEST_RUNNER" options:"unittest,pytest,behave"`
   395  		ModuleDir          string  `help:"Defines a directory containing modules from which they can be imported at the top level.\nBy default this is empty but by convention we define our pip_library rules in third_party/python and set this appropriately. Hence any of those third-party libraries that try something like import six will have it work as they expect, even though it's actually in a different location within the .pex." var:"PYTHON_MODULE_DIR"`
   396  		DefaultPipRepo     cli.URL `help:"Defines a location for a pip repo to download wheels from.\nBy default pip_library uses PyPI (although see below on that) but you may well want to use this define another location to upload your own wheels to.\nIs overridden by the repo argument to pip_library." var:"PYTHON_DEFAULT_PIP_REPO"`
   397  		WheelRepo          cli.URL `help:"Defines a location for a remote repo that python_wheel rules will download from. See python_wheel for more information." var:"PYTHON_WHEEL_REPO"`
   398  		UsePyPI            bool    `help:"Whether or not to use PyPI for pip_library rules or not. Defaults to true, if you disable this you will presumably want to set DefaultPipRepo to use one of your own.\nIs overridden by the use_pypi argument to pip_library." var:"USE_PYPI"`
   399  		WheelNameScheme    string  `help:"Defines a custom templatized wheel naming scheme. Templatized variables should be surrounded in curly braces, and the available options are: url_base, package_name, and version. The default search pattern is '{url_base}/{package_name}-{version}-${{OS}}-${{ARCH}}.whl' along with a few common variants." var:"PYTHON_WHEEL_NAME_SCHEME"`
   400  	} `help:"Please has built-in support for compiling Python.\nPlease's Python artifacts are pex files, which are essentially self-executable zip files containing all needed dependencies, bar the interpreter itself. This fits our aim of at least semi-static binaries for each language.\nSee https://github.com/pantsbuild/pex for more information.\nNote that due to differences between the environment inside a pex and outside some third-party code may not run unmodified (for example, it cannot simply open() files). It's possible to work around a lot of this, but if it all becomes too much it's possible to mark pexes as not zip-safe which typically resolves most of it at a modest speed penalty."`
   401  	Java struct {
   402  		JavacTool          string    `help:"Defines the tool used for the Java compiler. Defaults to javac." var:"JAVAC_TOOL"`
   403  		JlinkTool          string    `help:"Defines the tool used for the Java linker. Defaults to jlink." var:"JLINK_TOOL"`
   404  		JavaHome           string    `help:"Defines the path of the Java Home folder." var:"JAVA_HOME"`
   405  		JavacWorker        string    `help:"Defines the tool used for the Java persistent compiler. This is significantly (approx 4x) faster for large Java trees than invoking javac separately each time. Default to javac_worker in the install directory, but can be switched off to fall back to javactool and separate invocation." var:"JAVAC_WORKER"`
   406  		JarCatTool         string    `help:"Defines the tool used to concatenate .jar files which we use to build the output of java_binary, java_test and various other rules. Defaults to jarcat in the Please install directory." var:"JARCAT_TOOL"`
   407  		PleaseMavenTool    string    `help:"Defines the tool used to fetch information from Maven in maven_jars rules.\nDefaults to please_maven in the Please install directory." var:"PLEASE_MAVEN_TOOL"`
   408  		JUnitRunner        string    `help:"Defines the .jar containing the JUnit runner. This is built into all java_test rules since it's necessary to make JUnit do anything useful.\nDefaults to junit_runner.jar in the Please install directory." var:"JUNIT_RUNNER"`
   409  		DefaultTestPackage string    `help:"The Java classpath to search for functions annotated with @Test. If not specified the compiled sources will be searched for files named *Test.java." var:"DEFAULT_TEST_PACKAGE"`
   410  		ReleaseLevel       string    `help:"The default Java release level when compiling.\nSourceLevel and TargetLevel are ignored if this is set. Bear in mind that this flag is only supported in Java version 9+." var:"JAVA_RELEASE_LEVEL"`
   411  		SourceLevel        string    `help:"The default Java source level when compiling. Defaults to 8." var:"JAVA_SOURCE_LEVEL"`
   412  		TargetLevel        string    `help:"The default Java bytecode level to target. Defaults to 8." var:"JAVA_TARGET_LEVEL"`
   413  		JavacFlags         string    `help:"Additional flags to pass to javac when compiling libraries." example:"-Xmx1200M" var:"JAVAC_FLAGS"`
   414  		JavacTestFlags     string    `help:"Additional flags to pass to javac when compiling tests." example:"-Xmx1200M" var:"JAVAC_TEST_FLAGS"`
   415  		DefaultMavenRepo   []cli.URL `help:"Default location to load artifacts from in maven_jar rules. Can be overridden on a per-rule basis." var:"DEFAULT_MAVEN_REPO"`
   416  	} `help:"Please has built-in support for compiling Java.\nIt builds uber-jars for binary and test rules which contain all dependencies and can be easily deployed, and with the help of some of Please's additional tools they are deterministic as well.\n\nWe've only tested support for Java 7 and 8, although it's likely newer versions will work with little or no change."`
   417  	Cpp struct {
   418  		CCTool             string     `help:"The tool invoked to compile C code. Defaults to gcc but you might want to set it to clang, for example." var:"CC_TOOL"`
   419  		CppTool            string     `help:"The tool invoked to compile C++ code. Defaults to g++ but you might want to set it to clang++, for example." var:"CPP_TOOL"`
   420  		LdTool             string     `help:"The tool invoked to link object files. Defaults to ld but you could also set it to gold, for example." var:"LD_TOOL"`
   421  		ArTool             string     `help:"The tool invoked to archive static libraries. Defaults to ar." var:"AR_TOOL"`
   422  		AsmTool            string     `help:"The tool invoked as an assembler. Currently only used on OSX for cc_embed_binary rules and so defaults to nasm." var:"ASM_TOOL"`
   423  		LinkWithLdTool     bool       `help:"If true, instructs Please to use the tool set earlier in ldtool to link binaries instead of cctool.\nThis is an esoteric setting that most people don't want; a vanilla ld will not perform all steps necessary here (you'll get lots of missing symbol messages from having no libc etc). Generally best to leave this disabled unless you have very specific requirements." var:"LINK_WITH_LD_TOOL"`
   424  		DefaultOptCflags   string     `help:"Compiler flags passed to all C rules during opt builds; these are typically pretty basic things like what language standard you want to target, warning flags, etc.\nDefaults to --std=c99 -O3 -DNDEBUG -Wall -Wextra -Werror" var:"DEFAULT_OPT_CFLAGS"`
   425  		DefaultDbgCflags   string     `help:"Compiler rules passed to all C rules during dbg builds.\nDefaults to --std=c99 -g3 -DDEBUG -Wall -Wextra -Werror." var:"DEFAULT_DBG_CFLAGS"`
   426  		DefaultOptCppflags string     `help:"Compiler flags passed to all C++ rules during opt builds; these are typically pretty basic things like what language standard you want to target, warning flags, etc.\nDefaults to --std=c++11 -O3 -DNDEBUG -Wall -Wextra -Werror" var:"DEFAULT_OPT_CPPFLAGS"`
   427  		DefaultDbgCppflags string     `help:"Compiler rules passed to all C++ rules during dbg builds.\nDefaults to --std=c++11 -g3 -DDEBUG -Wall -Wextra -Werror." var:"DEFAULT_DBG_CPPFLAGS"`
   428  		DefaultLdflags     string     `help:"Linker flags passed to all C++ rules.\nBy default this is empty." var:"DEFAULT_LDFLAGS"`
   429  		DefaultNamespace   string     `help:"Namespace passed to all cc_embed_binary rules when not overridden by the namespace argument to that rule.\nNot set by default, if you want to use those rules you'll need to set it or pass it explicitly to each one." var:"DEFAULT_NAMESPACE"`
   430  		PkgConfigPath      string     `help:"Custom PKG_CONFIG_PATH for pkg-config.\nBy default this is empty." var:"PKG_CONFIG_PATH"`
   431  		Coverage           bool       `help:"If true (the default), coverage will be available for C and C++ build rules.\nThis is still a little experimental but should work for GCC. Right now it does not work for Clang (it likely will in Clang 4.0 which will likely support --fprofile-dir) and so this can be useful to disable it.\nIt's also useful in some cases for CI systems etc if you'd prefer to avoid the overhead, since the tests have to be compiled with extra instrumentation and without optimisation." var:"CPP_COVERAGE"`
   432  		TestMain           BuildLabel `help:"The build target to use for the default main for C++ test rules." example:"@pleasings//cc:unittest_main" var:"CC_TEST_MAIN"`
   433  		ClangModules       bool       `help:"Uses Clang-style arguments for compiling cc_module rules. If disabled gcc-style arguments will be used instead. Experimental, expected to be removed at some point once module compilation methods are more consistent." var:"CC_MODULES_CLANG"`
   434  	} `help:"Please has built-in support for compiling C and C++ code. We don't support every possible nuance of compilation for these languages, but aim to provide something fairly straightforward.\nTypically there is little problem compiling & linking against system libraries although Please has no insight into those libraries and when they change, so cannot rebuild targets appropriately.\n\nThe C and C++ rules are very similar and simply take a different set of tools and flags to facilitate side-by-side usage."`
   435  	Proto struct {
   436  		ProtocTool       string   `help:"The binary invoked to compile .proto files. Defaults to protoc." var:"PROTOC_TOOL"`
   437  		ProtocGoPlugin   string   `help:"The binary passed to protoc as a plugin to generate Go code. Defaults to protoc-gen-go.\nWe've found this easier to manage with a go_get rule instead though, so you can also pass a build label here. See the Please repo for an example." var:"PROTOC_GO_PLUGIN"`
   438  		GrpcPythonPlugin string   `help:"The plugin invoked to compile Python code for grpc_library.\nDefaults to protoc-gen-grpc-python." var:"GRPC_PYTHON_PLUGIN"`
   439  		GrpcJavaPlugin   string   `help:"The plugin invoked to compile Java code for grpc_library.\nDefaults to protoc-gen-grpc-java." var:"GRPC_JAVA_PLUGIN"`
   440  		GrpcCCPlugin     string   `help:"The plugin invoked to compile C++ code for grpc_library.\nDefaults to grpc_cpp_plugin." var:"GRPC_CC_PLUGIN"`
   441  		Language         []string `help:"Sets the default set of languages that proto rules are built for.\nChosen from the set of {cc, java, go, py}.\nDefaults to all of them!" var:"PROTO_LANGUAGES"`
   442  		PythonDep        string   `help:"An in-repo dependency that's applied to any Python proto libraries." var:"PROTO_PYTHON_DEP"`
   443  		JavaDep          string   `help:"An in-repo dependency that's applied to any Java proto libraries." var:"PROTO_JAVA_DEP"`
   444  		GoDep            string   `help:"An in-repo dependency that's applied to any Go proto libraries." var:"PROTO_GO_DEP"`
   445  		JsDep            string   `help:"An in-repo dependency that's applied to any Javascript proto libraries." var:"PROTO_JS_DEP"`
   446  		PythonGrpcDep    string   `help:"An in-repo dependency that's applied to any Python gRPC libraries." var:"GRPC_PYTHON_DEP"`
   447  		JavaGrpcDep      string   `help:"An in-repo dependency that's applied to any Java gRPC libraries." var:"GRPC_JAVA_DEP"`
   448  		GoGrpcDep        string   `help:"An in-repo dependency that's applied to any Go gRPC libraries." var:"GRPC_GO_DEP"`
   449  	} `help:"Please has built-in support for compiling protocol buffers, which are a form of codegen to define common data types which can be serialised and communicated between different languages.\nSee https://developers.google.com/protocol-buffers/ for more information.\n\nThere is also support for gRPC, which is an implementation of protobuf's RPC framework. See http://www.grpc.io/ for more information.\n\nNote that you must have the protocol buffers compiler (and gRPC plugins, if needed) installed on your machine to make use of these rules."`
   450  	Licences struct {
   451  		Accept []string `help:"Licences that are accepted in this repository.\nWhen this is empty licences are ignored. As soon as it's set any licence detected or assigned must be accepted explicitly here.\nThere's no fuzzy matching, so some package managers (especially PyPI and Maven, but shockingly not npm which rather nicely uses SPDX) will generate a lot of slightly different spellings of the same thing, which will all have to be accepted here. We'd rather that than trying to 'cleverly' match them which might result in matching the wrong thing."`
   452  		Reject []string `help:"Licences that are explicitly rejected in this repository.\nAn astute observer will notice that this is not very different to just not adding it to the accept section, but it does have the advantage of explicitly documenting things that the team aren't allowed to use."`
   453  	} `help:"Please has some limited support for declaring acceptable licences and detecting them from some libraries. You should not rely on this for complete licence compliance, but it can be a useful check to try to ensure that unacceptable licences do not slip in."`
   454  	Aliases  map[string]string `help:"It is possible to define aliases for new commands in your .plzconfig file. These are essentially string-string replacements of the command line, for example 'deploy = run //tools:deployer --' makes 'plz deploy' run a particular tool."`
   455  	Alias    map[string]*Alias `help:"Allows defining alias replacements with more detail than the [aliases] section. Otherwise follows the same process, i.e. performs replacements of command strings."`
   456  	Provider map[string]*struct {
   457  		Target BuildLabel   `help:"The in-repo target to build this provider."`
   458  		Path   []BuildLabel `help:"The paths that this provider should operate for."`
   459  	} `help:"Allows configuring BUILD file providers, which are subprocesses that know how to provide the contents of a BUILD file when none exists. For example, a Go provider might infer the contents of a BUILD file from the Go source files directly."`
   460  	Bazel struct {
   461  		Compatibility bool `help:"Activates limited Bazel compatibility mode. When this is active several rule arguments are available under different names (e.g. compiler_flags -> copts etc), the WORKSPACE file is interpreted, Makefile-style replacements like $< and $@ are made in genrule commands, etc.\nNote that Skylark is not generally supported and many aspects of compatibility are fairly superficial; it's unlikely this will work for complex setups of either tool." var:"BAZEL_COMPATIBILITY"`
   462  	} `help:"Bazel is an open-sourced version of Google's internal build tool. Please draws a lot of inspiration from the original tool although the two have now diverged in various ways.\nNonetheless, if you've used Bazel, you will likely find Please familiar."`
   463  
   464  	// buildEnvStored is a cached form of BuildEnv.
   465  	buildEnvStored *storedBuildEnv
   466  }
   467  
   468  // An Alias represents aliases in the config.
   469  type Alias struct {
   470  	Cmd              string   `help:"Command to run for this alias."`
   471  	Desc             string   `help:"Description of this alias"`
   472  	Subcommand       []string `help:"Known subcommands of this command"`
   473  	Flag             []string `help:"Known flags of this command"`
   474  	PositionalLabels bool     `help:"Treats positional arguments after commands as build labels for the purpose of tab completion."`
   475  }
   476  
   477  type storedBuildEnv struct {
   478  	Env, Path []string
   479  	Once      sync.Once
   480  }
   481  
   482  // Hash returns a hash of the parts of this configuration that affect building targets in general.
   483  // Most parts are considered not to (e.g. cache settings) or affect specific targets (e.g. changing
   484  // tool paths which get accounted for on the targets that use them).
   485  func (config *Configuration) Hash() []byte {
   486  	h := sha1.New()
   487  	// These fields are the ones that need to be in the general hash; other things will be
   488  	// picked up by relevant rules (particularly tool paths etc).
   489  	// Note that container settings are handled separately.
   490  	for _, f := range config.Parse.BuildFileName {
   491  		h.Write([]byte(f))
   492  	}
   493  	h.Write([]byte(config.Build.Lang))
   494  	h.Write([]byte(config.Build.Nonce))
   495  	for _, l := range config.Licences.Reject {
   496  		h.Write([]byte(l))
   497  	}
   498  	for _, env := range config.getBuildEnv(false) {
   499  		h.Write([]byte(env))
   500  	}
   501  	return h.Sum(nil)
   502  }
   503  
   504  // ContainerisationHash returns the hash of the containerisation part of the config.
   505  func (config *Configuration) ContainerisationHash() []byte {
   506  	h := sha1.New()
   507  	encoder := gob.NewEncoder(h)
   508  	if err := encoder.Encode(config.Docker); err != nil {
   509  		panic(err)
   510  	}
   511  	return h.Sum(nil)
   512  }
   513  
   514  // GetBuildEnv returns the build environment configured for this config object.
   515  func (config *Configuration) GetBuildEnv() []string {
   516  	config.buildEnvStored.Once.Do(func() {
   517  		config.buildEnvStored.Env = config.getBuildEnv(true)
   518  		for _, e := range config.buildEnvStored.Env {
   519  			if strings.HasPrefix(e, "PATH=") {
   520  				config.buildEnvStored.Path = strings.Split(strings.TrimPrefix(e, "PATH="), ":")
   521  			}
   522  		}
   523  	})
   524  	return config.buildEnvStored.Env
   525  }
   526  
   527  // Path returns the slice of strings corresponding to the PATH env var.
   528  func (config *Configuration) Path() []string {
   529  	config.GetBuildEnv() // ensure it is initialised
   530  	return config.buildEnvStored.Path
   531  }
   532  
   533  func (config *Configuration) getBuildEnv(expanded bool) []string {
   534  	maybeExpandHomePath := func(s string) string {
   535  		if !expanded {
   536  			return s
   537  		}
   538  		return ExpandHomePath(s)
   539  	}
   540  
   541  	env := []string{
   542  		// Need to know these for certain rules.
   543  		"ARCH=" + config.Build.Arch.Arch,
   544  		"OS=" + config.Build.Arch.OS,
   545  		// These are slightly modified forms that are more convenient for some things.
   546  		"XARCH=" + config.Build.Arch.XArch(),
   547  		"XOS=" + config.Build.Arch.XOS(),
   548  		// It's easier to just make these available for Go-based rules.
   549  		"GOARCH=" + config.Build.Arch.GoArch(),
   550  		"GOOS=" + config.Build.Arch.OS,
   551  	}
   552  
   553  	// from the BuildEnv config keyword
   554  	for k, v := range config.BuildEnv {
   555  		pair := strings.Replace(strings.ToUpper(k), "-", "_", -1) + "=" + v
   556  		env = append(env, pair)
   557  	}
   558  
   559  	// from the user's environment based on the PassEnv config keyword
   560  	path := false
   561  	for _, k := range config.Build.PassEnv {
   562  		if v, isSet := os.LookupEnv(k); isSet {
   563  			if k == "PATH" {
   564  				// plz's install location always needs to be on the path.
   565  				v = maybeExpandHomePath(config.Please.Location) + ":" + v
   566  				path = true
   567  			}
   568  			env = append(env, k+"="+v)
   569  		}
   570  	}
   571  	if !path {
   572  		// Use a restricted PATH; it'd be easier for the user if we pass it through
   573  		// but really external environment variables shouldn't affect this.
   574  		// The only concession is that ~ is expanded as the user's home directory
   575  		// in PATH entries.
   576  		env = append(env, "PATH="+maybeExpandHomePath(strings.Join(append([]string{config.Please.Location}, config.Build.Path...), ":")))
   577  	}
   578  
   579  	sort.Strings(env)
   580  	return env
   581  }
   582  
   583  // TagsToFields returns a map of string represent the properties of CONFIG object to the config Structfield
   584  func (config *Configuration) TagsToFields() map[string]reflect.StructField {
   585  	tags := make(map[string]reflect.StructField)
   586  	v := reflect.ValueOf(config).Elem()
   587  	for i := 0; i < v.NumField(); i++ {
   588  		if field := v.Field(i); field.Kind() == reflect.Struct {
   589  			for j := 0; j < field.NumField(); j++ {
   590  				if tag := field.Type().Field(j).Tag.Get("var"); tag != "" {
   591  					tags[tag] = field.Type().Field(j)
   592  				}
   593  			}
   594  		}
   595  	}
   596  	return tags
   597  }
   598  
   599  // ApplyOverrides applies a set of overrides to the config.
   600  // The keys of the given map are dot notation for the config setting.
   601  func (config *Configuration) ApplyOverrides(overrides map[string]string) error {
   602  	match := func(s1 string) func(string) bool {
   603  		return func(s2 string) bool {
   604  			return strings.ToLower(s2) == s1
   605  		}
   606  	}
   607  	elem := reflect.ValueOf(config).Elem()
   608  	for k, v := range overrides {
   609  		split := strings.Split(strings.ToLower(k), ".")
   610  		if len(split) != 2 {
   611  			return fmt.Errorf("Bad option format: %s", k)
   612  		}
   613  		field := elem.FieldByNameFunc(match(split[0]))
   614  		if !field.IsValid() {
   615  			return fmt.Errorf("Unknown config field: %s", split[0])
   616  		} else if field.Kind() == reflect.Map {
   617  			field.SetMapIndex(reflect.ValueOf(split[1]), reflect.ValueOf(v))
   618  			continue
   619  		} else if field.Kind() != reflect.Struct {
   620  			return fmt.Errorf("Unsettable config field: %s", split[0])
   621  		}
   622  		subfield, ok := field.Type().FieldByNameFunc(match(split[1]))
   623  		if !ok {
   624  			return fmt.Errorf("Unknown config field: %s", split[1])
   625  		}
   626  		field = field.FieldByNameFunc(match(split[1]))
   627  		switch field.Kind() {
   628  		case reflect.String:
   629  			// verify this is a legit setting for this field
   630  			if options := subfield.Tag.Get("options"); options != "" {
   631  				if !cli.ContainsString(v, strings.Split(options, ",")) {
   632  					return fmt.Errorf("Invalid value %s for field %s; options are %s", v, k, options)
   633  				}
   634  			}
   635  			if field.Type().Name() == "URL" {
   636  				field.Set(reflect.ValueOf(cli.URL(v)))
   637  			} else {
   638  				field.Set(reflect.ValueOf(v))
   639  			}
   640  		case reflect.Bool:
   641  			v = strings.ToLower(v)
   642  			// Mimics the set of truthy things gcfg accepts in our config file.
   643  			field.SetBool(v == "true" || v == "yes" || v == "on" || v == "1")
   644  		case reflect.Int:
   645  			i, err := strconv.Atoi(v)
   646  			if err != nil {
   647  				return fmt.Errorf("Invalid value for an integer field: %s", v)
   648  			}
   649  			field.Set(reflect.ValueOf(i))
   650  		case reflect.Int64:
   651  			var d cli.Duration
   652  			if err := d.UnmarshalText([]byte(v)); err != nil {
   653  				return fmt.Errorf("Invalid value for a duration field: %s", v)
   654  			}
   655  			field.Set(reflect.ValueOf(d))
   656  		case reflect.Slice:
   657  			// Comma-separated values are accepted.
   658  			if field.Type().Elem().Kind() == reflect.Struct {
   659  				// Assume it must be a slice of BuildLabel.
   660  				l := []BuildLabel{}
   661  				for _, s := range strings.Split(v, ",") {
   662  					l = append(l, ParseBuildLabel(s, ""))
   663  				}
   664  				field.Set(reflect.ValueOf(l))
   665  			} else if field.Type().Elem().Name() == "URL" {
   666  				urls := []cli.URL{}
   667  				for _, s := range strings.Split(v, ",") {
   668  					urls = append(urls, cli.URL(s))
   669  				}
   670  				field.Set(reflect.ValueOf(urls))
   671  			} else {
   672  				field.Set(reflect.ValueOf(strings.Split(v, ",")))
   673  			}
   674  		default:
   675  			return fmt.Errorf("Can't override config field %s (is %s)", k, field.Kind())
   676  		}
   677  	}
   678  	return nil
   679  }
   680  
   681  // Completions returns a list of possible completions for the given option prefix.
   682  func (config *Configuration) Completions(prefix string) []flags.Completion {
   683  	ret := []flags.Completion{}
   684  	t := reflect.TypeOf(config).Elem()
   685  	for i := 0; i < t.NumField(); i++ {
   686  		if field := t.Field(i); field.Type.Kind() == reflect.Struct {
   687  			for j := 0; j < field.Type.NumField(); j++ {
   688  				subfield := field.Type.Field(j)
   689  				if name := strings.ToLower(field.Name + "." + subfield.Name); strings.HasPrefix(name, prefix) {
   690  					help := subfield.Tag.Get("help")
   691  					if options := subfield.Tag.Get("options"); options != "" {
   692  						for _, option := range strings.Split(options, ",") {
   693  							ret = append(ret, flags.Completion{Item: name + ":" + option, Description: help})
   694  						}
   695  					} else {
   696  						ret = append(ret, flags.Completion{Item: name + ":", Description: help})
   697  					}
   698  				}
   699  			}
   700  		}
   701  	}
   702  	return ret
   703  }
   704  
   705  // UpdateArgsWithAliases applies the aliases in this config to the given set of arguments.
   706  func (config *Configuration) UpdateArgsWithAliases(args []string) []string {
   707  	for idx, arg := range args[1:] {
   708  		// Please should not touch anything that comes after `--`
   709  		if arg == "--" {
   710  			break
   711  		}
   712  		for k, v := range config.AllAliases() {
   713  			if arg == k {
   714  				// We could insert every token in v into os.Args at this point and then we could have
   715  				// aliases defined in terms of other aliases but that seems rather like overkill so just
   716  				// stick the replacement in wholesale instead.
   717  				// Do not ask about the inner append and the empty slice.
   718  				cmd, err := shlex.Split(v.Cmd)
   719  				if err != nil {
   720  					log.Fatalf("Invalid alias replacement for %s: %s", k, err)
   721  				}
   722  				return append(append(append([]string{}, args[:idx+1]...), cmd...), args[idx+2:]...)
   723  			}
   724  		}
   725  	}
   726  	return args
   727  }
   728  
   729  // AllAliases returns all the aliases defined in this config
   730  func (config *Configuration) AllAliases() map[string]*Alias {
   731  	ret := map[string]*Alias{}
   732  	for k, v := range config.Aliases {
   733  		ret[k] = &Alias{Cmd: v}
   734  	}
   735  	for k, v := range config.Alias {
   736  		ret[k] = v
   737  	}
   738  	return ret
   739  }
   740  
   741  // PrintAliases prints the set of aliases defined in the config.
   742  func (config *Configuration) PrintAliases(w io.Writer) {
   743  	aliases := config.AllAliases()
   744  	names := make([]string, 0, len(aliases))
   745  	maxlen := 0
   746  	for alias := range aliases {
   747  		names = append(names, alias)
   748  		if len(alias) > maxlen {
   749  			maxlen = len(alias)
   750  		}
   751  	}
   752  	sort.Strings(names)
   753  	w.Write([]byte("\nAvailable commands for this repository:\n"))
   754  	tmpl := fmt.Sprintf("  %%-%ds  %%s\n", maxlen)
   755  	for _, name := range names {
   756  		fmt.Fprintf(w, tmpl, name, aliases[name].Desc)
   757  	}
   758  }
   759  
   760  // IsABuildFile returns true if given filename is a build file name.
   761  func (config *Configuration) IsABuildFile(name string) bool {
   762  	for _, buildFileName := range config.Parse.BuildFileName {
   763  		if name == buildFileName {
   764  			return true
   765  		}
   766  	}
   767  	return false
   768  }