美文网首页
【译】模块(Modules)第二部分:项目,依赖项和Gopls

【译】模块(Modules)第二部分:项目,依赖项和Gopls

作者: 豆腐匠 | 来源:发表于2020-04-12 16:59 被阅读0次

    介绍

    模块是Go集成到系统中,为依赖管理提供支持的解决方案。这意味着模块可以接触到与源代码有关的所有内容,包括对编辑器的支持。为了向编辑器提供对模块的支持(以及其他原因),Go团队构建了一个名为gopls的服务,该服务实现了语言服务器协议(LSP
    。LSP最初是由Microsoft为VS Code开发的,现已成为一个开放标准。该协议的核心思想是为编辑人员提供对编程语言的支持,例如代码自动补全,跳转到定义位置以及查找所有引用。

    在你使用模块和VS Code时,在编辑器中单击“保存”将不再直接运行go build命令。现在发生的事情是将请求发送到gopls,然后gopls运行适当的Go命令和关联的API,来为编辑器提供反馈和支持。Gopls也可以将信息发送到编辑器而无需请求。有时,由于LSP的性质或运行Go命令的固有延迟,编辑器中代码的更改会出现滞后或不同步的现象。Go团队正在努力推出gopls v1.0版本来处理这些极端情况,以便您可以获得最流畅的编辑体验。

    在本文中,我将逐步介绍在项目中添加和删除依赖项的基本工作流程。这篇文章使用的是VS Code编辑器,gopls的0.2.0版和Go的1.13.3版。

    模块缓存

    为了保证项目快速构建以及保持项目中的依赖项为最新更新,Go维护了一份本地缓存,保存了所有已经下载的模块。我们可以在$GOPATH/pkg找到这份缓存。如果你没有设置过GOPATH,那么会使用默认的GOPATH,目录是$HOME/go

    注意:建议提供一个环境变量,以允许用户自定义模块缓存的位置。如果未更改,$ GOPATH / pkg将是默认值。

    代码1

    $HOME/code/go/pkg
    $ ls -l
    total 0
    drwxr-xr-x  11 bill  staff  352 Oct 16 15:53 mod
    drwxr-xr-x   3 bill  staff   96 Oct  3 16:49 sumdb
    
    

    代码1显示了当前我本地的$GOPATH/pkg文件夹内容。可以看到有两个文件夹,modsumdb。如果仔细查看mod文件夹内部,则可以了解有关模块缓存布局的更多信息。

    代码2

    $HOME/code/go/pkg
    $ ls -l mod/
    total 0
    drwxr-xr-x   5 bill  staff   160 Oct  7 10:37 cache
    drwxr-xr-x   3 bill  staff    96 Oct  3 16:55 contrib.go.opencensus.io
    drwxr-xr-x  40 bill  staff  1280 Oct 16 15:53 github.com
    dr-x------  26 bill  staff   832 Oct  3 16:50 go.opencensus.io@v0.22.1
    drwxr-xr-x   3 bill  staff    96 Oct  3 16:56 golang.org
    drwxr-xr-x   4 bill  staff   128 Oct  7 10:37 google.golang.org
    drwxr-xr-x   7 bill  staff   224 Oct 16 15:53 gopkg.in
    drwxr-xr-x   7 bill  staff   224 Oct 16 15:53 k8s.io
    drwxr-xr-x   5 bill  staff   160 Oct 16 15:53 sigs.k8s.io
    
    

    代码2显示了当前模块缓存的顶层结构。我们可以看到与模块名称关联的URL的第一部分被用作模块缓存中的顶级文件夹。下面我进入github.com/ardanlabs,我可以向你展示两个实际的模块。

    代码3

    $HOME/code/go/pkg
    $ ls -l mod/github.com/ardanlabs/
    total 0
    dr-x------  13 bill  staff  416 Oct  3 16:49 conf@v1.1.0
    dr-x------  18 bill  staff  576 Oct 12 10:08 service@v0.0.0-20191008203700-49ed4b4f1088
    
    

    代码3显示了我在ArdanLabs中使用的两个模块及其版本。第一个是conf模块,另一个模块与我用来教学kubernetes服务的项目相关。

    gopls服务在内存中也维护了一份模块缓存。启动VS Code并进入模块模式后,gopls服务会启动来支持编辑器的会话。gopls内部会将模块缓存与磁盘上当前的内容同步。gopls正是使用此内部模块缓存来处理编辑器请求。

    对于这篇文章,我将在开始之前清除模块缓存,以便拥有一个干净的工作空间。在启动VS Code编辑器之前,我还将设置项目。这将向你展示如何处理尚未下载到本地的模块缓存或在gopls内部的模块缓存中进行更新的情况。

    注意:清除模块缓存是在任何常规工作流程中都不需要执行的操作。

    代码4

    $ go clean -modcache
    
    

    代码4展示了如何清除磁盘上的本地模块缓存。go clean传统上,用于清理本地GOPATH工作目录和GOPATH bin文件夹。现在携带新的-modcache标志,该命令可用于清除模块缓存。

    注意:此命令不会清除任何正在运行的gopls实例的内部缓存。

    新建项目

    我将在GOPATH之外启动一个新项目,在编写代码的过程中,我将逐步介绍添加和删除依赖项的基本工作流程。

    代码5

    $ cd $HOME
    $ mkdir service
    $ cd service
    $ mkdir cmd
    $ mkdir cmd/sales-api
    $ touch cmd/sales-api/main.go
    
    

    代码5展示了设置工作目录的命令,创建初始项目结构并添加main.go文件。

    使用模块的第一步是初始化项目源代码树的根。这是一步通过使用go mod init命令来完成。

    代码6

    $ go mod init github.com/ardanlabs/service
    
    

    代码6展示了go mod init的调用,将模块名称作为参数传递。如第一篇文章所述,模块名称允许内部导入并在模块内部解析。常见的命名方式是用托管代码的仓库的URL来命名模块。对于这篇文章,我假设此模块将与Github的Ardan Labs下的服务项目相关联。

    调用go mod init完成后,会在当前工作目录中创建一个go.mod文件。该文件代表了项目的根目录。

    代码7

    01 module github.com/ardanlabs/service
    02
    03 go 1.13
    
    

    代码7展示了此项目的初始模块文件的内容。完成后,该项目可以进行编码了。

    代码8

    $ code .
    
    

    代码8展示了启动VS Code实例的命令。同时,这也将启动gopls服务的实例以支持该编辑器。

    图1

    image

    图1展示了运行所有命令后VS Code编辑器中项目的效果。为了确保你使用的设置与我相同,这里列出了当前VS Code的设置。

    代码9

    {
        // Important Settings
        "go.lintTool": "golint",
        "go.goroot": "/usr/local/go",
        "go.gopath": "/Users/bill/code/go",
    
        "go.useLanguageServer": true,
        "[go]": {
            "editor.snippetSuggestions": "none",
            "editor.formatOnSave": true,
            "editor.codeActionsOnSave": {
                "source.organizeImports": true
            }
        },
        "gopls": {
            "usePlaceholders": true,    // add parameter placeholders when completing a function
            "completeUnimported": true, // autocomplete unimported packages
            "deepCompletion": true,     // enable deep completion
        },
        "go.languageServerFlags": [
            "-rpc.trace", // for more detailed debug logging
        ],
    }
    
    

    代码9显示了我当前的VS Code设置。如果你按照上面的步骤进行但没有看到相同的效果,请对照配置进行检查。如果你想查看当前推荐的VS Code设置,在这里

    开始编码

    我将从该应用程序的初始代码开始。

    代码10
    https://play.golang.org/p/c8kGx7I9HJH

    01 package main
    02
    03 func main() {
    04     if err := run(); err != nil {
    05         log.Println("error :", err)
    06         os.Exit(1)
    07     }
    08 }
    09
    10 func run() error {
    11     return nil
    12 }
    
    

    代码10展示了我添加在main.go里的前12行代码。它设置了应用程序具有单一退出点的功能,并记录了启动或关闭时的任何错误。一旦将这12行代码保存到文件中,编辑器就会自动(感谢gopls)导入标准库中所需的依赖。

    代码11
    https://play.golang.org/p/x3hBA6PuW3R

    03 import (
    04     "log"
    05     "os"
    06 )
    
    

    代码11展示了在03至06行对源代码所做的更改,这要归功于gopls与编辑器的集成。

    接下来,我将添加对配置的支持。

    代码12
    https://play.golang.org/p/4hFXLJj4yT_Z

    17 func run() error {
    18     var cfg struct {
    19         Web struct {
    20             APIHost         string        `conf:"default:0.0.0.0:3000"`
    21             DebugHost       string        `conf:"default:0.0.0.0:4000"`
    22             ReadTimeout     time.Duration `conf:"default:5s"`
    23             WriteTimeout    time.Duration `conf:"default:5s"`
    24             ShutdownTimeout time.Duration `conf:"default:5s"`
    25         }
    26     }
    27
    28     if err := conf.Parse(os.Args[1:], "SALES", &cfg); err != nil {
    29         return fmt.Errorf("parsing config : %w", err)
    30     }
    
    

    代码12展示了run在第18至30行上添加到该函数中用来支持配置的代码。当此代码添加到源文件中并单击“保存”时,编辑器正确地将fmttime包导入进来。不幸的是,由于gopls当前在其内部模块缓存中没有有关 conf软件包的任何信息,因此gopls无法指导编辑器添加导入conf或为编辑器提供软件包信息。

    图2

    image

    图2显示了编辑器清楚地表明它无法解析与conf软件包有关的任何信息。

    添加依赖

    为了解决导入,需要检索包含conf软件包的模块。一种方法是,将导入添加到源代码文件的顶部,然后让编辑器和gopls完成工作。

    代码13

    01 package main
    02
    03 import (
    04     "fmt"
    05     "log"
    06     "os"
    07     "time"
    08
    09     "github.com/ardanlabs/conf"
    10 )
    
    

    在代码13中,我在第09行中添加了conf包的导入。一旦单击保存,编辑器通知gopls,然后gopls使用Go命令和关联的API查找,下载并提取该包的模块。这些调用还会更新Go模块文件以反映此更改。

    代码14

    ~/code/go/pkg/mod/github.com/ardanlabs
    $ ls -l
    total 0
    drwxr-xr-x   3 bill  staff    96B Nov  8 16:02 .
    drwxr-xr-x   3 bill  staff    96B Nov  8 16:02 ..
    dr-x------  13 bill  staff   416B Nov  8 16:02 conf@v1.2.0
    
    

    代码14显示了Go命令如何完成其​​工作并下载了conf1.2.0版的模块。现在,我们解决导入所需的代码在我的本地模块缓存中。

    图3

    image

    图3显示了编辑器如何仍然无法解析有关程序包的信息。为什么编辑器无法解析此信息?很不辛,gopls内部模块缓存与本地模块缓存不同步。gopls服务不知道Go命令刚刚进行的更改。由于gopls使用其内部缓存,因此gopls无法为编辑器提供所需的信息。

    注意:此缺陷当前正在解决中,并将在即将发布的版本中修复。您可以在此处跟踪问题。(https://github.com/golang/go/issues/31999

    gopls内部模块缓存与本地模块缓存同步的一种快速方法是重新加载VS Code编辑器。这将重新启动gopls服务并重置其内部模块缓存。在VS Code中,有一个特殊的命令reload window可以做到这一点。

    Ctrl + Shift + P and run  > Reload Window
    
    

    图4[图片上传失败...(image-6a6146-1586268866514)]

    图4显示了使用Ctrl + Shift + P和键入reload window后VS Code中出现的对话框。

    运行此快捷命令后,导入关联的所有消息都会得到解决。

    传递依赖

    从Go工具的角度来看,构建此应用程序所需的所有代码现在都在本地模块缓存中。但是,conf程序包依赖于Google 的go-cmp程序包进行测试。

    代码15

    module github.com/ardanlabs/conf
    
    go 1.13
    
    require github.com/google/go-cmp v0.3.1
    
    

    代码15显示了conf模块1.2.0版的模块配置文件。我们可以看到conf依赖 go-cmp的0.3.1版本。该模块未在服务的模块文件中列出,因为这是多余的工作。Go工具可以遵循模块文件的路径,以获取构建或测试代码所需的所有模块的完整镜像。

    此刻,该传递模块尚未找到、下载并提取到我的本地模块缓存中。由于构建代码时不需要此模块,因此Go生成工具尚未发现下载这个模块的意义。如果我在命令行上运行go mod tidy,那么Go工具将会花费时间将go-cmp模块加入本地缓存。

    代码16

    $ go mod tidy
    go: downloading github.com/google/go-cmp v0.3.1
    go: extracting github.com/google/go-cmp v0.3.1
    
    

    代码16中显示了go-cmp模块是如何找到、下载并提取出来。go mod tidy命令不会更改项目的模块文件,因为这不是直接依赖项。它将更新go.sum文件,以便记录模块的哈希值,以此来保证可以持久且可复制的构建。我将在以后的文章中讨论一下“校验和数据库”。

    代码17

    github.com/ardanlabs/conf v1.2.0 h1:2IntiqlEhRk+sYUbc8QAAZdZlpBWIzNoqILQvV6Jofo=
    github.com/ardanlabs/conf v1.2.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
    github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
    github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
    
    

    代码17显示了运行go mod tidy后的校验和文件。与该项目关联的每个模块都有两个记录。

    下载模块

    如果您尚未准备好在代码库中使用特定模块,但想将模块提前下载到本地模块缓存中,一种方法是手动将模块添加到项目go.mod文件中,然后在编辑器外部运行go mod tidy

    代码18

    01 module github.com/ardanlabs/service
    02
    03 go 1.13
    04
    05 require (
    06     github.com/ardanlabs/conf v1.2.0
    07     github.com/pkg/errors latest
    08 )
    
    

    在代码18中,可以看到我如何在模块文件中第7行手动添加的命令,用来获取最新版本的errors模块。手动添加所需模块的重要部分是使用latest标签。当go mod tidy运行时发现此更改,它将告诉Go查找该errors模块的最新版本并将其下载到我的缓存中。

    代码19

    $HOME/service
    $ go mod tidy
    go: finding github.com/pkg/errors v0.8.1
    
    

    代码19显示了errors0.8.1版本是如何找到、下载并提取。命令运行完成后,由于项目未使用该模块,因此将该模块会从模块文件中删除。但是,该模块已经记录在校验和文件中。

    代码20

    github.com/ardanlabs/conf v1.2.0 h1:2IntiqlEhRk+sYUbc8QAAZdZlpBWIzNoqILQvV6Jofo=
    github.com/ardanlabs/conf v1.2.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
    github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
    github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
    github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
    
    

    代码20显示了errors现在如何在校验和文件中记录的。有一点很重要,校验和文件不是项目正在使用的所有依赖项的规范记录。它可以包含更多模块。

    我更喜欢这种通过使用上面的方法来下载新模块而不是go get,因为如果你不小心,go get也可以升级项目的依赖关系图中的依赖关系(直接和间接)。重要的是要知道何时进行版本升级,而不仅仅是下载所需的新模块。在以后的文章中,我将讨论如何使用go get更新现有模块的依赖。

    删除依赖项

    如果我决定不再使用conf包,该怎么办?我可以删除使用了此包的任何代码。

    代码21
    https://play.golang.org/p/x3hBA6PuW3R

    01 package main
    02
    03 import (
    04     "log"
    05     "os"
    06 )
    07
    08 func main() {
    09     if err := run(); err != nil {
    10         log.Println("error :", err)
    11         os.Exit(1)
    12     }
    13 }
    14
    15 func run() error {
    16     return nil
    17 }
    
    

    代码21显示了confmain函数中删除的代码。当我点击保存后,编辑器将从导入集中删除conf的导入。但是,模块文件尚未更新。

    代码22

    01 module github.com/ardanlabs/service
    02
    03 go 1.13
    04
    05 require github.com/ardanlabs/conf v1.1.0
    06
    
    

    代码22显示代码仍然认为conf软件包是必需的。要解决此问题,我需要离开编辑器并再次运行go mod tidy

    代码23

    $HOME/service
    $ go mod tidy
    
    

    代码23 显示了再次运行go mod tidy的效果。这次没有输出。该命令完成后,模块文件将变得正确。

    代码24

    $HOME/services/go.mod
    
    01 module github.com/ardanlabs/service
    02
    03 go 1.13
    04
    
    

    代码24显示该conf模块已从模块文件中删除。这次go mod tidy命令清除了校验和文件,它将为空。在你提交任何代码到VCS之前应该确保执行了go mod tidy,并确保模块文件准确且与所使用的依赖项保持一致,这一点很重要。

    结论

    在不久的将来,不再需要我分享的这些变通办法,例如重新加载窗口。Go团队意识到了目前存在的这一缺陷和其他问题,因此他们正在积极努力解决所有缺陷。他们非常感谢Go问题跟踪上的所有反馈,因此,如果你发现了一些其他问题,请及时上报。问题无大小之分。作为社区,让我们与Go团队合作,快速解决这些剩余的问题。

    目前正在研究的一项核心功能是gopls监视文件系统并查看项目更改的能力。这将有助于gopls使其内部模块缓存与磁盘上的本地模块缓存保持同步。一旦这个功能实现,就不需要再重新加载窗口了。后台进行工作时提供视觉提示的功能也在开发了。

    总体而言,我对当前的工具集和重新加载窗口解决方法感到满意。希望你可以考虑使用模块。这些模块已准备就绪,可以使用,并且开始使用它的项目也越来越多了,Go生态系统对每个人来说会越来越好。

    相关文章

      网友评论

          本文标题:【译】模块(Modules)第二部分:项目,依赖项和Gopls

          本文链接:https://www.haomeiwen.com/subject/gcysphtx.html