programs listing versions of Go earlier than Go 1.20 are configured to match Go 1.20,
not the older version.
-To override these defaults, a main package's source files
+To override these defaults, starting in Go 1.23, the work module's `go.mod`
+or the workspace's `go.work` can list one or more `godebug` lines:
+
+ godebug (
+ default=go1.21
+ panicnil=1
+ asynctimerchan=0
+ )
+
+The special key `default` indicates a Go version to take unspecified
+settings from. This allows setting the GODEBUG defaults separately
+from the Go language version in the module.
+In this example, the program is asking for Go 1.21 semantics and
+then asking for the old pre-Go 1.21 `panic(nil)` behavior and the
+new Go 1.23 `asynctimerchan=0` behavior.
+
+Only the work module's `go.mod` is consulted for `godebug` directives.
+Any directives in required dependency modules are ignored.
+It is an error to list a `godebug` with an unrecognized setting.
+(Toolchains older than Go 1.23 reject all `godebug` lines, since they do not
+understand `godebug` at all.)
+
+The defaults from the `go` and `godebug` lines apply to all main
+packages that are built. For more fine-grained control,
+starting in Go 1.21, a main package's source files
can include one or more `//go:debug` directives at the top of the file
(preceding the `package` statement).
-Continuing the `panicnil` example, if the module or workspace is updated
-to say `go` `1.21`, the program can opt back into the old `panic(nil)`
-behavior by including this directive:
+The `godebug` lines in the previous example would be written:
+ //go:debug default=go1.21
//go:debug panicnil=1
+ //go:debug asynctimerchan=0
Starting in Go 1.21, the Go toolchain treats a `//go:debug` directive
with an unrecognized GODEBUG setting as an invalid program.
//
// The -module flag changes the module's path (the go.mod file's module line).
//
+// The -godebug=key=value flag adds a godebug key=value line,
+// replacing any existing godebug lines with the given key.
+//
+// The -dropgodebug=key flag drops any existing godebug lines
+// with the given key.
+//
// The -require=path@version and -droprequire=path flags
// add and drop a requirement on the given module path and version.
// Note that -require overrides any existing requirements on path.
// which make other go.mod adjustments as needed to satisfy
// constraints imposed by other modules.
//
+// The -go=version flag sets the expected Go language version.
+// This flag is mainly for tools that understand Go version dependencies.
+// Users should prefer 'go get go@version'.
+//
+// The -toolchain=version flag sets the Go toolchain to use.
+// This flag is mainly for tools that understand Go version dependencies.
+// Users should prefer 'go get toolchain@version'.
+//
// The -exclude=path@version and -dropexclude=path@version flags
// add and drop an exclusion for the given module path and version.
// Note that -exclude=path@version is a no-op if that exclusion already exists.
// like "v1.2.3" or a closed interval like "[v1.1.0,v1.1.9]". Note that
// -retract=version is a no-op if that retraction already exists.
//
-// The -require, -droprequire, -exclude, -dropexclude, -replace,
-// -dropreplace, -retract, and -dropretract editing flags may be repeated,
-// and the changes are applied in the order given.
-//
-// The -go=version flag sets the expected Go language version.
-//
-// The -toolchain=name flag sets the Go toolchain to use.
+// The -godebug, -dropgodebug, -require, -droprequire, -exclude, -dropexclude,
+// -replace, -dropreplace, -retract, and -dropretract editing flags may be
+// repeated, and the changes are applied in the order given.
//
// The -print flag prints the final go.mod in its text format instead of
// writing it back to go.mod.
// Module ModPath
// Go string
// Toolchain string
+// Godebug []Godebug
// Require []Require
// Exclude []Module
// Replace []Replace
// Deprecated string
// }
//
+// type Godebug struct {
+// Key string
+// Value string
+// }
+//
// type Require struct {
-// Path string
-// Version string
+// Path string
+// Version string
// Indirect bool
// }
//
// rewrite the go.mod file. The only time this flag is needed is if no other
// flags are specified, as in 'go work edit -fmt'.
//
+// The -godebug=key=value flag adds a godebug key=value line,
+// replacing any existing godebug lines with the given key.
+//
+// The -dropgodebug=key flag drops any existing godebug lines
+// with the given key.
+//
// The -use=path and -dropuse=path flags
// add and drop a use directive from the go.work file's set of module directories.
//
// type GoWork struct {
// Go string
// Toolchain string
+// Godebug []Godebug
// Use []Use
// Replace []Replace
// }
//
+// type Godebug struct {
+// Key string
+// Value string
+// }
+//
// type Use struct {
// DiskPath string
// ModulePath string
package load
import (
- "cmd/go/internal/modload"
"errors"
"fmt"
"go/build"
"sort"
"strconv"
"strings"
+
+ "cmd/go/internal/gover"
+ "cmd/go/internal/modload"
)
var ErrNotGoDebug = errors.New("not //go:debug line")
if !ok {
return "", "", fmt.Errorf("missing key=value")
}
- if strings.ContainsAny(k, " \t") {
- return "", "", fmt.Errorf("key contains space")
- }
- if strings.ContainsAny(v, " \t") {
- return "", "", fmt.Errorf("value contains space")
- }
- if strings.ContainsAny(k, ",") {
- return "", "", fmt.Errorf("key contains comma")
- }
- if strings.ContainsAny(v, ",") {
- return "", "", fmt.Errorf("value contains comma")
- }
-
- for _, info := range godebugs.All {
- if k == info.Name {
- return k, v, nil
- }
+ if err := modload.CheckGodebug("//go:debug setting", k, v); err != nil {
+ return "", "", err
}
- return "", "", fmt.Errorf("unknown //go:debug setting %q", k)
+ return k, v, nil
}
// defaultGODEBUG returns the default GODEBUG setting for the main package p.
if modload.RootMode == modload.NoRoot && p.Module != nil {
// This is go install pkg@version or go run pkg@version.
// Use the Go version from the package.
- // If there isn't one, then
+ // If there isn't one, then assume Go 1.20,
+ // the last version before GODEBUGs were introduced.
goVersion = p.Module.GoVersion
if goVersion == "" {
goVersion = "1.20"
}
}
- m := godebugForGoVersion(goVersion)
+ var m map[string]string
+ for _, g := range modload.MainModules.Godebugs() {
+ if m == nil {
+ m = make(map[string]string)
+ }
+ m[g.Key] = g.Value
+ }
for _, list := range [][]build.Directive{p.Internal.Build.Directives, directives, testDirectives, xtestDirectives} {
for _, d := range list {
k, v, err := ParseGoDebug(d.Text)
m[k] = v
}
}
+ if v, ok := m["default"]; ok {
+ delete(m, "default")
+ v = strings.TrimPrefix(v, "go")
+ if gover.IsValid(v) {
+ goVersion = v
+ }
+ }
+
+ defaults := godebugForGoVersion(goVersion)
+ if defaults != nil {
+ // Apply m on top of defaults.
+ for k, v := range m {
+ defaults[k] = v
+ }
+ m = defaults
+ }
+
var keys []string
for k := range m {
keys = append(keys, k)
The -module flag changes the module's path (the go.mod file's module line).
+The -godebug=key=value flag adds a godebug key=value line,
+replacing any existing godebug lines with the given key.
+
+The -dropgodebug=key flag drops any existing godebug lines
+with the given key.
+
The -require=path@version and -droprequire=path flags
add and drop a requirement on the given module path and version.
Note that -require overrides any existing requirements on path.
which make other go.mod adjustments as needed to satisfy
constraints imposed by other modules.
+The -go=version flag sets the expected Go language version.
+This flag is mainly for tools that understand Go version dependencies.
+Users should prefer 'go get go@version'.
+
+The -toolchain=version flag sets the Go toolchain to use.
+This flag is mainly for tools that understand Go version dependencies.
+Users should prefer 'go get toolchain@version'.
+
The -exclude=path@version and -dropexclude=path@version flags
add and drop an exclusion for the given module path and version.
Note that -exclude=path@version is a no-op if that exclusion already exists.
like "v1.2.3" or a closed interval like "[v1.1.0,v1.1.9]". Note that
-retract=version is a no-op if that retraction already exists.
-The -require, -droprequire, -exclude, -dropexclude, -replace,
--dropreplace, -retract, and -dropretract editing flags may be repeated,
-and the changes are applied in the order given.
-
-The -go=version flag sets the expected Go language version.
-
-The -toolchain=name flag sets the Go toolchain to use.
+The -godebug, -dropgodebug, -require, -droprequire, -exclude, -dropexclude,
+-replace, -dropreplace, -retract, and -dropretract editing flags may be
+repeated, and the changes are applied in the order given.
The -print flag prints the final go.mod in its text format instead of
writing it back to go.mod.
Module ModPath
Go string
Toolchain string
+ Godebug []Godebug
Require []Require
Exclude []Module
Replace []Replace
Deprecated string
}
+ type Godebug struct {
+ Key string
+ Value string
+ }
+
type Require struct {
- Path string
- Version string
+ Path string
+ Version string
Indirect bool
}
func init() {
cmdEdit.Run = runEdit // break init cycle
+ cmdEdit.Flag.Var(flagFunc(flagGodebug), "godebug", "")
+ cmdEdit.Flag.Var(flagFunc(flagDropGodebug), "dropgodebug", "")
cmdEdit.Flag.Var(flagFunc(flagRequire), "require", "")
cmdEdit.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
cmdEdit.Flag.Var(flagFunc(flagExclude), "exclude", "")
- cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
- cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
cmdEdit.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
+ cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
+ cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
cmdEdit.Flag.Var(flagFunc(flagRetract), "retract", "")
cmdEdit.Flag.Var(flagFunc(flagDropRetract), "dropretract", "")
return !modfile.MustQuote(arg)
}
+// flagGodebug implements the -godebug flag.
+func flagGodebug(arg string) {
+ key, value, ok := strings.Cut(arg, "=")
+ if !ok || strings.ContainsAny(arg, "\"`',") {
+ base.Fatalf("go: -godebug=%s: need key=value", arg)
+ }
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.AddGodebug(key, value); err != nil {
+ base.Fatalf("go: -godebug=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropGodebug implements the -dropgodebug flag.
+func flagDropGodebug(arg string) {
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.DropGodebug(arg); err != nil {
+ base.Fatalf("go: -dropgodebug=%s: %v", arg, err)
+ }
+ })
+}
+
// flagRequire implements the -require flag.
func flagRequire(arg string) {
path, version := parsePathVersion("require", arg)
"encoding/json"
"errors"
"fmt"
+ "internal/godebugs"
"internal/lazyregexp"
"io"
"os"
return gover.DefaultGoModVersion
}
+// Godebugs returns the godebug lines set on the single module, in module mode,
+// or on the go.work file in workspace mode.
+// The caller must not modify the result.
+func (mms *MainModuleSet) Godebugs() []*modfile.Godebug {
+ if inWorkspaceMode() {
+ if mms.workFile != nil {
+ return mms.workFile.Godebug
+ }
+ return nil
+ }
+ if mms != nil && len(mms.versions) == 1 {
+ f := mms.ModFile(mms.mustGetSingleMainModule())
+ if f == nil {
+ // Special case: we are outside a module, like 'go run x.go'.
+ return nil
+ }
+ return f.Godebug
+ }
+ return nil
+}
+
// Toolchain returns the toolchain set on the single module, in module mode,
// or the go.work file in workspace mode.
func (mms *MainModuleSet) Toolchain() string {
modRoots = append(modRoots, modRoot)
}
+ for _, g := range wf.Godebug {
+ if err := CheckGodebug("godebug", g.Key, g.Value); err != nil {
+ return nil, nil, err
+ }
+ }
+
return wf, modRoots, nil
}
}
}
+ if !inWorkspaceMode() {
+ ok := true
+ for _, g := range f.Godebug {
+ if err := CheckGodebug("godebug", g.Key, g.Value); err != nil {
+ errs = append(errs, fmt.Errorf("%s: %v", base.ShortPath(filepath.Dir(gomod)), err))
+ ok = false
+ }
+ }
+ if !ok {
+ continue
+ }
+ }
+
modFiles = append(modFiles, f)
mainModule := f.Module.Mod
mainModules = append(mainModules, mainModule)
}
}
}
+
return mainModules
}
}
return url + ".v" + m
}
+
+func CheckGodebug(verb, k, v string) error {
+ if strings.ContainsAny(k, " \t") {
+ return fmt.Errorf("key contains space")
+ }
+ if strings.ContainsAny(v, " \t") {
+ return fmt.Errorf("value contains space")
+ }
+ if strings.ContainsAny(k, ",") {
+ return fmt.Errorf("key contains comma")
+ }
+ if strings.ContainsAny(v, ",") {
+ return fmt.Errorf("value contains comma")
+ }
+ if k == "default" {
+ if !strings.HasPrefix(v, "go") || !gover.IsValid(v[len("go"):]) {
+ return fmt.Errorf("value for default= must be goVERSION")
+ }
+ if gover.Compare(v[len("go"):], gover.Local()) > 0 {
+ return fmt.Errorf("default=%s too new (toolchain is go%s)", v, gover.Local())
+ }
+ return nil
+ }
+ for _, info := range godebugs.All {
+ if k == info.Name {
+ return nil
+ }
+ }
+ return fmt.Errorf("unknown %s %q", verb, k)
+}
rewrite the go.mod file. The only time this flag is needed is if no other
flags are specified, as in 'go work edit -fmt'.
+The -godebug=key=value flag adds a godebug key=value line,
+replacing any existing godebug lines with the given key.
+
+The -dropgodebug=key flag drops any existing godebug lines
+with the given key.
+
The -use=path and -dropuse=path flags
add and drop a use directive from the go.work file's set of module directories.
type GoWork struct {
Go string
Toolchain string
+ Godebug []Godebug
Use []Use
Replace []Replace
}
+ type Godebug struct {
+ Key string
+ Value string
+ }
+
type Use struct {
DiskPath string
ModulePath string
func init() {
cmdEdit.Run = runEditwork // break init cycle
+ cmdEdit.Flag.Var(flagFunc(flagEditworkGodebug), "godebug", "")
+ cmdEdit.Flag.Var(flagFunc(flagEditworkDropGodebug), "dropgodebug", "")
cmdEdit.Flag.Var(flagFunc(flagEditworkUse), "use", "")
cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "")
cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
modload.WriteWorkFile(gowork, workFile)
}
+// flagEditworkGodebug implements the -godebug flag.
+func flagEditworkGodebug(arg string) {
+ key, value, ok := strings.Cut(arg, "=")
+ if !ok || strings.ContainsAny(arg, "\"`',") {
+ base.Fatalf("go: -godebug=%s: need key=value", arg)
+ }
+ workedits = append(workedits, func(f *modfile.WorkFile) {
+ if err := f.AddGodebug(key, value); err != nil {
+ base.Fatalf("go: -godebug=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagEditworkDropGodebug implements the -dropgodebug flag.
+func flagEditworkDropGodebug(arg string) {
+ workedits = append(workedits, func(f *modfile.WorkFile) {
+ if err := f.DropGodebug(arg); err != nil {
+ base.Fatalf("go: -dropgodebug=%s: %v", arg, err)
+ }
+ })
+}
+
// flagEditworkUse implements the -use flag.
func flagEditworkUse(arg string) {
workedits = append(workedits, func(f *modfile.WorkFile) {
stderr 'go: module . listed in go.work file requires go >= 1.21'
rm go.work
+# Go 1.21 go.mod with godebug default=go1.20
+rm go.work
+cp go.mod.21 go.mod
+go mod edit -godebug default=go1.20 -godebug asynctimerchan=0
+go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
+stdout panicnil=1
+stdout asynctimerchan=0
+
+# Go 1.21 go.work with godebug default=go1.20
+cp go.work.21 go.work
+go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
+! stdout panicnil # go.work wins
+stdout asynctimerchan=1 # go.work wins
+go work edit -godebug default=go1.20 -godebug asynctimerchan=0
+go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
+stdout panicnil=1
+stdout asynctimerchan=0
+rm go.work
+
+# Go 1.21 go.mod with //go:debug default=go1.20 in program
+cp go.mod.21 go.mod
+go list -tags godebug -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
+stdout panicnil=1
+stdout asynctimerchan=0
+
+# Invalid //go:debug line should be diagnosed at build.
+! go build -tags godebugbad
+stderr 'invalid //go:debug: value contains space'
+
[short] skip
# Programs in Go 1.21 work module should trigger run-time error.
panic(nil)
}
+-- godebug.go --
+//go:build godebug
+//go:debug default=go1.20
+//go:debug asynctimerchan=0
+
+package main
+
+-- godebugbad.go --
+//go:build godebugbad
+//go:debug default=go1.20 asynctimerchan=0
+
+package main
+
-- q/go.mod --
go 1.20
module q
go mod edit -module local-only -require=other-local@v1.0.0 -replace other-local@v1.0.0=./other
cmpenv go.mod go.mod.edit
+# go mod edit -godebug
+cd $WORK/g
+cp go.mod.start go.mod
+go mod edit -godebug key=value
+cmpenv go.mod go.mod.edit
+go mod edit -dropgodebug key2
+cmpenv go.mod go.mod.edit
+go mod edit -dropgodebug key
+cmpenv go.mod go.mod.start
+
-- x.go --
package x
"Replace": null,
"Retract": null
}
+-- $WORK/g/go.mod.start --
+module g
+
+go 1.10
+-- $WORK/g/go.mod.edit --
+module g
+
+go 1.10
+
+godebug key=value
go work edit -json -go 1.19 -use b -dropuse c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
cmp stdout go.work.want_json
+# go work edit -godebug
+cd $WORK/g
+cp go.work.start go.work
+go work edit -godebug key=value
+cmpenv go.work go.work.edit
+go work edit -dropgodebug key2
+cmpenv go.work go.work.edit
+go work edit -dropgodebug key
+cmpenv go.work go.work.start
+
+# go work edit -print -fmt
env GOWORK=$GOPATH/src/unformatted
go work edit -print -fmt
-cmp stdout formatted
+cmp stdout $GOPATH/src/formatted
-- m/go.mod --
module m
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
+-- $WORK/g/go.work.start --
+use g
+
+go 1.10
+-- $WORK/g/go.work.edit --
+use g
+
+go 1.10
+
+godebug key=value