Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It should be possible to use command line parameters extracted via flag.Parse within the init()-ization function of all packages #39093

Closed
GreatSnoopy opened this issue May 15, 2020 · 5 comments

Comments

@GreatSnoopy
Copy link

What version of Go are you using (go version)?

go version go1.13.6 darwin/amd64
as well as 
go version go1.13.8 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/someuser/.cache/go-build"
GOENV="/home/someuser/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/someuser/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/go1.13.8"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/go1.13.8/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build174629696=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Since the changes that are most extensively discussed here #31859
it is no longer possible to use parameters that come from the cli parsing within the init() function of dependent packages because it breaks the testing package.
It seems that it is advised to only call flag.Parse from within the main function of the application.
Before, we were using a singleton object created in a dedicated configuration package. This package parsed the cli parameters as well as some environment variables within its own init() function and created the singleton config object. Most other packages from the application would import the configuration package and access the config singleton. This worked well because the configuration package was first to execute and the other packages could in turn execute whatever complex initialization code they would need using parameters from the cli that were already available.
Now, this pattern is no longer possible, basically there is no straightforward way to use the cli parameters from within the init function of application packages. This seems an artificial limitation since if one would use environment variables for configuration, this would be possible. But not with cli parameters.

What did you expect to see?

Be able to use flag.Parse within init()

What did you see instead?

#31859

Discussion

Is this pattern I described not recommended in Go?
I feel that this is an artificial limitation, especially if one considers that the above pattern could be easily implemented with environment variables but not with cli flags.
Wouldn't be possible to address this limitation with a better implementation in the flag package that would allow multiple calls to flag.Parse() ? Or maybe separate the testing flags parsing out so it does not interfere with the application's own call to flag.Parse in an init() function?

@seankhliao
Copy link
Member

#33190

@ianlancetaylor
Copy link
Contributor

The pattern you describe is not recommended in Go. It can only work if the package P1 that calls flag.Parse from init is known to import all other packages that define flags. If the main package imports some package P2 that defines a flag, but P2 is not imported, directly or indirectly, by P1, then there is no way to know whether the flags defined by P2 will be seen when parsing flags.

This is not an artificial limitation. It's inherent in the design of the flag package.

Multiple calls to Parse would not help. At the time of the first call to Parse we can't know whether there will be subsequent calls, so we can't know whether to report an unknown flag or not.

We aren't going to change the flag package or the testing package. For your use case, consider using a different flag package.

@GreatSnoopy
Copy link
Author

GreatSnoopy commented May 15, 2020

First I would like to mention that I don't want to go against the current or against the best technical model. I don't want to challenge anything. That being said:

Question/Problem:

How would it then be correct to have packages use in their init() functions values that come from the cli? This is a simple question.
By comparison, one is able to use env vars within all init() without breaking anything, why wouldn't this be possible with cli flags? ALL the flags do exist at the moment of the executable invocation / loading by the OS, just like env vars, why can't we rely on them? Doesn't this look like an artificial limitation that we cannot use equally available resources (user provided config values) in the same way?

What I was (maybe naively) suggesting was that each package could register its set of flags it would be interested in and call Parse on its own to discover the values of the flags registered so far. This would not rely on the pattern of imports you describe above since each interested package could not only register flags, but try to fetch those already registered. Obviously, the Parse caller is interested in the flags it just defined moments before, not in the flags that any different package may define for its own use later.

Is there another way for the question above except using something totally different than Parse?

Thanks for your patience, hope this explains it a tad better.

@ianlancetaylor
Copy link
Contributor

With the syntax used by the standard library flags package, you can't parse flags if you don't know all the flags, because when you see -z arg you don't know whether arg is a value for -z or whether -z is a boolean flag that doesn't take a value.

You can't know all the flags in an init function. It's not possible in Go. So with the syntax used by the standard library flags package, you can't parse flags in an init function.

Of course, an init function can look at the command line, by using os.Args. So if you are able to unambiguously parse the command line without knowing all the flag definitions, then any individual package can look at os.Args and extract whatever information it likes.

There's nothing with doing things that way if you like. It's just not how the standard library flags package works, and it's not possible for the standard library flags package to work that way.

@GreatSnoopy
Copy link
Author

Ok, thanks for the explanation. It does make sense, even if I don't like the limitation per se :)
Thank you very much!

@golang golang locked and limited conversation to collaborators May 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants