如何在Go中导入本地包?

226

我是一名Go语言新手,正在处理一个需要本地化的示例代码。

在原始的main.go导入语句中,它是这样的:

 import (
    "log"
    "net/http"
    "github.com/foo/bar/myapp/common"
    "github.com/foo/bar/myapp/routers"
)

现在我在/home/me/go/src/myapp中有commonrouters包。

所以我将导入语句转换为:

import (
    "log"
    "net/http"
    "./common"
    "./routers"
)

但是当我运行go install myapp时,会出现以下错误:

can't load package: /home/me/go/src/myapp/main.go:7:3: local import "./common" in non-local package

另外,当我在导入语句中使用 commonrouters 代替 ./common./routers 时,会出现以下问题:

myapp/main.go:7:3: cannot find package "common" in any of:
    /usr/local/go/src/common (from $GOROOT)
    /home/me/go/src/common (from $GOPATH)
myapp/main.go:8:2: cannot find package "routers" in any of:
    /usr/local/go/src/routers (from $GOROOT)
    /home/me/go/src/routers (from $GOPATH)

我该如何修复这个问题?

11
无论导入路径如何,所有的导入都是“本地”的。详细解释请参阅《如何编写 Go 代码》。 - JimB
151
@JimB 暂且不论哲学辩论,我关心的是如何解决上述问题。 - Karlom
6
我并不想做一个哲学性的陈述,我只是字面上地说所有的导入都发生在你的本地文件系统中;无论它们来自远程仓库还是其他地方,这一点永远都没有区别。不要尝试使用相对路径(有时可以工作,但并不鼓励这样做),请参考"How to Write Go Code"文档,特别是关于"代码组织"的部分。 - JimB
这个回答解决了你的问题吗?在Go模块中访问本地包(go 1.11) - Michael Freidgeim
“如何编写Go代码”将邀请您在共享的全局文件夹(例如$HOME/hello/morestrings)中创建模块,这对于其他背景来说似乎非常不自然。但是相对路径也不好。下面的“供应商”答案是一个有趣的中间步骤。在JavaScript中,它类似于使用路径别名到本地“packages”文件夹,模仿“真正”的包,但让您保留代码在应用程序内部。当您重构代码时很有用,但您还不确定模块是否完全独立于应用程序时。 - Eric Burel
当然,被接受的答案也很好用,只需要找到正确的模块名称:import "mymodulename/mynestedpackagename" - Eric Burel
11个回答

143

嗯,我找到了问题所在。基本上Go的导入路径从$HOME/go/src开始。

所以我只需要在包名前面添加myapp,也就是导入应该是:

import (
    "log"
    "net/http"
    "myapp/common"
    "myapp/routers"
)

9
使用类似 myapp 这样的项目名称是一个不好的主意,例如如果您更改项目名称,所有导入将失败。 - TomSawyer
16
替代方案是什么?Go语言不推荐使用相对导入。 - Sam Holmes
31
如果您更改项目名称,那么所有导入都会失败。项目名称很少更改。 - Damien Roche
51
从go1.11开始,您可以使用新的模块系统。只需运行go mod init <module_name>,然后导入所需的包即可,例如import "<module_name>/<pkg_name>" - shriek
我们如何在.go文件中导入github.com/dgrijalva/jwt-go?我的jwt-go文件夹位于src/github.com/dgrijalva内。 - Manik Thakur
显示剩余2条评论

76
您应该使用go mod init创建您的包,例如:go mod init github.com/my-org/my-package 现在,在my-package中,您可以有一个名为utils的子模块。
main.go
utils
 |- randstr.go

你的 randstr.go 看起来是这样的:
package utils

func RandStr(n int) string {
    // TODO: Generate random string....
    return "I am a random string"
}

然后在您的项目中的任何地方,您可以像这样使用从utils包导出(大写)的函数,例如在main.go中:
package main

import (
    "fmt"

    // "github.com/my-org/my-package" is the module name at the
    // top of your `go.mod`
    "github.com/my-org/my-package/utils"
)

func main() {
    fmt.Printf("Random string: %s\n", utils.RandStr(20))
}

1
我发现你的回答很容易理解。总的来说,可以这样说,通过简单地将所需文件复制到模块目录并按照你的简单说明操作,就可以消除使用任何版本控制系统的必要性,仍然能够使用go模块吗?如果可以通过简单地复制所有代码(包括供应商代码)来维护本地代码,我就不喜欢将版本控制系统与语言开发系统绑定在一起,并增加相关的复杂性。随着我对go的信心越来越强,我可能会集成使用带有版本支持的模块。 - Sunny
1
对我来说很有效。这应该是被采纳的答案。 - kargirwar
3
谢谢,我花了将近一天时间试图理解Go语言中的导入/导出操作,这是我找到的迄今为止最简单的答案。 - Suraj Mandal

64
如果您正在使用Go 1.5以上的版本,您可以尝试使用vendoring功能。它允许您将本地包放在vendor文件夹下,并以更短的路径导入它。在您的情况下,您可以将您的common和routers文件夹放在vendor文件夹中,这样就会像这样:
myapp/
--vendor/
----common/
----routers/
------middleware/
--main.go

然后像这样导入它

import (
    "common"
    "routers"
    "routers/middleware"
)

这个方法可行是因为Go会尝试从你的项目的vendor目录(如果它至少有一个.go文件)开始查找你的包,而不是$GOPATH/src。

顺便说一下:使用vendor功能可以做更多的事情,因为它允许你将一个包的“所有依赖代码”放在自己项目的目录中,这样它就能始终获取相同版本的依赖项来构建。这类似于npm或Python中的pip,但你需要手动复制依赖项到项目中,或者如果想要更简单的方式,可以尝试看看Daniel Theophanes的govendor。

了解更多关于这个功能的内容,请尝试查阅这里

理解并使用Vendor文件夹 by Daniel Theophanes

理解Go依赖管理 by Lucas Fernandes da Costa

希望对您或其他人有所帮助。


2
你是指 Go 语言的版本 1.15 吗? - Serhii Polishchuk
@arimaulana 在 GO 1.8 中完美运行! - Jia
VS Code 不太开心,你知道怎么让它在 vendor 文件夹中查找包吗?目前这个问题还没有得到回答:https://dev59.com/Lrroa4cB1Zd3GeqPq8Zy 编辑:把我的项目移到自己的文件夹里,现在它似乎可以直接使用了。我只需要在最上层有一个 "go.mod",而不是在供应商模块中。 - Eric Burel
这真的应该成为被接受的答案。对于导入本地包,Vendoring似乎是最好的方法。 - Trevor Sullivan
请注意,您的项目必须包含一个go.mod文件,该文件指定了足够新的Go版本以使其正常工作。根据go-build的手册页面:"默认情况下,如果存在供应商目录并且go.mod中的Go版本为1.14或更高,则go命令将表现得好像设置了-mod=vendor一样"。 - Tenders McChiken

25

导入路径是相对于您的$GOPATH$GOROOT环境变量的。例如,使用以下$GOPATH

GOPATH=/home/me/go

位于 /home/me/go/src/lib/common/home/me/go/src/lib/routers 的包分别被导入为:

import (
    "lib/common"
    "lib/routers"
)

是的,第一个例子是我的错误。 - wlredeye
工具不支持相对路径是什么意思? - wlredeye
3
使用相对导入的软件包无法通过 go install 命令进行安装。 - JimB
这是关于你修复的有关相对于当前目录导入的部分的参考。是的,所有用户导入都是相对于$GOPATH/src的。 - JimB
要导入包,您需要将相对于 $GOPATH/src 路径添加到您的项目中。 lib/ 只是一个示例。 - wlredeye
显示剩余3条评论

13

一个例子:

  1. ./greetings中,执行go mod init example.com/greetings

  2. 从另一个模块中,执行go mod edit -replace=example.com/greetings=../greetings

  3. go get example.com/greetings

来自Go教程


我正在运行Go 1.18,但它无法工作。go mod tidy不会更新依赖项。 - A. Gille

11

请按照这里的说明进行操作:https://go.dev/doc/tutorial/call-module-code

主要是需要在您的 go.mod 文件中使用 replace 命令。

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

8
本地包是Go中一个令人讨厌的问题。
对于我们公司的一些项目,我们决定根本不使用子包。
使用以下命令可以正常工作: $ glide install $ go get $ go install
对于一些项目,我们使用子包,并使用完整路径导入本地包:
import "xxxx.gitlab.xx/xxgroup/xxproject/xxsubpackage"
但是,如果我们fork这个项目,那么子包仍然会引用原始包。

5

另一种方法是使用自 go1.18 版本以来提供的 go.work 文件。

首先,本地的 common 包必须是一个模块,因此在 common 文件夹中提供一个 go.mod 文件:

module common

go 1.18

现在,您可以在目录的根目录下手动创建一个名为go.work的文件,或者调用 go work init 命令,然后执行 go work use . 和最后的 go work use ./common 命令。它看起来像这样:
go 1.18

use (
    .
    ./common
)

最后,您可以通过名称在代码中导入该包

package main

import "common"

只需记住不要提交您的go.work文件 :)


2
如果go.work文件中的路径与项目相关,为什么提交该文件是个坏主意呢? - Igor
我不确定你为什么想这样做。据我所知,在发布到自己的存储库之前,我们在工作中使用本地包。一旦它被发布了,就没有必要导入本地包,因为你可以正常地导入它。如果你有一个本地包的副本,你只需要按照设计使用go.work即可。也许我漏掉了什么? - Inuart
@karlom 在2023年,这可能应该是被接受的答案。 - KobeJohn

5

如题,文件夹的结构为:

/home/me/go/src/myapp
                └─ common
                └─ routers

前往myapp目录

cd /home/me/go/src/myapp

Do

go mod init myapp

这将创建一个 go.mod 文件,让 Go 知道模块的名称是 myapp,因此当在任何包中查看导入路径时,它会知道不要去其他地方寻找 myapp
然后您可以在代码中执行以下操作:
import (
    "log"
    "net/http"
    "myapp/common"
    "myapp/routers"
)

现在导入常用包和路由器。

4
关键在于您如何在以下命令中命名您的模块。
go mod init <TheNameGiven>

然后使用以下命令引用内部文件夹中的模块:

TheNameGiven/folder

我在这里找到了最佳解决方案... 阅读更多


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接