手把手教你如何创建及使用Go module

Posted GoCN

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你如何创建及使用Go module相关的知识,希望对你有一定的参考价值。

Go module是从Go 1.11版本才引入的新功能。其目标是取代旧的的基于GOPATH方法来指定在工程中使用哪些源文件或导入包。本文首先分析Go引入module之前管理依赖的优缺点,然后针对这些缺点,看module是如何解决的。
get github.com/content=mod init github.com/goxuetang/encodex
该命令会在encodex的根目录下创建go.mod文件,go.mod文件会包含我们定义的module的导入路径和依赖的包及对应的版本。如下所示:

由上图可知,在生成的go.mod文件中显示了该module可被导入的路径以及Go的版本。因为目前还没有导入任何其他依赖包,所以没有显示导入包的信息。好,现在我们把该目录同时提交到git上。
git init

git remote add origin https:get github.com/goxuetang/encodex/hash@v1get github.com/goxuetang/encodex/hash

get:added github.com/goxuetang/encodex v1get -u=patch

如果想更新同一个大版本下的小版本,那么可以使用如下命令:

get -u
该命令是如果小版本有更新,则升级小版本。如果只有补丁版本有更新,则会升级补丁版本。
如果想升级到指定的版本,则使用指定版本的命令:
get module@version

例如,要将encodex模块升级到v1.1.3版本,则使用如下命令:

get github.com/goxuetang/encodex@v1newHash github.com/goxuetang/encodex/v2/hash

同时使用go get命令下载并安装该模块:

get github.com/goxuetang/encodex/v2

四、间接依赖

一个工程所依赖的模块可分为直接依赖和**间接依赖。**直接依赖就是我们的工程文件中使用import语句导入的模块。而间接依赖就是我们直接依赖的模块所依赖的。如下图: 
现在我们在main模块中引入github.com/go-redis/redis 模块,然后查看go.mod文件,发现有如下间接的依赖模块,这里的模块正是在github.com/go-redis/redis 中引入的模块,可以查看github.com/go-redis/redis 模块的go.mod文件以确认。

在上图中,我们还发现redis的模块后面的版本是 v6.15.9+incompatible。这个代表什么意思呢?这个代表的是引入的模块的最新版本是v5.15.9,但同时具有不兼容的风险。为什么呢?因为在redis模块中未使用规范的导入名称。例如,规范的模块命名应该是在模块的版本大于1的时候,导入名称就需要增加主版本信息。例如,当该模块是第一个版本时,其对应的go.mod文件如下:
module github.com/go-redis/redis

当主版本升级到2时,则go.mod中的模块导入名称应该为:

module github.com/go-redis/redis/v2
如果不增加v2这个标识,那么当使用go get github.com/go-redis/redis 下载包的时候,go会找到模块名称没有使用主版本标识的最新的版本。我们通过查看该模块在git上的6.15.9的版本源码,发现其源码中并没有go.mod文件。
所以,当模块的go.mod文件中的导入路径没有版本后缀(例如v2)的情况下,默认是v1版本,因此在使用go get获取这样的模块时,默认会获取v1.x.x的最新版本。

五、 小版本的选择

我们已经知道了Go可以同时导入主版本不同的module。那么,如果只有小版本或补丁版本不同,那么Go该如何选择呢?假设工程项目直接依赖于两个module:A和B。同时A依赖于MODULE 1 的v1.0.1版本,但B依赖于MODULE 1的v1.0.2版本。如下图所示: 
那么,在工程项目模块(PROJECT MODULE)中需要间接依赖MODULE 1的哪个版本呢?如果我们使用v1.0.1,那么MODULE B有可能会产生异常。在语义化版本中,我们知道小版本或补丁版本应该向后兼容,即v1.0.2是兼容v1.0.1的,所以在PROJECZT MODULE中应该选择MODULE 1的v1.0.2版本。

总结

Go module不仅解决了项目代码不再依赖于GOPATH路径,而且还解决了相同module的多版本引入问题。通过本文章,相信您对module的创建、发布、版本管理、依赖关系都会有了一个清晰的认识。

想要了解更多 Golang 相关的内容,欢迎扫描下方

话编程 |手把手教你开发docker一样的命令行

目录

  • 前言

  • 一、安装

  • 二、初始化应用

    • 初始化项目

    • 创建入口文件cmd/root.go

    • 创建主程序main.go

  • 三、如何自定义命令

    • 创建hello子命令

    • 创建version子命令

  • 四、如何设置flag选项

    • 全局选项

    • 本地选项

    • 设置必填

    • 绑定配置

  • 五、如何设置arguments

    • 使用示例

  • 六、如何使用参数

    • 获取flag参数

    • 获取args参数

  • 七、如何设置钩子

  • 总结


前言

Cobra是一个强大的用来构建命令行程序的库,许多流行的Go项目都是用它来构建的,比如Kubernetes、Docker、etcd、Istio、Github CLI等等。

接下来,演示开发一个我们自己的命令行程序chenqionghe,模仿一下docker命令行,预期功能如下

# 查看帮助
chenqiong -h
# 查看版本,类似docker version
chenqionghe version
# 查看hello命令帮助,类似docker ps -h
chenqionghe hello -h
# 使用hello命令,类似docker run --name app --volume /app/data
chenqionghe hello --name light-weight-baby --author gym

Cobra基于三个基本概念

  • commands(行为)

  • arguments(位置参数)

  • flags(命令行选项)

使用基本模式是APPNAME VERB NOUN --ADJECTIVE或APPNAME COMMAND ARG --FLAG,例如

# server是一个command,--port=1313是一个命令行选项
hugo server --port=1313
# clone 是 commands,URL 是 arguments,brae是命令行选项
git clone URL --bare

一、安装

go get -u github.com/spf13/cobra/cobra
go install github.com/spf13/cobra/cobra

二、初始化应用

初始化项目

这里我的应用名叫chenqionghe

go mod init chenqionghe

创建入口文件cmd/root.go

创建文件夹cmd,并创建文件cmd/root.go,这是用来放所有的命令的基本文件

package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var rootCmd = &cobra.Command{
Use: "chenqionghe",
Short: "getting muscle is not easy",
Long: `let's do it, yeah buddy light weight baby!`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello chenqionghe")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

创建主程序main.go

package main

import "chenqionghe/cmd"

func main() {
cmd.Execute()
}

运行一下main.go可以看到生效了

三、如何自定义命令

创建hello子命令

cobra add hello

会在cmd下生成一个hello.cmd的命令,生成的命令是长下面这样的,核心是调用了AddCommand方法
话编程 |手把手教你开发docker一样的命令行

我们把没用的信息干掉,精简后如下

package cmd

import (
"fmt"
"github.com/spf13/cobra"
)
var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令简介",
Long: `hello命令详细介绍`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello called")
},
TraverseChildren: true,
}

func init() {
rootCmd.AddCommand(helloCmd)
}

直接运行看看

go run main.go hello

话编程 |手把手教你开发docker一样的命令行

创建version子命令

同理,我们再创建一个version命令

cobra add version

修改一下Run方法

Run: func(cmd *cobra.Command, args []string) {
fmt.Println("chenqionghe version v0.0.1")
},

运行如下
话编程 |手把手教你开发docker一样的命令行

四、如何设置flag选项

flag选项按作用范围分为persistent和local两类

全局选项

persistent是全局选项,对应的方法为PersistentFlags,可以分配给命令和命令下的所有子命令,上面的rootCmd和helloCmd都是可以调用flag
例如,添加一个-v选项

func init() {
var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "全局版本")
}

运行,可以看到生效了
话编程 |手把手教你开发docker一样的命令行

本地选项

local为本地选项,对应方法为Flags,只对指定的Command生效,我们往hello命令的init里边添加一个本地flag

func init() {
rootCmd.AddCommand(helloCmd)
//本地flag
var Source string
helloCmd.Flags().StringVarP(&Source, "source", "s", "", "读取文件路径")
}

运行如下
话编程 |手把手教你开发docker一样的命令行

设置必填

我们在init函数添加以下代码

rootCmd.Flags().StringVarP(&Name, "name", "n", "", "你的名字")
rootCmd.MarkFlagRequired("name")

运行如下,必须填写name参数才可以运行
话编程 |手把手教你开发docker一样的命令行

绑定配置

添加一个initConfig方法

func initConfig() {
viper.AddConfigPath("./")
viper.AddConfigPath("./conf")
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
}

在init中调用

cobra.OnInitialize(initConfig) //这会运行每个子命令之前运行
rootCmd.PersistentFlags().StringVar(&Author, "author", "defaultAuthor", "作者名")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))

这将把viper配置和flag绑定,如果用户不设置-author选项,将从配置中查找

五、如何设置arguments

cobra默认提供了一些验证方法

  • NoArgs: 如果包含任何位置参数,命令报错

  • ArbitraryArgs: 命令接受任何参数

  • OnlyValidArgs: 如果有位置参数不在ValidArgs中,命令报错

  • MinimumArgs(init): 如果参数数目少于N个后,命令行报错

  • MaximumArgs(init): 如果参数数目多余N个后,命令行报错

  • ExactArgs(init): 如果参数数目不是N个话,命令行报错

  • RangeArgs(min, max): 如果参数数目不在范围(min, max)中,命令行报错

使用示例

往Command中添加参数Args,我们规定参数不能少于5个,如下

var rootCmd = &cobra.Command{
Use: "chenqionghe",
Short: "getting muscle is not easy",
Long: `let's do it, yeah buddy light weight baby!`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello chenqionghe")
},
Args: cobra.MinimumNArgs(5),
}

运行输出
话编程 |手把手教你开发docker一样的命令行

六、如何使用参数

我们可以看到核心的方法,其实就是cobra.Command中的Run参数,指定了func(cmd *cobra.Command, args []string)类型的回调
代表我们可以直接使用cmd和args来编写我们的程序

获取flag参数

我们可以直接使用cmd的flag方法获取传递的flag

var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令简介",
Long: `hello命令详细介绍`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(cmd.Flag("author").Value)
fmt.Println(cmd.Flag("name").Value)
},
}

运行如下
话编程 |手把手教你开发docker一样的命令行

获取args参数

var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令简介",
Long: `hello命令详细介绍`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(args)
},
TraverseChildren: true,
}

调用如下,可以看到已经取出了所有的args参数
话编程 |手把手教你开发docker一样的命令行

七、如何设置钩子

cobra提供了很多钩子方法,可按运行顺序排列如下

  • PersistentPreRun

  • PreRun

  • Run

  • PostRun

  • PersistentPostRun
    使用示例

var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令简介",
Long: `hello命令详细介绍`,
//Args: cobra.MinimumNArgs(1),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v ", args)
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PreRun with args: %v ", args)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Run with args: %v ", args)
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PostRun with args: %v ", args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPostRun with args: %v ", args)
},
}

运行如下
话编程 |手把手教你开发docker一样的命令行

总结

到这里,我们就已经学会了如果设置子命令、flag参数、arguments参数以及编写方法使用这些参数,
还有最后一步,就是编译出我们的二进制程序,验证一下我们之前的需求

  • 编译

go build -o chenqionghe

如下,已经生成二进制文件chenqionghe

  • 运行命令

./chenqionghe -h # 查看帮助
./chenqionghe version # 查看版本,类似docker version
./chenqionghe hello -h # 查看hello命令帮助,类似docker ps -h
./chenqionghe hello --name light-weight-baby --author gym # 使用hello命令,类似docker run --name app --volume /app/data

可以看到,完美的实现了预期需求,就是这么简单,light weight baby!


以上是关于手把手教你如何创建及使用Go module的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你用IntelliJ IDEA 创建jsp项目

一学就会,手把手教你用Go语言调用智能合约

手把手教你怎么导入Go语言第三方库

Go区块链开发手把手教你导入Go语言第三方库

手把手教你如何使用NodeJs和JavaScript开发微信公众号建议收藏

手把手教你如何使用NodeJs和JavaScript开发微信公众号建议收藏