diff --git a/src/cmd/asm/internal/lex/input.go b/src/cmd/asm/internal/lex/input.go index e373ae817e0..276b4b0dcd6 100644 --- a/src/cmd/asm/internal/lex/input.go +++ b/src/cmd/asm/internal/lex/input.go @@ -50,7 +50,7 @@ func predefine(defines flags.MultiFlag) map[string]*Macro { // Set macros for GOEXPERIMENTs so we can easily switch // runtime assembly code based on them. if *flags.CompilingRuntime { - for _, exp := range buildcfg.EnabledExperiments() { + for _, exp := range buildcfg.Experiment.Enabled() { // Define macro. name := "GOEXPERIMENT_" + exp macros[name] = &Macro{ diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index deab3dddd0d..77c0e229e5e 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -22,6 +22,26 @@ import ( "cmd/go/internal/fsys" ) +// Global build parameters (used during package load) +var ( + Goos = envOr("GOOS", build.Default.GOOS) + Goarch = envOr("GOARCH", build.Default.GOARCH) + + ExeSuffix = exeSuffix() + + // ModulesEnabled specifies whether the go command is running + // in module-aware mode (as opposed to GOPATH mode). + // It is equal to modload.Enabled, but not all packages can import modload. + ModulesEnabled bool +) + +func exeSuffix() string { + if Goos == "windows" { + return ".exe" + } + return "" +} + // These are general "build flags" used by build and other commands. var ( BuildA bool // -a flag @@ -60,8 +80,6 @@ var ( // GoPathError is set when GOPATH is not set. it contains an // explanation why GOPATH is unset. GoPathError string - - GOEXPERIMENT = envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT) ) func defaultContext() build.Context { @@ -79,20 +97,15 @@ func defaultContext() build.Context { build.ToolDir = filepath.Join(ctxt.GOROOT, "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) } - ctxt.GOPATH = envOr("GOPATH", gopath(ctxt)) - // Override defaults computed in go/build with defaults // from go environment configuration file, if known. - ctxt.GOOS = envOr("GOOS", ctxt.GOOS) - ctxt.GOARCH = envOr("GOARCH", ctxt.GOARCH) + ctxt.GOPATH = envOr("GOPATH", gopath(ctxt)) + ctxt.GOOS = Goos + ctxt.GOARCH = Goarch - // The experiments flags are based on GOARCH, so they may - // need to change. TODO: This should be cleaned up. - buildcfg.UpdateExperiments(ctxt.GOOS, ctxt.GOARCH, GOEXPERIMENT) + // ToolTags are based on GOEXPERIMENT, which we will parse and + // initialize later. ctxt.ToolTags = nil - for _, exp := range buildcfg.EnabledExperiments() { - ctxt.ToolTags = append(ctxt.ToolTags, "goexperiment."+exp) - } // The go/build rule for whether cgo is enabled is: // 1. If $CGO_ENABLED is set, respect it. @@ -137,6 +150,33 @@ func init() { BuildToolchainLinker = func() string { return "missing-linker" } } +// Experiment configuration. +var ( + // RawGOEXPERIMENT is the GOEXPERIMENT value set by the user. + RawGOEXPERIMENT = envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT) + // CleanGOEXPERIMENT is the minimal GOEXPERIMENT value needed to reproduce the + // experiments enabled by RawGOEXPERIMENT. + CleanGOEXPERIMENT = RawGOEXPERIMENT + + Experiment *buildcfg.ExperimentFlags + ExperimentErr error +) + +func init() { + Experiment, ExperimentErr = buildcfg.ParseGOEXPERIMENT(Goos, Goarch, RawGOEXPERIMENT) + if ExperimentErr != nil { + return + } + + // GOEXPERIMENT is valid, so convert it to canonical form. + CleanGOEXPERIMENT = Experiment.String() + + // Add build tags based on the experiments in effect. + for _, exp := range Experiment.Enabled() { + BuildContext.ToolTags = append(BuildContext.ToolTags, "goexperiment."+exp) + } +} + // An EnvVar is an environment variable Name=Value. type EnvVar struct { Name string @@ -151,26 +191,6 @@ var OrigEnv []string // not CmdEnv. var CmdEnv []EnvVar -// Global build parameters (used during package load) -var ( - Goarch = BuildContext.GOARCH - Goos = BuildContext.GOOS - - ExeSuffix = exeSuffix() - - // ModulesEnabled specifies whether the go command is running - // in module-aware mode (as opposed to GOPATH mode). - // It is equal to modload.Enabled, but not all packages can import modload. - ModulesEnabled bool -) - -func exeSuffix() string { - if Goos == "windows" { - return ".exe" - } - return "" -} - var envCache struct { once sync.Once m map[string]string diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index c1adf8cef4c..fcabc8d1c71 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -74,7 +74,14 @@ func MkEnv() []cfg.EnvVar { {Name: "GOCACHE", Value: cache.DefaultDir()}, {Name: "GOENV", Value: envFile}, {Name: "GOEXE", Value: cfg.ExeSuffix}, - {Name: "GOEXPERIMENT", Value: buildcfg.GOEXPERIMENT()}, + + // List the raw value of GOEXPERIMENT, not the cleaned one. + // The set of default experiments may change from one release + // to the next, so a GOEXPERIMENT setting that is redundant + // with the current toolchain might actually be relevant with + // a different version (for example, when bisecting a regression). + {Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT}, + {Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")}, {Name: "GOHOSTARCH", Value: runtime.GOARCH}, {Name: "GOHOSTOS", Value: runtime.GOOS}, @@ -222,6 +229,9 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) { } buildcfg.Check() + if cfg.ExperimentErr != nil { + base.Fatalf("go: %v", cfg.ExperimentErr) + } env := cfg.CmdEnv env = append(env, ExtraEnvVars()...) @@ -374,9 +384,9 @@ func checkBuildConfig(add map[string]string, del map[string]bool) error { } } - goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", buildcfg.GOEXPERIMENT(), "") + goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", cfg.RawGOEXPERIMENT, buildcfg.DefaultGOEXPERIMENT) if okGOEXPERIMENT { - if _, _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil { + if _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil { return err } } diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index fdc00f95dca..8c169d1643a 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -2334,8 +2334,8 @@ func (p *Package) setBuildInfo() { } } appendSetting("GOARCH", cfg.BuildContext.GOARCH) - if cfg.GOEXPERIMENT != "" { - appendSetting("GOEXPERIMENT", cfg.GOEXPERIMENT) + if cfg.RawGOEXPERIMENT != "" { + appendSetting("GOEXPERIMENT", cfg.RawGOEXPERIMENT) } appendSetting("GOOS", cfg.BuildContext.GOOS) if key, val := cfg.GetArchEnv(); key != "" && val != "" { diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 7d3a16c5f51..6d6837aa8a2 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -12,7 +12,6 @@ import ( "encoding/json" "errors" "fmt" - "internal/buildcfg" exec "internal/execabs" "internal/lazyregexp" "io" @@ -320,8 +319,8 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { key, val := cfg.GetArchEnv() fmt.Fprintf(h, "%s=%s\n", key, val) - if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { - fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment) + if cfg.CleanGOEXPERIMENT != "" { + fmt.Fprintf(h, "GOEXPERIMENT=%q\n", cfg.CleanGOEXPERIMENT) } // TODO(rsc): Convince compiler team not to add more magic environment variables, @@ -1301,8 +1300,8 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) { key, val := cfg.GetArchEnv() fmt.Fprintf(h, "%s=%s\n", key, val) - if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { - fmt.Fprintf(h, "GOEXPERIMENT=%q\n", goexperiment) + if cfg.CleanGOEXPERIMENT != "" { + fmt.Fprintf(h, "GOEXPERIMENT=%q\n", cfg.CleanGOEXPERIMENT) } // The linker writes source file paths that say GOROOT_FINAL, but diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index e1e2b11dd73..a6174b2ed28 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -8,7 +8,6 @@ import ( "bufio" "bytes" "fmt" - "internal/buildcfg" "io" "log" "os" @@ -245,7 +244,7 @@ CheckFlags: } // TODO: Test and delete these conditions. - if buildcfg.Experiment.FieldTrack || buildcfg.Experiment.PreemptibleLoops { + if cfg.ExperimentErr != nil || cfg.Experiment.FieldTrack || cfg.Experiment.PreemptibleLoops { canDashC = false } diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go index c0a1d3ccfc4..ed46ed822a6 100644 --- a/src/cmd/go/main.go +++ b/src/cmd/go/main.go @@ -190,6 +190,9 @@ func invoke(cmd *base.Command, args []string) { // 'go env' handles checking the build config if cmd != envcmd.CmdEnv { buildcfg.Check() + if cfg.ExperimentErr != nil { + base.Fatalf("go: %v", cfg.ExperimentErr) + } } // Set environment (GOOS, GOARCH, etc) explicitly. diff --git a/src/cmd/internal/objabi/flag.go b/src/cmd/internal/objabi/flag.go index f75c054fcb6..acb2dd59eaf 100644 --- a/src/cmd/internal/objabi/flag.go +++ b/src/cmd/internal/objabi/flag.go @@ -99,11 +99,11 @@ func (versionFlag) Set(s string) error { if s == "goexperiment" { // test/run.go uses this to discover the full set of // experiment tags. Report everything. - p = " X:" + strings.Join(buildcfg.AllExperiments(), ",") + p = " X:" + strings.Join(buildcfg.Experiment.All(), ",") } else { - // If the enabled experiments differ from the defaults, + // If the enabled experiments differ from the baseline, // include that difference. - if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { + if goexperiment := buildcfg.Experiment.String(); goexperiment != "" { p = " X:" + goexperiment } } diff --git a/src/cmd/internal/objabi/util.go b/src/cmd/internal/objabi/util.go index 6bfa25a5cae..c2f1b204b98 100644 --- a/src/cmd/internal/objabi/util.go +++ b/src/cmd/internal/objabi/util.go @@ -22,5 +22,5 @@ const ( // or link object files that are incompatible with each other. This // string always starts with "go object ". func HeaderString() string { - return fmt.Sprintf("go object %s %s %s X:%s\n", buildcfg.GOOS, buildcfg.GOARCH, buildcfg.Version, strings.Join(buildcfg.EnabledExperiments(), ",")) + return fmt.Sprintf("go object %s %s %s X:%s\n", buildcfg.GOOS, buildcfg.GOARCH, buildcfg.Version, strings.Join(buildcfg.Experiment.Enabled(), ",")) } diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 26f9db8ec41..d13c3ff8b61 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -124,7 +124,7 @@ func Main(arch *sys.Arch, theArch Arch) { addstrdata1(ctxt, "internal/buildcfg.defaultGOROOT="+final) buildVersion := buildcfg.Version - if goexperiment := buildcfg.GOEXPERIMENT(); goexperiment != "" { + if goexperiment := buildcfg.Experiment.String(); goexperiment != "" { buildVersion += " X:" + goexperiment } addstrdata1(ctxt, "runtime.buildVersion="+buildVersion) diff --git a/src/go/build/build.go b/src/go/build/build.go index c1d044e55a8..b0842b3a1a6 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -311,7 +311,7 @@ func defaultContext() Context { // used for compiling alternative files for the experiment. This allows // changes for the experiment, like extra struct fields in the runtime, // without affecting the base non-experiment code at all. - for _, exp := range buildcfg.EnabledExperiments() { + for _, exp := range buildcfg.Experiment.Enabled() { c.ToolTags = append(c.ToolTags, "goexperiment."+exp) } defaultToolTags = append([]string{}, c.ToolTags...) // our own private copy diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go index 230ec0b231a..a56b36efdf5 100644 --- a/src/internal/buildcfg/exp.go +++ b/src/internal/buildcfg/exp.go @@ -12,6 +12,13 @@ import ( "internal/goexperiment" ) +// ExperimentFlags represents a set of GOEXPERIMENT flags relative to a baseline +// (platform-default) experiment configuration. +type ExperimentFlags struct { + goexperiment.Flags + baseline goexperiment.Flags +} + // Experiment contains the toolchain experiments enabled for the // current build. // @@ -21,14 +28,17 @@ import ( // experimentBaseline specifies the experiment flags that are enabled by // default in the current toolchain. This is, in effect, the "control" // configuration and any variation from this is an experiment. -var Experiment, experimentBaseline = func() (goexperiment.Flags, goexperiment.Flags) { - flags, baseline, err := ParseGOEXPERIMENT(GOOS, GOARCH, envOr("GOEXPERIMENT", defaultGOEXPERIMENT)) +var Experiment ExperimentFlags = func() ExperimentFlags { + flags, err := ParseGOEXPERIMENT(GOOS, GOARCH, envOr("GOEXPERIMENT", defaultGOEXPERIMENT)) if err != nil { Error = err + return ExperimentFlags{} } - return flags, baseline + return *flags }() +// DefaultGOEXPERIMENT is the embedded default GOEXPERIMENT string. +// It is not guaranteed to be canonical. const DefaultGOEXPERIMENT = defaultGOEXPERIMENT // FramePointerEnabled enables the use of platform conventions for @@ -45,21 +55,24 @@ var FramePointerEnabled = GOARCH == "amd64" || GOARCH == "arm64" // flag sets. // // TODO(mdempsky): Move to internal/goexperiment. -func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment.Flags, err error) { +func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) { regabiSupported := false switch goarch { case "amd64", "arm64", "ppc64le", "ppc64": regabiSupported = true } - baseline = goexperiment.Flags{ + baseline := goexperiment.Flags{ RegabiWrappers: regabiSupported, RegabiArgs: regabiSupported, PacerRedesign: true, } // Start with the statically enabled set of experiments. - flags = baseline + flags := &ExperimentFlags{ + Flags: baseline, + baseline: baseline, + } // Pick up any changes to the baseline configuration from the // GOEXPERIMENT environment. This can be set at make.bash time @@ -67,7 +80,7 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment if goexp != "" { // Create a map of known experiment names. names := make(map[string]func(bool)) - rv := reflect.ValueOf(&flags).Elem() + rv := reflect.ValueOf(&flags.Flags).Elem() rt := rv.Type() for i := 0; i < rt.NumField(); i++ { field := rv.Field(i) @@ -92,7 +105,7 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment // GOEXPERIMENT=none disables all experiment flags. // This is used by cmd/dist, which doesn't know how // to build with any experiment flags. - flags = goexperiment.Flags{} + flags.Flags = goexperiment.Flags{} continue } val := true @@ -101,8 +114,7 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment } set, ok := names[f] if !ok { - err = fmt.Errorf("unknown GOEXPERIMENT %s", f) - return + return nil, fmt.Errorf("unknown GOEXPERIMENT %s", f) } set(val) } @@ -119,9 +131,15 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment } // Check regabi dependencies. if flags.RegabiArgs && !flags.RegabiWrappers { - err = fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers") + return nil, fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers") } - return + return flags, nil +} + +// String returns the canonical GOEXPERIMENT string to enable this experiment +// configuration. (Experiments in the same state as in the baseline are elided.) +func (exp *ExperimentFlags) String() string { + return strings.Join(expList(&exp.Flags, &exp.baseline, false), ",") } // expList returns the list of lower-cased experiment names for @@ -154,37 +172,14 @@ func expList(exp, base *goexperiment.Flags, all bool) []string { return list } -// GOEXPERIMENT is a comma-separated list of enabled or disabled -// experiments that differ from the baseline experiment configuration. -// GOEXPERIMENT is exactly what a user would set on the command line -// to get the set of enabled experiments. -func GOEXPERIMENT() string { - goexp := strings.Join(expList(&Experiment, &experimentBaseline, false), ",") - if goexp == "" && DefaultGOEXPERIMENT != "" { - goexp = "," // non-empty to override DefaultGOEXPERIMENT - } - return goexp -} - -// EnabledExperiments returns a list of enabled experiments, as +// Enabled returns a list of enabled experiments, as // lower-cased experiment names. -func EnabledExperiments() []string { - return expList(&Experiment, nil, false) +func (exp *ExperimentFlags) Enabled() []string { + return expList(&exp.Flags, nil, false) } -// AllExperiments returns a list of all experiment settings. +// All returns a list of all experiment settings. // Disabled experiments appear in the list prefixed by "no". -func AllExperiments() []string { - return expList(&Experiment, nil, true) -} - -// UpdateExperiments updates the Experiment global based on a new GOARCH value. -// This is only required for cmd/go, which can change GOARCH after -// program startup due to use of "go env -w". -func UpdateExperiments(goos, goarch, goexperiment string) { - var err error - Experiment, experimentBaseline, err = ParseGOEXPERIMENT(goos, goarch, goexperiment) - if err != nil { - Error = err - } +func (exp *ExperimentFlags) All() []string { + return expList(&exp.Flags, nil, true) }