先明白一个点, 想要成功运行一个go程序, 有两种模式
一: 以前的默认模式,必须将项目放在gopath/src下
二:使用go mod 包管理方式, 项目可以放到任意位置,这样目录下需要有go.mod文件
下边我会分析 :
1.GO111MODULE 的三种模式,
2.将项目放在gopath/src下, 但却使用go.mod 包管理的方式
3.多文件,多目录下,go mod 包管理的使用细节
通过go env 命令 可以看到 GO111MODULE 字段
可以通过export GO111MODULE=’ ’ 来修改,当然这种命令的方式是在linux下, 若是windows平台,直接去设置环境变量即可
它有三种状态:
auto
如果在gopath/src下,但是存在go.mod文件,就采用的go mod包管理方式;
若没有go.mod文件, 采用以前默认的方式(项目必须放在gopath/src下);
若未在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/src 是/home/jt/go
为了更好的演示为什么以前默认的方式必须在gopath/src下, 这里(/home/jt/go)我们再创建一个文件夹
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), 完整的写出来其实是/home/jt/go/my_gotest2/pkg/util, 这也就是为什么必须放到gopath/src下。
2.现在我们采用go mod包管理的方式
现在的项目路径在gopath/src下(即/home/jt/go)
使用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/my_gotest2, 所以它并不依赖gopath/src(/home/jt/go), 你将项目移到其他位置, demo就会表示那个位置的绝对路径, demo可以换成任意字符, 比如你最开始用的是go mod init demo_test, 那么这里就要 import(“demo_test/pkg/util”), 可以在go.mod文件中对它进行修改。
以上就是默认方式 以及 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
我们的目标是:
- 如何在calc.go中调用pkg/util中的包函数。
- main包实现的功能如何拆分在不同文件中。
这里我们又会学习到一个新的小知识,比如这里的t1.go ,t2.go和 t3.go
只要包名一样(main包有点区别,后边说),他们的功能实现可以在不同文件中。
这个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 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
如遇任何问题欢迎评论区留言~!!