go: Identify the package where "flag redefined"

huangapple go评论78阅读模式
英文:

go: Identify the package where "flag redefined"

问题

如果一个标志被重新定义,Go 只会告诉你其中一个重新定义的位置。有没有办法确定另一个标志是在哪个位置定义的呢?

举个例子,看下面的程序。在 foo.go 中定义了第一个标志。假设我试图在 main 包中添加一个同名的标志,Go 会告诉我该标志已经被定义过了(因为 foo 包先被初始化),而我正在 main 包中重新定义它。

这只是一个简单的例子,但在一个大型的代码库中,标志可能在导入的库中定义,很难找出标志最初是在哪里定义的。有没有办法找到这个信息呢?

恐慌信息:

/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

英文:

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

答案1

得分: 3

如上面的评论所述,没有相关的工具。

但是你可以使用一个简单的技巧来找出。

你想注册一个标志,但是由于它已经在其他地方(未知位置)被注册过了,所以你无法注册。

如果你能先注册它,那么当前注册它的位置将会在错误中报告出来。

怎么做呢?创建一个注册这个标志的包:

package first

import "flag"

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

然后在你的 main 包中首先导入这个包,像这样:

package main

import _ "play.gorund/first"

import (
    "flag"

    "play.ground/foo"
)

会发生什么?first 包的 init() 函数首先被执行,正确地注册了 tf 标志,然后 foo 将尝试再次注册,失败,并报告错误。

示例输出(在 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

如你所见,foo.go 的第 10 行注册了 tf 标志。任务完成。

注意:

许多编辑器在保存时会自动格式化代码,这可能涉及重新排列/重新分组导入语句,所以你的 play.ground/first 导入语句可能会被移动到下面,不再是第一个。为了避免这种情况,不要使用自动格式化功能,或者选择一个在自动格式化后仍然保持第一个位置的包名。

注意2:

规范:包初始化 中规定了初始化包的要求和规则,导入的顺序没有指定(唯一保证的是所有引用的包在使用之前都会被递归初始化)。这意味着尽管当前的编译器按照列表中的顺序处理它们,但你不能百分之百地依赖这一点。还有一个问题是即使对于 main 包,有多个源文件,以不同的顺序将它们提供给编译器也可能改变初始化顺序。规范中对此有一个“建议”:

为了确保可重现的初始化行为,构建系统鼓励按照词法文件名顺序将属于同一个包的多个文件呈现给编译器。

英文:

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.

huangapple
  • 本文由 发表于 2022年9月2日 14:41:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/73578696.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定