知名网站建设定制史上最全的Go语言模块(Module)管理详解(基于Go1.19)

  

目录

      一直到1.10,都是使用GOPATH知名网站建设定制设置模块搜索路径,但从1.11开始,知名网站建设定制引入了新的Go知名网站建设定制模块管理机制(go modules),知名网站建设定制不过一直到1.15,知名网站建设定制默认的模块管理方式仍然是GOPATH,直到Go1.16开始,知名网站建设定制将默认的模块管理方式改成了go modules,知名网站建设定制在这种工作模式下,知名网站建设定制每一个模块都必须使用go.mod知名网站建设定制文件指定模块的位置。

        go modules一经推出,知名网站建设定制就饱受诟病。知名网站建设定制最大的问题是如果go.mod知名网站建设定制文件中使用了绝对路径知名网站建设定制指定了模块路径,如果在git push知名网站建设定制时将每个模块的go.mod知名网站建设定制文件都上传到了服务器,那么在git pull知名网站建设定制到其他机器,由于路径可能不一样,如果进行git push操作的是macOS或Linux,而进行git pull操作的是Windows,那路径肯定是不一样的。所以每一次git pull时,都要修改模块的路径,尤其是当模块很多时,简直是一场噩梦。当然,你可以选择不上传go.mod文件,但go modules机制要求每一个模块的根目录必须有一个go.mod文件,所以即使不上传go.mod文件,你仍然需要为每一个模块创建新的go.mod文件。

        为了解决go modules的这个饱受诟病的问题,从Go1.18开始,推出了工作区的概念,基本思路就是,每一个模块仍然需要一个go.mod文件,但这个文件主要用于指定模块名和go的版本,并不需要指定引用模块的路径,而所有模块的路径统一由工程根目录的go.work文件指定。go.work文件的语法与go.mod文件的语法类似,但可以通过use指定模块的路径。

        如果go.work文件使用了相对路径作为模块路径,那么上传还是不上传go.work文件都无关紧要,如果go.work文件中使用了绝对路径,并不需要上传go.work文件。如果要添加新模块,或删除模块,只需要在每一台机器的工程根目录中的go.work文件中添加或上传该目录的路径即可。

下面给出一个完整的案例,看一下Go工作区模式是怎样工作的。

1. 使用go modules方式管理Go模块

        这一节先使用传统的go modules方式管理模块(也就是需要在go.mod文件中指定模块的路径),然后再使用工作区方式管理模块,读者可以对比两者的差异。

        本例包含两个模块(ModuleA和ModuleB)和一个调用程序(Demo),其中ModuleA会调用ModuleB中的函数,Demo会同时调用ModuleA模块和ModuleB模块中的函数,也就是说,在这个例子中,ModuleB会被应用两次。

        本案例在macOS下操作,Linux和Windows的操作类似,只是涉及到路径的系统特有属性时,需要根据当前OS进行变更(这些请读者自行完成)。请读者按下面步骤完成:

        注意:路径请换成读者机器上对应的路径

步骤1:创建ModuleA

  1. cd /Volumes/data
  2. mkdir examples
  3. cd examples
  4. mkdir ModuleA
  5. cd ModuleA
  6. go mod init github.com/unitymarvel/ModuleA
  7. touch A.go
  8. cd ..

步骤2:创建ModuleB

  1. mkdir ModuleB
  2. cd ModuleB
  3. go mod init github.com/unitymarvel/ModuleB
  4. touch B.go
  5. cd ..

步骤3:创建Demo

  1. mkdir Demo
  2. cd Demo
  3. go mod init github.com/unitymarvel/Demo
  4. touch Demo.go
  5. cd ..

        经过上面3步,会在examples目录中创建三个子目录:ModuleA、ModuleB和Demo,并且每一个子目录中都有两个文件,一个是go文件,一个是go.mod文件,使用tree命令会看到examples目录中的完整结构,如下图所示:

步骤4:编写B.go的代码

  1. package ModuleB
  2. func Greet(name string) string{
  3. return "Hello " + name
  4. }

步骤5:编写A.go的代码

  1. package ModuleA
  2. import (
  3. "fmt"
  4. "github.com/unitymarvel/ModuleB"
  5. )
  6. func OutputMsg(str string) {
  7. fmt.Println(ModuleB.Greet(str))
  8. }

步骤6:编写Demo.go的代码

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/unitymarvel/ModuleA"
  5. "github.com/unitymarvel/ModuleB"
  6. )
  7. func main() {
  8. ModuleA.OutputMsg("李宁")
  9. fmt.Println(ModuleB.Greet("Bill"))
  10. }

        很明显,在A.go文件中引用了ModuleB模块,而在Demo.go文件中同时引用了ModuleA模块和ModuleB模块。在终端进入demo目录,并执行go run Demo.go,肯定不会执行的,会抛出如下的异常:
         错误信息的基本含义就是github.com/unitymarvel/ModuleA和github.com/unitymarvel/ModuleB这两个地址根本没有对应的模块,所以就要使用本地模块,但Go编译器也不知道本地模块在什么位置,所以就会运行失败。
        现在要让Go编译器知道本地模块的位置,通常在go.mod文件中使用replace指令将域名映射到本地的模块目录,所以应该按下面的步骤修改对应模块的go.mod文件。

步骤7:修改ModuleA/go.mod文件

修改后的结果如下:

  1. module github.com/unitymarvel/ModuleA
  2. go 1.19
  3. require github.com/unitymarvel/ModuleB v1.0.0
  4. replace github.com/unitymarvel/ModuleB => ../ModuleB

        replace执行中,”=>“左侧的部分是模块对应的网址,右侧对应了本地模块目录,可以使用相对路径,也可以使用绝对路径。

步骤8:修改Demo/go.mod文件

修改后的结果如下:

  1. module github.com/unitymarvel/Demo
  2. go 1.19
  3. require github.com/unitymarvel/ModuleA v1.0.0
  4. require github.com/unitymarvel/ModuleB v1.0.0
  5. replace github.com/unitymarvel/ModuleA => ../ModuleA
  6. replace github.com/unitymarvel/ModuleB => ../ModuleB

现在运行go run Demo.go,就会输出如下的内容:

Hello 李宁

Hello Bill

        以上使用了go modules的方式管理了ModuleA和ModuleB,尽管go.mod文件中使用了相对路径,pull也不会有路径问题,但由于ModuleB被引用了两次,这就意味着,如果ModuleB的位置或模块名发生了变化,那么就要修改两个地方,如果一个模块被引用了100次,那么就要修改100个地方,这将会带来灾难性的后果,有可能整个工程最后会乱套了。而如果使用go.work文件管理模块,那么不管模块路径和模块名如何变化,只需要修改go.work文件中的内容即可,而且不管模块名被引用多少次,只需要修改一次即可。

2. 使用工作区管理Go模块

使用工作区管理Go模块必须使用Go1.18或以上版本,使用其他Go版本的同学赶快升级。

在终端进入工程根目录,执行下面的命令创建go.work文件。

go work init ModuleA ModuleB Demo

其中ModuleA、ModuleB和Demo是3个模块的相对路径,如果读者使用了其他路径,需要将其修改成本机的模块路径。执行完命令后,会在工程根目录生成一个go.work文件,内容如下:

  1. go 1.19
  2. use (
  3. ./Demo
  4. ./ModuleA
  5. ./ModuleB
  6. )

其中use指令用于指定模块的路径(相对路径或绝对路径)。现在可以将Demo模块和ModuleA模块的go.mod文件中的replace指令和require指令全部删除了,接下来在Demo目录下执行go run Demo.go文件,就会执行成功,并输出如下内容:

Hello 李宁

Hello Bill

如果想关闭工作区模式,可以使用workfile命令行参数,如下所示:

go run -workfile=off Demo.go

go build -workfile=off Demo

3. 设置全局模块名

        可能细心的读者会发现一个问题,使用go.work的use指令,模块路径问题是解决了,但还有一个问题没解决,那就是模块名问题,如果修改模块名(如改变了模块名的网址),仍然需要修改大量的源代码。为了解决这个问题,可以在go.work文件中使用replace指令为模块名起一个别名,例如,可以将go.work文件改成如下的内容:

  1. go 1.19
  2. use (
  3. ./Demo
  4. ./ModuleA
  5. )
  6. replace MA v1.2.3 => ./ModuleA
  7. replace MB v1.2.3 => ./ModuleB

其中MA是MobileA的别名,MB是MobuleB的别名,目前这两个别名都指向本地模块,在引用MobuleA和MobuleB时,就可以直接使用MA和MB了,代码如下:

Demo.go文件的代码:

  1. package main
  2. import (
  3. "fmt"
  4. "MA"
  5. "MB"
  6. )
  7. func main() {
  8. ModuleA.OutputMsg("李宁")
  9. fmt.Println(ModuleB.Greet("Bill"))
  10. }

        如果使用了replace指令,use指令中对应模块的路径可以去掉了,当然,也可以保留这些路径,如果在use指令中保留了模块路径,那么在引用模块时,既可以使用在模块的go.mod文件中定义的模块名(module xxx中的xxx),也可以使用replace指令为模块起的别名。

        在go.work文件中使用replace指令时,需要在每个模块的go.mod文件中使用require指令指定别名和版本号(必须),如果replace指令没有指定版本号,那么谁便指定一个版本号即可,代码如下:

  1. module github.com/unitymarvel/Demo
  2. go 1.19
  3. require MA v1.2.3
  4. require MB v1.2.3

        现在执行go run Demo.go,就可以正常运行了。

        可能有的同学会问,目前使用了本地模块,那么在开发完本地模块后,要上传到github上,这就需要切换回网址形式的模块名,该如何做呢?其实很简单,只需要修改replace指令即可,如下面的代码将MA模块指向了github.com上对应的网址(这个网址并不存在,本文只是举个例子,读者需要替换成真正存在的网址)。

replace MA  => github.com/unitymarvel/ModuleA v1.3.3

        现在运行go run Demo.go,会首先从github.com/unitymarvel/ModuleA下载模块的相关代码(只下载一次,下载到GOPATH环境变量指定的第一个路径中),然后再运行。

        ”=>“左侧的部分可以选择添加版本后,也可以不添加,但右侧的部分如果是网址,必须添加版本号(可以与左侧的版本号不一致),但如果右侧是本地模块的路径,则不能添加版本号,例如,下面的replace指令是错误的:

replace MA => ./ModuleA v1.2.3

        注意:如果模块名与用replace指令为模块起的别名暂时相同,也可以先不使用replace指令,如果未来有一天需要改变本地模块路径或将模块指向网络地址,再在go.work文件中使用replace指令也不迟。

4. 在中使用工作区模式管理Go模块

        只需要用VSCode打开Go工程根目录,就可以直接使用工作区模式管理Go模块了,但如果修改某个配置,可能要稍微等一下才能生效,因为Go语言的LSP需要时间来处理,当配置文件(go.work、go.mod等)有错误时,LSP可能会出错,这时VSCode的智能编辑器可能会无法正确列出对象中的成员。要查看LSP到底是哪里出错了,可以在设置中搜索Use Language Server,然后找到Go: Use Language Server项,如下图所示。去掉前面的复选框,再重新选中前面的复选框,会看到重新启动Go LSP的输出日志。

 如果未显示启动日志,可以在VSCode的右下方的视图菜单中选择gopls(server),如下图所示。

 日志信息类似下面的内容:

 启动日志中有两个错误,但这两个错误并不致命,也不影响使用。错误的含义是MA和MB不包含点(.),也就是建议replace后面的部分(“=>”左侧的内容)应该是一个域名,为了消除这个错误,可以将replace指令按下面的方式修改:

  1. replace github.com/unitymarvel/MA => ./ModuleA
  2. replace github.com/unitymarvel/MB => ./ModuleB

        但要注意修改Go源代码文件中的模块引用和go.mod文件中的require指令。

        注意:如果使用的是本地模块,而且没有在go.work文件中使用replace为模块起别名,那么不能在模块的go.mod文件中使用require指令引用模块对应的网址(如下所示),否则执行go run Demo.go后,Go编译器会首先从指定网址下载模块,如果这些网址不存在或无法访问,Go编译器会一直被阻塞,直到强行中断。

  1. require github.com/marvel/antlr4 v1.0.0
  2. require github.com/marvel/compiler v1.0.0
  3. require github.com/marvel/core v1.0.0
  4. require github.com/marvel/parser v1.0.0

    

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发