最佳体验请使用Chrome67及以上版本、火狐、Edge、Safari浏览器 ×

创建银行
创建开票

    go语言包管理方式(go mod)

    编者:杨博@勾股弦数据 阅读89 来源: CSDN 2024/09/18 01:27:47 文章 外链 公开

    很多时候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 utilimport("fmt")func Test() {	//注意首字母大写, 不然无法调用,大写表示允许被调用
            fmt.Println("I'm pkg/util/Test()")}

    现在我们需要在main.go 中调用这个Test() 函数
    main.go 内容 , go run main.go 即可看到结果

    package mainimport("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 utilimport("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 mainimport(
            "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_gotest2go 1.18require github.com/cnwyt/my_gotest v0.0.0replace 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 utilimport("time")func UnixTime() int64 {
            return time.Now().Unix()}

    此时这个包名还是util, 刚才我们引用的my_gotest中的包也是util,那么怎么区别呢?
    去看看my_gotest2中go.mod的内容
    可以看到我们的项目名为my_gotest ,将main.go的内容更改如下 ,即可解决包名冲突问题

    package mainimport(
            "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 mainimport (
      "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 calcimport(
            "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 utilimport("fmt")func Say2() {
            fmt.Println("I'm t2")}t3.go
    package utilimport("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 mainimport(
            "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 utilgo 1.18

    一般来说,xixi.go的包名应该和目录名一致, 但我在里面写的是mm

    xixi.go内容

    package mmimport 	"fmt"func TT() {
    	fmt.Println("TT")}

    如何在test.go中调用它呢
    test.go内容

    package gagaimport (
    	"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.1replace "util" => ./ttgo 1.18

    main.go内容如下

    package mainimport (
    	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

    vscode中, 点击install all 总是超时失败

    当你安装好go时,默认go的代理环境是这样的配置
    go env -w GOPROXY=https://goproxy.io,direct

    需要改成如下方式, 直接在控制台执行命令后, 重新打开vscode即可。
    go env -w GOPROXY=https://proxy.golang.com.cn,direct




    声明:本网站部分内容来源于网络,版权归原权利人所有,其观点不代表本网站立场;本网站视频或图片制作权归当前商户及其作者,涉及未经授权的制作均须标记“样稿”。如内容侵犯了您相关权利,请及时通过邮箱service@ichub.com与我们联系。
     0  0

    微信扫一扫:分享

    微信里点“+”,扫一扫二维码

    便可将本文分享至朋友圈。

      
    
    
    分享
     0
      验证