很多时候go程序找不到包导致无法运行的问题, 都是因为没有搞懂当前的包管理方式
一: 以前的默认模式,必须将项目放在gopath/src下
二:使用go mod 包管理方式, 项目可以放到任意位置,这样目录下需要有go.mod文件

如果你是初学者, 建议看完, 学懂包管理方式是深入学习go语言的基础

本文主要从以下三点展开分析 :
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

我们的目标是:

  1. 如何在calc.go中调用pkg/util中的包函数。
  2. 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

如遇任何问题欢迎评论区留言~!!