-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
(This might be better categorized under the linker, please edit as appropriate)
What version of Go are you using (go version)?
$ go version go version go1.21.4 darwin/arm64
Does this issue reproduce with the latest release?
Go 1.21.4 is the latest version as of this posting.
What operating system and processor architecture are you using (go env)?
go env Output
$ go env GO111MODULE='on' GOARCH='arm64' GOBIN='' GOCACHE='/Users/sethvargo/Library/Caches/go-build' GOENV='/Users/sethvargo/Library/Application Support/go/env' GOEXE='' GOEXPERIMENT='' GOFLAGS='' GOHOSTARCH='arm64' GOHOSTOS='darwin' GOINSECURE='' GOMODCACHE='/Users/sethvargo/Development/go/pkg/mod' GONOPROXY='' GONOSUMDB='' GOOS='darwin' GOPATH='/Users/sethvargo/Development/go' GOPRIVATE='' GOPROXY='https://proxy.golang.org,direct' GOROOT='/opt/homebrew/Cellar/go/1.21.4/libexec' GOSUMDB='sum.golang.org' GOTMPDIR='' GOTOOLCHAIN='auto' GOTOOLDIR='/opt/homebrew/Cellar/go/1.21.4/libexec/pkg/tool/darwin_arm64' GOVCS='' GOVERSION='go1.21.4' GCCGO='gccgo' AR='ar' CC='cc' CXX='c++' CGO_ENABLED='1' GOMOD='/Users/sethvargo/Development/project/go.mod' GOWORK='' CGO_CFLAGS='-O2 -g' CGO_CPPFLAGS='' CGO_CXXFLAGS='-O2 -g' CGO_FFLAGS='-O2 -g' CGO_LDFLAGS='-O2 -g' PKG_CONFIG='pkg-config' GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/cp/qb9vbbkx4w36f6dclng481br00gy5b/T/go-build715077767=/tmp/go-build -gno-record-gcc-switches -fno-common'
What did you do?
I tried to override a package variable (that has a default value) using ldflags, all using the same command:
go run \
-a \
-trimpath \
-ldflags="-s -w -X main.Version=1.2.3. -extldflags=-static"My expectation is that the Version value is set to "1.2.3" and therefore the program prints "1.2.3".
✅ Explicit string type
package main
import "fmt"
var Version string = "development"
func main() {
fmt.Println(Version) // correctly prints "1.2.3"
}✅ Implicit string type
package main
import "fmt"
var Version = "development"
func main() {
fmt.Println(Version) // correctly prints "1.2.3"
}✅ Explicit string type func() string
package main
import "fmt"
var Version string = func() string {
return "development"
}()
func main() {
fmt.Println(Version) // prints "1.2.3"
}✅ Implicit string type func() string
package main
import "fmt"
var Version = func() string {
return "development"
}()
func main() {
fmt.Println(Version) // prints "1.2.3"
}❌ Explicit string type complex func() string
package main
import "fmt"
var Version string = func() string {
if true {
return "development"
}
return "production"
}()
func main() {
fmt.Println(Version) // ❌ always prints "development"
}❌ String concatenation with runtime variable
package main
import "fmt"
var Version string = "devel" + os.Getenv("FOO")
func main() {
fmt.Println(Version) // ❌ always prints "devel"
}At first I thought this was because the compiler was optimizing away the function call, but surely the compiler should also optimize out the "always-true" branch.
What did you expect to see?
I expect the ldflags to override the variable.
What did you see instead?
ldflags conditionally overrides the variable, depending on other variables I don't fully understand.
What problem am I trying to solve?
I'd like to inject build information into the compiled binary, with sane fallback values if none are provided. Some of these values will be injected by the build process into the final binary, but if someone builds the binary themselves (or if they go install it), then I'd like some reasonable build information. Fortunately modern versions of Go expose this, but it seems to be incompatible with allowing the values to be overridden:
var Version string = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
if v := info.Main.Version; v != "" {
return v // e.g. "v0.0.1-alpha1.0.20231115..."
}
}
return "source"
}()I would like the default version to come from the debug package, but still provide a mechanism for builders to inject/override with their own values. With the function definition above, it's impossible for a builder to override Version. Since some of these functions could be called hundreds or thousands of times, I don't want to make this a pure function call. The best I could come up with was to move the package into internal and leverage a combination of private variables and once functions, but it's not pleasing:
package buildinfo
var version string
var Version string = sync.OnceValue(func() string {
if v := version; v != "" {
return v
}
if info, ok := debug.ReadBuildInfo(); ok {
if v := info.Main.Version; v != "" {
return v // e.g. "v0.0.1-alpha1.0.20231115..."
}
}
return "source"
})()Metadata
Metadata
Assignees
Labels
Type
Projects
Status