Home > Back-end >  go: Identify the package where "flag redefined"
go: Identify the package where "flag redefined"

Time:09-03

If a flag is redefined, go only tells one of the places where the flag is redefined. Is there a way to identify at which place the other flag is defined?
Just to give an instance, see the below program. Here in foo.go, the first flag is defined. Say if I am trying to add a flag with the same name in package main, go panic tells me that the flag is already defined (as package foo was initialized first) and I am redefining it in package main. This is a simple example but in a large code-base where the flag could be defined in imported libraries, its hard to find out where the flag is first defined. Any way to find this out?

package main

import (
    "flag"

    "play.ground/foo"
)

func main() {
    var tf string
    flag.StringVar(&tf, "tf", "", "")
    foo.Bar()
}
-- go.mod --
module play.ground
-- foo/foo.go --
package foo

import (
    "flag"
    "fmt"
)

func init() {
    var tf string
    flag.StringVar(&tf, "tf", "", "")
}

func Bar() {
    fmt.Println("This function lives in an another file!")
}

Panic:

/tmpfs/play flag redefined: tf
panic: /tmpfs/play flag redefined: tf

goroutine 1 [running]:
flag.(*FlagSet).Var(0xc0000960c0, {0x4c3148, 0xc0000980a0}, {0x4a2642, 0x2}, {0x0, 0x0})
    /usr/local/go-faketime/src/flag/flag.go:980  0x2f9
flag.StringVar(...)
    /usr/local/go-faketime/src/flag/flag.go:851
main.main()
    /tmp/sandbox3734065722/prog.go:11  0x7d

Program exited.

Playground: https://go.dev/play/p/jsmMcnEO2hy

CodePudding user response:

As written in the comments above, there's no tooling for that.

But you can use a simple trick to find out.

You want to register a flag, but you can't because it has been registered earlier somewhere else (unknown where).

If you can register it first, then the place where it currently gets registered at will be reported in the error.

How to do that? Create a package which registers this flag:

package first

import "flag"

func init() {
    flag.String("tf", "", "")
}

And import this package first thing in your main package like this:

package main

import _ "play.gorund/first"

import (
    "flag"

    "play.ground/foo"
)

What will happen? Package init() of first is executed first, properly registering the tf flag, then foo will attempt to do that again, failing, and reporting the error.

Example output (try it on the Go Playground):

/tmpfs/play flag redefined: tf
panic: /tmpfs/play flag redefined: tf

goroutine 1 [running]:
flag.(*FlagSet).Var(0xc000062180, {0x4c3188, 0xc0000142a0}, {0x4a2642, 0x2}, {0x0, 0x0})
    /usr/local/go-faketime/src/flag/flag.go:980  0x2f9
flag.StringVar(...)
    /usr/local/go-faketime/src/flag/flag.go:851
play.ground/foo.init.0()
    /tmp/sandbox1464715048/foo/foo.go:10  0x7d

As you can see, foo.go line 10 registers the tf flag. Mission accomplished.

Note:

Many editors auto-format on save, which may involve rearranging / regrouping imports, so your play.ground/first import may be moved down, not being the first. To avoid this, don't use the auto-format feature, or choose a name for this package that will remain first after auto-formatting.

Note #2:

The Spec: Package initialization states the requirements and rules of initializing packages, and the order in which imports are processed is not specified (only thing guaranteed is that all referenced package will be initialized recursively before it can be used). This means that although current compilers process them as listed, you cannot rely on this for 100%. There's also the issue of having multiple source files even for the main package, supplying them in different order to the compiler may also change the initialization order. The spec has this as a "recommendation":

To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler.

  • Related