依赖包管理是所有包管理工具的噩梦。幸运的是,Gogradle的包管理机制非常优秀,足以面对极端复杂的情况。
众所周知,Go语言本身不参与代码包的管理;它假设所有的包都位于一个或者多个Workspace中,
这些Workspace由GOPATH
指定。GOPATH
可以包含多个路径,在构建时,Go语言构建工具会依次在这些路径中寻找所需的代码包。这就带来了许多问题:
饱受包管理问题困扰的Go语言不得已在1.5之后引入了vendor机制, 允许一个Go项目与自身的依赖包一起进入源代码管理系统。这一定程度上缓解了上述情况,却引入了新的问题:
Gogradle致力于改善这种情况。与glide
类似,它通过将声明的依赖包安装到vendor
中来完成项目级依赖的隔离,从而避免与全局GOPATH
发生关系。
Gogradle管理的依赖包声明于build.gradle
的dependencies
块中。当前只支持Git管理的依赖包,其他源代码管理系统的支持正在开发中。一般来说,你只需要声明你直接依赖的包,Gogradle会自动帮你解析传递性依赖并解决冲突。
下面是一些示例:
dependencies {
golang {
build 'github.com/user/project' // No specific version, the latest will be used
build name:'github.com/user/project' // Equivalent to last line
build 'github.com/user/project@1.0.0-RELEASE' // Specify a version(tag in Git)
build name:'github.com/user/project', tag:'1.0.0-RELEASE' // Equivalent to last line
build 'github.com/user/project@master' // Specify a branch
build name:'github.com/user/project', branch:'master' // Equivalent to last line
test 'github.com/user/project#d3fbe10ecf7294331763e5c219bb5aa3a6a86e80' // Specify a commit
test name:'github.com/user/project', commit:'d3fbe10ecf7294331763e5c219bb5aa3a6a86e80' // Equivalent to last line
build name:'github.com/user/project', version:'d3fbe10ecf7294331763e5c219bb5aa3a6a86e80' // Equivalent to last line
}
}
默认情况下,如果你的声明没有指定commit/tag/branch的话,Gogradle会每次执行git fetch
或者hg update -u
,以保证获取远端的最新版本。
依赖声明支持语义化版本。在Git中,”版本”即Git的tag。
dependencies {
golang {
build 'github.com/user/project@1.*' // Equivalent to >=1.0.0 & <2.0.0
build 'github.com/user/project@1.x' // Equivalent to last line
build 'github.com/user/project@1.X' // Equivalent to last line
build 'github.com/user/project@~1.5' // Equivalent to >=1.5.0 & <1.6.0
build 'github.com/user/project@1.0-2.0' // Equivalent to >=1.0.0 & <=2.0.0
build 'github.com/user/project@^0.2.3' // Equivalent to >=0.2.3 & <0.3.0
build 'github.com/user/project@1' // Equivalent to 1.X or >=1.0.0 & <2.0.0
build 'github.com/user/project@!(1.x)' // Equivalent to <1.0.0 & >=2.0.0
build 'github.com/user/project@ ~1.3 | (1.4.* & !=1.4.5) | ~2' // Very complicated expression
}
}
可以在声明时指定仓库的url。这尤其适用于私有仓库。有关私有仓库的权限验证请参考仓库管理。
dependencies {
golang {
build name: 'github.com/user/project', url:'https://github.com/user/project.git', tag:'v1.0.0'
build name: 'github.com/user/project', url:'git@github.com:user/project.git', tag:'v2.0.0'
}
}
可以同时声明多个依赖:
dependencies {
golang {
build 'github.com/a/b@1.0.0', 'github.com/c/d@2.0.0', 'github.com/e/f#commitId'
build([name: 'github.com/g/h', tag: '2.5'],
[name: 'github.com/i/j', commit: 'commitId'])
}
}
Gogradle支持对传递性依赖的管理。例如,下列声明禁止了github.com/user/project
的传递性依赖。
dependencies {
golang {
build('github.com/user/project') {
transitive = false
}
}
}
此外,还可以排除指定条件的传递性依赖,例如,下列声明从github.com/a/b
的后代中排除了全部github.com/c/d
依赖包和指定版本的github.com/e/f
依赖包。
dependencies {
golang {
build('github.com/a/b') {
exclude name:'github.com/c/d'
exclude name:'github.com/c/d', tag: 'v1.0.0'
}
}
}
若依赖包位于本地,可以使用如下方式予以声明:
dependencies {
golang {
build name: 'a/local/package', dir: 'path/to/local/package' // It must be absolute
}
}
子包的概念受glide启发。许多情况下,我们只需要依赖一个仓库的某些子包,因此,你可以进行如下声明:
dependencies {
golang {
build name: 'github.com/big/package', subpackages: ['.', 'sub1', 'sub2/subsub']
}
}
这段代码声明依赖github.com/big/package
仓库的三个子包:根目录、sub1
的所有后代目录、sub2/subsub
的所有后代目录。如果你只需要sub1
目录下的所有go文件(即sub1
包),你需要声明:
dependencies {
golang {
build name: 'github.com/big/package', subpackages: 'sub1/.'
}
}
你可能注意到了,上面的依赖声明中始终包含build
和test
字样。它们是Gradle构建模型中的一个概念,称为Configuration。
Gogradle预定义了两个名为build
和test
的Configuration。无需深究其细节,你可以将它们理解成两组完全独立的依赖包集合。
在构建中,只有build
依赖会生效;在测试中,build
和test
依赖同时生效,且build
中的优先级更高。
Gogradle将依赖包分为四种:
import
语句中声明的代码包Go语言本身没有依赖包的概念,一个包就是一个普通的文件夹。
在Gogradle中,依赖包通常以被源代码管理系统所管理的仓库为最小单位,例如,一个被Git管理的仓库中的所有go文件属于同一个依赖包。 Gogradle按照Go语言默认的方式解析包的路径,将原本散乱的代码包看作一个个的依赖包。
依赖解析,即将依赖包解析成实际代码的过程。这个过程通常需要借助源代码管理系统,如Git。 Gogradle的目标是支持Go语言原生支持的全部四种(Git/Mercurial/Svn/Bazaar)源代码管理工具,不过当前只实现了Git和Mercurial。
一个项目的依赖包(传递性依赖)可以由以下途径产生:
vendor
目录中的依赖包import
声明默认情况下,Gogradle会读取前两者作为传递性依赖,且vendor
中的优先级更高。若这样得到的结果为空,Gogradle会扫描.go
源代码中的import
语句,
提取其中的代码包当作传递性依赖。
由于传递性依赖的存在,在实际的构建中,依赖关系可能错综复杂。
当一个项目依赖了同一个代码包的不同版本(无论它们位于何处),我们认为这些版本处于冲突状态,需要解决。例如,A依赖了B的版本1和C,
C依赖了B的版本2,此时,B的版本1和版本2就存在冲突。Go语言的vendor
机制允许这些版本同时存在,这是Gogradle所反对的。
因为这样做迟早会带来问题。Gogradle会尝试解决所有的依赖冲突(扁平化),
并将解决后的结果放在vendor
中,以便进行隔离的、可复现的构建。
Gogradle解决依赖的策略是:
具体来说,Gogradle会识别每个依赖包的”更新时间”,并将这些更新时间作为解决冲突的依据。
你可以令Gogradle锁定当前构建的依赖,这会在项目目录下生成一个名为gogradle.lock
的文件,记录了构建所需的全部依赖的详细版本,以便进行稳定的、可重现的构建。无论何时,此文件都不应被手动修改。
Gogradle推荐将此文件提交到源代码管理系统中。可以通过
gradlew goLock
生成依赖锁定文件。
一些广泛使用的包中的import声明包含无法识别的包声明,这样会导致Gogradle报错”Cannot recognize package xxx”,从而使用户感到迷惑。
例如,github.com/golang/mock
包含声明import "a"
,这会阻碍Gogradle进行进一步的代码分析。
为解决此问题,从0.9开始,Gogradle默认排除了一些这样的包。
欲增加默认排除的包,在build.gradle
中加入:
golang {
ignorePackage('package1', 'package2')
}
如果你碰巧需要被排除的包,使用下列配置:
golang {
ignoredPackages = []
}