【问题标题】:Get Name of Current Module in Go在 Go 中获取当前模块的名称
【发布时间】:2021-11-26 01:27:59
【问题描述】:

我正在尝试为我正在编写的 HTTP 处理程序自动创建命名记录器,并在其中传递了一个函数(指针)。

我正在使用this question 中提到的代码来获取函数的名称:

package utils

import (
    "reflect"
    "runtime"
)

func GetFunctionName(fn interface{}) string {
    value := reflect.ValueOf(fn)
    ptr := value.Pointer()
    ffp := runtime.FuncForPC(ptr)

    return ffp.Name()
}

我在我的main 函数中使用它来尝试如下:

package main

import (
    "github.com/naftulikay/golang-webapp/experiments/functionname/long"
    "github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path"
    "github.com/naftulikay/golang-webapp/experiments/functionname/utils"
    "log"
)

type Empty struct{}

func main() {
    a := long.HandlerA
    b := path.HandlerB
    c := path.HandlerC

    log.Printf("long.HandlerA: %s", utils.GetFunctionName(a))
    log.Printf("long.nested.path.HandlerB: %s", utils.GetFunctionName(b))
    log.Printf("long.nested.path.HandlerC: %s", utils.GetFunctionName(c))
}

我看到这样的输出:

github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA

这没关系,但我想要long.HandlerAlong.nested.path.HandlerB 等输出。

如果我能得到 Go 模块名称 (github.com/naftulikay/golang-webapp/experiments/functionname),然后我可以使用 strings.Replace 删除模块名称以到达 long/nested/path.HandlerB,然后 strings.Replace/ 替换为 . 到最终达到我想要的值,即long.nested.path.HandlerB

第一个问题是:我能否比runtime.FuncForPC(reflect.ValueOf(fn).Pointer()) 更好地获取函数的合格路径?

如果答案是否定的,有没有办法使用 runtimereflect 获取当前 Go 模块名称,以便我可以将 runtime.FuncForPC 的输出转换为我需要的?

再一次,我得到如下值:

  • github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA
  • github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerB
  • github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerC

我想获得如下值:

  • long.HandlerA
  • long.nested.path.HandlerB
  • long.nested.path.HandlerC

编辑: 看来 Go 没有模块的运行时表示,这没关系,如果我可以在编译时这样做,那也很好。我已经看过 codegen 文档,但我很难弄清楚如何编写可以从 go generate 使用的自定义 codegen。

【问题讨论】:

  • @gopher 有没有办法在构建时进行代码生成以解析 go.mod 并将结果嵌入到我的程序中?
  • 我非常确定您需要“然后用字符串替换字符串。替换为 / 。”因为对于大多数 Go 开发人员来说,这看起来是非常错误的。这就是像 Java 这样的语言组织包的方式,但 不是 Go 组织它们的方式。 Go 使用斜线。因此,除非您有理由需要将它们用点分隔,否则您应该真正考虑让它们像往常一样以斜线分隔。
  • @Adrian 我正在为我的各种路线生成记录器名称,例如app.routes.login.LoginHandler。斜线似乎有效,但斜线和圆点的混合让我感到困惑。我不需要文件名或行,只需要从函数指针中提取该值。
  • 这很公平,这是您的设计决定,我只是想建议您谨慎,因为您似乎感到困惑的是过去使用 Go 的开发人员的正常情况(反之亦然)。 YMMV。
  • @gopher 没有吗? debug.ReadBuildInfo()呢?

标签: go reflection


【解决方案1】:

模块信息包含在可执行二进制文件中,可以使用debug.ReadBuildInfo()函数获取(唯一的要求是必须使用模块支持构建可执行文件,但这是当前版本中的默认设置,很可能仅在未来的版本中)。

BuildInfo.Path是当前模块的路径。

假设您有以下go.mod 文件:

module example.com/foo

读取构建信息的示例:

bi, ok := debug.ReadBuildInfo()
if !ok {
    log.Printf("Failed to read build info")
    return
}

fmt.Println(bi.Path)

这将输出(在Go Playground 上尝试):

example.com/foo

查看相关:Golang - How to display modules version from inside of code

【讨论】:

  • 但是,为什么不改用bi.Main.Path(为了更清楚)?
  • @blackgreen bi.Path 是主模块的路径,与bi.Main.Path 相同。两者的工作原理相同,前者更短。
  • 很棒的解决方案,我唯一注意到的是ok 在使用go test 运行时是false,大概是因为那里没有使用模块。不知道在这种情况下该怎么做,但我可能会退回到加载go.mod,因为当test 运行时,源代码是存在的。
  • @NaftuliKay 你这样做肯定有问题,因为读取构建信息也适用于测试。同样,这个解决方案只需要在模块模式下使用 go 工具,没有别的。
  • 这对我来说可能是与 IDE 相关的事情,Goland 测试运行器做了一些魔法,过去曾产生过其他问题。
【解决方案2】:

如果您的目标是只是在您的程序中提供可用模块的名称,并且如果您可以在链接时设置此值,那么您可以使用 -ldflags 构建选项.

您可以在模块目录中使用go list -m 获取模块的名称。

您可以将所有内容放在 Makefile 或 shell 脚本中:

MOD_NAME=$(go list -m)
go build -ldflags="-X 'main.MODNAME=$MOD_NAME'" -o main ./...

main.go 看起来像:

package main

import "fmt"

var MODNAME string

func main() {
    fmt.Println(MODNAME) // example.com
}

使用提到的"golang.org/x/mod/modfile" 包,示例可能如下所示:

package main

import (
    "fmt"
    "golang.org/x/mod/modfile"
    _ "embed"
)

//go:embed go.mod
var gomod []byte

func main() {
    f, err := modfile.Parse("go.mod", gomod, nil)
    if err != nil {
        panic(err)
    }
    fmt.Println(f.Module.Mod.Path) // example.com
}

但是,在您的用例中嵌入整个 go.mod 文件似乎有点过头了。当然,您也可以在运行时打开该文件,但这意味着您必须将go.mod 与您的可执行文件一起部署。使用 -ldflags 设置模块名称更直接 IMO。

【讨论】:

  • 好的,第二个解决方案是优秀的,可能正是我将使用的。我尝试编写go generate command,但无法使构建自动化工作。这绝对是下一个最好的事情,我将把它带到我的constants 包中的init 函数中,并设置一个包变量或使用一个函数来防止修改。非常感谢!
  • @NaftuliKay 很好,为了清楚起见,我建议您实际嵌入go.mod 文件。我举这个例子主要是为了学习。我的建议是使用-ldflags。 Icza 的回答还提供了一个很好的替代方案,可以在运行时获取信息而不更改构建过程
猜你喜欢
  • 1970-01-01
  • 2021-06-10
  • 2017-12-20
  • 1970-01-01
  • 2011-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-06
相关资源
最近更新 更多