赞
踩
很多时候go程序找不到包导致无法运行的问题, 都是因为没有搞懂当前的包管理方式
一: 以前的默认模式,必须将项目放在gopath/src下
二:使用go mod 包管理方式, 项目可以放到任意位置,这样目录下需要有go.mod文件
如果你是初学者, 建议看完, 学懂包管理方式是深入学习go语言的基础
在文章最后会介绍在vscode中当弹出某个提示包不存在, 但点击install all总是会超时失败的问题。
本文主要从以下三点展开分析 :
1.GO111MODULE 的三种模式,
2.将项目放在gopath/src下,但使用go.mod 包管理的方式
3.多文件,多目录下,go mod 包管理的使用细节
通过go env 命令 可以看到 GO111MODULE 字段
可以通过export GO111MODULE=’ ’ 来修改,当然这种命令的方式是在linux下, 若是windows平台,直接去设置环境变量即可
它有三种状态:
auto
如果在gopath/src下,但是存在go.mod文件,就采用的go mod包管理方式;
在gopath/src下,但没有go.mod文件, 采用以前默认的方式;
若未在gopath/src下, 自然是采用的go mod包管理方式(前提是需要go.mod文件存在)
on, 不管在不在gopath/src下,都采用的go mod包管理方式
off,就是以前的默认方式(这时候项目必须放在gopath/src下),若需要引用外部包文件,使用go get命令下载下来。
比如在一个.go文件中,require (“github.com/gin-gonic/gin”)
那么使用go get github.com/gin-gonic/gin ,并且这个下载下来的资源会放在gopath/src下, 而使用go mod包管理的方式,下载下来的资源会放在gopath/pkg下,后边会用测试案例详细介绍如何操作。
小细节:
采用go mod 包管理方式, 虽然不会去gopath/src下找资源, 但是会去gopath/pkg下找资源, 同时还会去goroot/src下寻找(回忆一下, 最常用的fmt.Println(), fmt等等那些包就在那里);
采用以前的默认方式, 就会去gopath/src, 以及goroot/src下寻找,不管是哪种方式都需要去goroot/src,因为fmt等包是在安装go的时候就下载好的资源。
实例演示:
先用go env 查看go的环境变量,重点记住gopath路径
1:先展示以前的默认方式,
使用export GO111MODULE=“off”, 表示关闭go mod包管理方式,采用默认的模式,那么我们的工程就必须放在gopath/src下。
我的gopath 是/home/jt/go
为了更好的演示为什么以前默认的方式必须在gopath/src下, 这里(/home/jt/go/src)我们再创建一个文件夹
mkdir my_gotest2
cd my_gotest2
touch main.go
mkdir -p pkg/util
cd pkg/util
vim test.go , 内容如下
package util
import("fmt")
func Test() { //注意首字母大写, 不然无法调用,大写表示允许被调用
fmt.Println("I'm pkg/util/Test()")
}
现在我们需要在main.go 中调用这个Test() 函数
main.go 内容 , go run main.go 即可看到结果
package main
import("my_gotest2/pkg/util")
func main() {
util.Test()
}
这里主要分析import(“my_gotest2/pkg/util”) , 对于以前的默认方式,也就是项目必须放到gopath/src下的原因, 其实它会在my_gotest2/pkg/util前面自动加上gopath/src路径(我的是/home/jt/go/src), 完整的写出来其实是/home/jt/go/src/my_gotest2/pkg/util, 这也就是为什么必须放到gopath/src下。
2.现在我们采用go mod包管理的方式
现在的项目路径在gopath/src下(即/home/jt/go/src)
使用go mod init demo 生成 一个go.mod 文件 (demo名字是自己取的,什么都是可以,记住它)
使用export GO111MODULE=“auto” , 这里为什么使用auto而不使用on,因为想给大家分析在gopath/src下却使用auto模式,它会使用go mod包管理方式,还是采用以前的默认方式? 答案是如果存在go.mod文件就会用go mod包管理方式, 如果没有go.mod 就使用以前默认方法,当然前提是放在gopath/src路径下。
这种情况下我们如何才能在main.go中调用pkg/util下的Test()函数呢?
直接go run main.go
main.go:3:8: package my_gotest2/pkg/util is not in GOROOT (/usr/lib/go-1.18/src/my_gotest2/pkg/util)
可以看到以上报错, 其实很细节,为什么没去gopath/src下找呢, 因为我们此时是go mod包管理方式,那么又为什么要去GOROOT(goroot/src)下找呢? 因为像fmt那些包都在那,所以不管是否开启go mod包管理模式都会去goroot/src找。
正确的方式是将刚才的import(“my_gotest2/pkg/util”) 换成 import(“demo/pkg/util”)
这个demo是我们上边go mod init demo 生成的项目模块名称,可以在go.mod中看到。
再次go run main.go 即可成功
注意到我们最开始使用的go mod init demo 的重要性没有, demo用来替代了当下的绝对路径,在这里其实demo表示的是/home/jt/go/src/my_gotest2, 所以它并不依赖gopath/src(/home/jt/go/src), 你将项目移到其他位置, demo就会表示那个位置的绝对路径, demo可以换成任意字符, 比如你最开始用的是go mod init demo_test, 那么这里就要 import(“demo_test/pkg/util”), 可以在go.mod文件中对它(demo名称)进行修改。
以上就是默认方式 以及 go mod包管理方式的简单使用
拓展1:go mod包管理方式,如何调用不同工程中的包
以上的文件都是在同一个工程下,接下来我们创建两个工程, 我直接给出方法,以及如何写代码,建议自行放到电脑上运行查看加深理解
在任意位置创建 两个文件夹
我当前的工作路径是/home/jt
mkdir my_gotest my_gotest2
目标是在my_gotest2工程中调用my_gotest中的util包,使用SayHello函数
cd my_gotest
mkdir -p pkg/util
vim hello.go
package util
import("fmt")
func SayHello() {
fmt.Println("hello -- from my_gotest")
}
回到my_gotest目录
go mod init github.com/cnwyt/my_gotest //这里为什么这样命名,方便你后续可以把包提交到github上供他人调用。
就像我们上边说的go mod init 后边的名字是自己取的
这里的github.com/cnwyt/my_gotest就代表的是my_gotest文件夹的绝对路径
相当于 /home/jt/my_gotest
现在我们去到my_gotest2文件夹
go mod init my_gotest2
现在我们要调用my_gotest中的util包
touch main.go (my_gotest2目录 下)
package main
import(
"fmt"
"github.com/cnwyt/my_gotest/pkg/util"
)
func main() {
fmt.Println("Hello, my_gotest2")
util.SayHello();
}
显然 github.com/cnwyt/my_gotest/pkg/util 并不是github官网上的,而是我们本地的,所以我们需要在go.mod((/home/jt/my_gotest2))中修改一下
module my_gotest2
go 1.18
require github.com/cnwyt/my_gotest v0.0.0
replace github.com/cnwyt/my_gotest => /home/jt/my_gotest //这就是replace的用处,用于替换
replace不仅可以这样做,比如你在以前在github上引用的包,但时间长了,可能作者改变了它的位置。
举例:
replace github.com/gin-gonic/gin v1.0.1 => github.com/piannide/gin v1.0.2 //当然版本号只是举例,不一定是这个版本
其实它的意思就是, 去把新位置(github.com/piannide/gin)的包下载下来放到了老位置(gopath/pkg/github/gin-gonic)下,这样就可以继续使用了,而不用做太大改动
回到我们的目标
此时我们 go run main.go 就可以发现成功调用了
这里还有一个小细节,比如包的名字重复了:
在my_gotest2下
mkdir pkg/util
vim datetime.go
package util
import("time")
func UnixTime() int64 {
return time.Now().Unix()
}
此时这个包名还是util, 刚才我们引用的my_gotest中的包也是util,那么怎么区别呢?
去看看my_gotest2中go.mod的内容
可以看到我们的项目名为my_gotest ,将main.go的内容更改如下 ,即可解决包名冲突问题
package main
import(
"fmt"
"my_gotest2/pkg/util"
//因为是go mod包管理方式,其实my_gotest2就相当于替换了当前工程的绝对路径(/home/jt/my_gotest2)
util2"github.com/cnwyt/my_gotest/pkg/util"
)
func main() {
fmt.Println("Hello, my_gotest2")
t := util.UnixTime()
fmt.Println("timestap:", t)
util2.SayHello();
}
以上内容都是对于本地包的引用, 那么如果想引用github上的包怎么操作?
我们以github.com/gin-gonic/gin 为例子
我们在main.go 中 (任意地方),当然因为是go mod包管理方式, 必须先通过go mod init ”任意” 生成go.mod 文件,把main.go 写好后, 使用 go mod tidy 它会自动去查找工程下所有.go文件引用的外部资源,并自动下载下来, 下载下来后可以去 gopath/pkg/mod/github.com/ 中看到 gin-gonic。
对比以前的默认方式, 以前是使用 go get github.com/gin-gonic/gin, 然后这个资源会下载到gopath/src中, 当然go mod包管理方式也是可以使用go get命令的。
此时就可以正常go run main.go 运行文件了
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
拓展2
go mod的方式如何在多文件中应用
比如工程结构如下。
├── calc
│ └── calc.go
├── go.mod
├── main.go
├── main_son.go
└── pkg
└── util
├── t1.go
├── t2.go
└── t3.go
我们的目标是:
这个go.mod 是通过go mod init demo 生成的
cala.go
package calc
import(
"demo/pkg/util" //主要就是学习它怎么写
"fmt"
)
func Add(x, y int) int {
fmt.Println("我是calc, 我在这里调用了Say3()")
util.Say3()
return x + y
}
t1.go, t2.go, t3.go 内容
package util t1.go import("fmt") func Say1() { fmt.Println("I'm t1") } t2.go package util import("fmt") func Say2() { fmt.Println("I'm t2") } t3.go package util import("fmt") func Say3() { fmt.Println("I'm t3, I will user t1 and t2!!") Say1() Say2() }
至此第一个目标实现
由上可见,对于普通包,这里是util包, 可以直接引用同包名下其他文件的函数,而main包有点区别
main. go 和 main_son.go 都数据main包, 我们去看一下他们的实现
package main import( "fmt" "demo/pkg/util" "demo/calc" ) func main() { fmt.Println("test_ main") fmt.Println("--------------") util.Say1() fmt.Println("---------------") util.Say2() fmt.Println("-------------") util.Say3() fmt.Println("--------------") sum := calc.Add(1, 2) fmt.Println(sum) fmt.Println("---------------") //test() }
可以看到我把test()注释了,因为他是在main_son.go中实现的,在这种情况下我们使用
go run main.go 程序是可以正常执行的, 但当你打开注释,会提示
//使用 go run main.go
# command-line-arguments
./main.go:23:2: undefined: test
此刻的正确方式是将main_son.go 放到命令行参数中,如
go run main.go main_son.go //此刻即可正常执行
包名和目录名不相同,怎么调用
go.mod内容
module util
go 1.18
一般来说,xixi.go的包名应该和目录名一致, 但我在里面写的是mm
xixi.go内容
package mm
import "fmt"
func TT() {
fmt.Println("TT")
}
如何在test.go中调用它呢
test.go内容
package gaga
import (
"fmt"
mm "util/pkg"
)
func Test() {
fmt.Println("test")
mm.TT()
}
超级重点:引入包的时候,默认的就是把路径的最后一截路径名当作包名,如果不加上这个mm,就会去找pkg包(因为引入的是"util/pkg"),但并没有这个包,就会报错,同时还需要注意,pkg这个目录下不能出现两种包名,也就是说如下图,lolo.go和xixi.go属于同一个路径下,它们的包名必须是一致的(不一定非得是pkg,比如我这里写的就是mm)
上边取的别名不一定是mm,你可以这样想,不管你取什么,它都是为了去顶替这个路径下的包(刚才已经说了一个路径下(嵌套的路径是单独的,比如这里的pkg相对于tt路径来说就是嵌套的)只能存在一种包名),所以至于这个包名是啥已经不重要了),但如果你不写别名,并且包名不是pkg那么就会出错,因为它默认会去找 以路径的最后一截
命名的包(这里对应pkg)
go.mod路径下还有go.mod文件,怎么去调用?
嵌套了go.mod的工程已经不能使用以前普通的方法了。
这种情况和不同工程下包的相互调用一样。
接着上一个问题(包名和目录名不相同,怎么调用), 可以发现上一个问题就是这里的一个子集。
最外层go.mod内容如下
module demo
require "util" v0.0.1
replace "util" => ./tt
go 1.18
main.go内容如下
package main
import (
huhu"util"
mm "util/pkg"
)
func main() {
huhu.Test()
mm.TT()
}
注意这个huhu别名,还记得上边说的吗,不管取啥都可以,但如果不取,则test.go里面必须写成package util
如果test.go里面写的是
package util
那这里就根本不需要有huhu这个别名(main函数里面的调用就改成util.Test())
但test.go里面写的是
package gaga
所以需要加上这个别名,因为它默认会去找util包,但并没有
go build 又是什么?
主要用于编译代码,输出可执行文件,比如将源码打包成可执行文件部署线上服务
//如果是普通包(非main包), 只做检查, 不产生可执行文件
//如果是main包,生成可执行文件, 默认生成的可执行文件名为项目名(go mod里面)
//命令: go build main.go
// -o 参数指定可执行文件名称
//交叉编译
在linux生成window需要的 exe文件
GOOS=windows GOARCH=amd64 go build -o demo.exe mian.go
反之
GOOS=linux GOARCH=amd64 go build -o demo mian.go
当你安装好go时,默认go的代理环境是这样的配置
go env -w GOPROXY=https://goproxy.io,direct
需要改成如下方式, 直接在控制台执行命令后, 重新打开vscode即可。
go env -w GOPROXY=https://proxy.golang.com.cn,direct
如遇任何问题欢迎评论区留言~!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。