gradle工程maven部署经验总结

前言

若使用gradle进行项目的开发,在引入一些sdk和开发组件时,依赖配置中经常会有类似于implementation 'androidx.appcompat:appcompat:1.2.0'的语句,gradle就可以根据引号内的内容去配置好的仓库中找到你想使用的jar包或是aar包。
上述的内容格式可以视为groupId:artifactId:version,这是maven仓库用于唯一定位一个包的索引,在进行组件或者sdk开发时,如果想将代码进行发布供他人使用,只需使用相应的上传工具并指定用于索引所必要的属性,就可以完成发布。

maven上传插件

对于Android开发来说,可部署的代码包格式,除了jar还有aar,aar的生成依赖于Android Gradle Plugin,在AGP 3.6.0以下,只能使用maven插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
apply plugin: 'maven'

uploadArchives {
repositories.mavenDeployer {
// MAVENREPO为指定的符合maven标准的仓库
repository(url: uri(MAVENREPO))
pom.project {
groupId "io.nebula.test"
artifactId 'test'
version '1.0.0'
}
}
}

通过如上的配置就可以生成对应task,运行

1
./gradlew [moduleName:]uploadArchives

执行完毕之后就可以找到部署到指定仓库的代码包。
如果目标仓库需要身份认证,有两种方式可以处理

  1. 使用authentication
    1
    2
    3
    repository(url: "scp://repos.mycompany.com/releases") {
    authentication(userName: "me", password: "myPassword")
    }
  2. 在仓库地址之前加上认证信息,用@符号和地址分割
    1
    repository(url: "scp://me:myPassword@repos.mycompany.com/releases")

AGP variant

maven插件在AGP下使用会有一些问题,插件不支持agp的variant,会默认将configuration archive下的代码包进行发布,也就是说,默认会发布release buildType的构建结果。一旦module声明了productFlavors,配置了新的variant,maven插件就无法部署了,需要额外进行一些处理,通过如下代码,可以选择指定名称的variant进行构建与发布。

1
2
3
4
5
6
7
android.libraryVariants.all { variant ->
def name = variant.name
if(variantName == "devRelease") {
def task = project.tasks.getByName("bundle${name.capitalize()}Aar")
artifacts.add('archives', task)
}
}

需要注意的是,通过这样的方式发布的aar,其对应的pom文件没有对应的依赖选项,需要手动填充Conf2ScopeMappings。但是aar又不支持直接在MavenPluginConvention内的conf2ScopeMappings中进行操作。目前依笔者经验,比较可行的方案是在pom配置完成后进行处理。dependencies内的element为org.apache.maven.model.Dependency,在脚本中无法直接引用,需要通过反射操作。

1
2
3
4
5
deployer.pom.whenConfigured { pom ->
def newDependencies = pom.dependencies
// do something
pom.dependencies = newDependencies
}

maven-publish插件

在AGP 3.6.0版本以后,添加了对maven-publish的支持,下面是官方的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
afterEvaluate {
publishing {
publications {
// Creates a Maven publication called "release".
release(MavenPublication) {
// Applies the component for the release build variant.
from components.release

// You can then customize attributes of the publication as shown below.
groupId = 'com.example.MyLibrary'
artifactId = 'final'
version = '1.0'
}
// Creates a Maven publication called “debug”.
debug(MavenPublication) {
// Applies the component for the debug build variant.
from components.debug

groupId = 'com.example.MyLibrary'
artifactId = 'final-debug'
version = '1.0'
}
}
}
}

要注意的是该配置必须写在afterEvaluate中,因为AGP的配置也是在gradle默认配置阶段结束后,运行阶段之前进行的。
如果要指定部署的仓库,可以按照如下方式(仓库的声明方式和maven插件相同)

1
2
3
4
5
6
7
publishing {
repositories {
maven {
url = MAVENREPO
}
}
}

如果不配置repository
maven-publish对每一个配置完成的publication和Repository进行组合,并为其生成task

1
2
3
4
publishDebugPublicationToMavenLocal
publishDebugPublicationToMavenRepository
publishReleasePublicationToMavenLocal
publishReleasePublicationToMavenRepository

*ToMaven是默认生成的上传任务,会将包部署至USER_HOME/.m2/repository下。
maven-publish插件相比旧版本的maven插件,配置的灵活性更高。

多project上传

在开发sdk时,经常会出现多个module同时开发的情况,module之间有依赖关系,且可能运行在不同的环境下。我见过很多开发者,选择将一个module部署在本地,或使用SNAPSHOT部署在maven服务器上,每次改动一个module,就重新部署一遍,这无疑是极其影响开发效率的。
究其原因,发现直接使用implementation project的方式引用一个module,在上传时,pom文件内对应的依赖项无法指定group和version。实际上这个问题很好解决,只需要在对应module的build.gradle中指定group和version即可,artifactId使用的是module name。
如果想指定artifactId,却不能修改已有的module name,并且使用的AGP版本在3.6.0以上(gradle 6.0以上),这时可以考虑使用dependencySubstitution。

1
2
3
4
5
6
configurations.all {
resolutionStrategy.dependencySubstitution {
// module中不需要传递版本号
substitute module("com.test111:test") with project(":mylibrary1")
}
}

首先使用[groupId:artifactId:version]的形式引入依赖,此时并不需要这个依赖已经存在;之后将上述代码添加到build.gradle中dependencies闭包的上面。此时gradle会用依赖替换原则,将标识定位的模块替换成本地的module,开发也和直接implementation project完全相同。
至于gradle 6.0以下的情况,由于dependencySubstitution不支持gradle api的依赖引用方式,因此该方案有很大的缺陷;不过可以考虑使用上文提到的方式,在pom.whenConfigured的回调中,直接进行pom依赖项的处理。