Gradle手札之组件发布迅疾如风

Posted by Dorck on August 20, 2022

大浪淘沙,随着 jcenter 的落幕,组件发布逐渐转向了 maven central 仓库。各种平台层出不穷,不论组件存储在何处,作为开发更加关注的是组件发布的效率,这也正是本文接下来想要阐述的主题。

发布Android组件

Android Gradle 早早地就更新换代到了 maven-publish 插件。一般情况下,我们发布一个普通的 Android 组件通常需要添加如下代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
plugins {
  id 'com.android.library'
  id 'org.jetbrains.kotlin.android'
	id 'maven-publish'
}

task androidSourcesJar(type: Jar) {
	archiveClassifier.set('sources')
	from android.sourceSets.main.java.srcDirs
}

// Because the components are created only during the afterEvaluate phase, you must
// configure your publications using the afterEvaluate() lifecycle method.
afterEvaluate {
    publishing {
        publications {
            // Creates a Maven publication called "release".
            register<MavenPublication>("release") {
                // Applies the component for the release build variant.
                from(components["release"])

                artifact(androidSourcesJar)

                groupId = 'com.example'
                artifactId = 'custom-library'
                version = '1.0'

                pom {
                    //...
                }
                repositories {
                    maven {
                        url = version.endsWith('SNAPSHOT') 
                      ? snapshotsRepoUrl : releasesRepoUrl
                        val properties = gradleLocalProperties(rootDir)
                        credentials {
                            username = properties.getProperty("OSSRH_USERNAME")
                            password = properties.getProperty("OSSRH_PASSWORD")
                        }
                    }
                }
            }
        }
    }
}

需要注意的是,Android libary 发布比较特殊,需要根据当前的 variant 来生成 sources jar 和 javadoc jar,然后再打包成 aar 产物,这里我们仅考虑 release variant 的情况。最后,我们默认只需要执行 ./gradlew publishReleasePublicationToMavenRepository 即可成功发布组件。

此外,AGP 7.1 引入了新的 DSL,以控制在发布期间使用哪些 build 变体以及忽略哪些 build 变体。借助 DSL,我们可以更加方便地发布具有一个或多个 variant 内容变体的 SoftwareComponent。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 单个内容变体
android {
  publishing {
    singleVariant("release") {
        withSourcesJar()
    }
  }
}
// 多个内容变体
android {
  publishing {
    multipleVariants {
      allVariants()
      withJavadocs()
    }
  }
}

发布 Java/Kotlin 组件

相较于 Android Library,发布 Java 和 Kotlin Library 更加简单些,基本步骤还是差不多的:

  1. 为生成源码和文档 JAR 创建对应的 Task,用于最终集成进发布产物当中
  2. 创建 MavenPublication 对象并声明产物发布的仓库信息(repositories)
  3. 提供发布所需的软件组件声明(SoftwareComponent)
  4. 为发布内容提供签名(可选)

方便理解,直接看以下示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
val sourceSets = the<SourceSetContainer>()
  val javadocJar = tasks.register("javadocJar", Jar::class.java) {
    archiveClassifier.set("javadoc")
    val task = tasks.named(JavaPlugin.JAVADOC_TASK_NAME)
    dependsOn(task)
    from(task)
  }
  val sourcesJar = tasks.register("sourcesJar", Jar::class.java) {
    dependsOn(JavaPlugin.CLASSES_TASK_NAME)
    archiveClassifier.set("sources")
    from(sourceSets["main"].allSource)
  }

  publishing {
    repositories {
      maven {
        url = if (version.endsWith('SNAPSHOT')) snapshotsRepoUrl else releasesRepoUrl
      }
    }
    publications {
      register("maven", MavenPublication::class) {
        groupId = "com.sample"
        artifactId = project.name
        version = "1.0.0-alpha"

        from(components["java"])

        artifact(sourcesJar.get())
        artifact(javadocJar.get())

        pom {
              name.set(PluginInfo.artifactId)
              description.set(PluginInfo.description)
              url.set(PluginInfo.url)

              scm {
                   connection.set(PluginInfo.scmConnection)
                   developerConnection.set(PluginInfo.scmConnection)
                   url.set(PluginInfo.url)
              }

              licenses {
                  license {
                      name.set("The Apache Software License, Version 2.0")
                      url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
                      distribution.set("repo")
                  }
              }

              developers {
                  developer {
                      name.set(PluginInfo.developer)
                      email.set(PluginInfo.email)
                  }
              }
        }
      }
    }
  }

  signing {
    sign(publishing.publications)
  }

插件封装

组件数量较少时,我们上述方式发布也无可厚非。但问题来了,一旦随着我们团队的组件数量持续攀升,每次发布组件都需要关注如此繁杂的配置,这显然会增加出错几率、降低我们的研发效率。因此,我将上述功能封装成一个 Gradle 插件 供其他待发布组件使用,对外屏蔽复杂且重复的发布配置,同时也支持更多组件类型(e.g. Gradle Library)。只需要简单的三步即可帮你完成一切:

1.引入插件

对于 AGP 6.8 之前的项目,可以通过以下方式配置:

项目根目录 build.gradle:

1
2
3
4
5
6
7
8
9
10
11
12
buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "cn.dorck:publish-plugin:1.0.0"
  }
}

apply plugin: "cn.dorck.component.publisher"

AGP 6.8 及之后项目,可以替换成下面新的方式引入插件:

待发布项目 build.gradle:

1
2
3
plugins {
  id "cn.dorck.component.publisher" version "1.0.0"
}
2. 配置发布选项

我们只需要在待发布组件的 build.gradle 中增加如下 dsl 即可:

1
2
3
4
5
publishOptions {
    group = "com.dorck.android"
    version = "0.1.0-LOCAL"
    artifactId = "sample-library"
}

如上所示,倘若我们希望发布一个组件到 mavenLocal,只需如此简单配置即可,本地发布的组件默认存储在 /Users/<username>/.m2/repository 目录下。

更多可配置选项参考:[component-publisher](https://github.com/Moosphan/component-publisher)

3. 发布组件

配置完发布属性后,我们只需要在终端执行如下命令即可开始发布:

1
$ ./gradlew :<component module name>:publishComponent

接下来,如果我们在终端看到如下发布结果日志即代表发布成功:

component_output

从输出的信息来看,组件发布成功后,此插件已经帮我们组装完成了最终的依赖链接,直接复制到项目中同步下即可。

全局配置

通常,我们的存储库 url 和凭证信息对于不同的组件来说一般是相同的。 为防止重复配置,此处支持配置全局默认发布选项。 您可以像这样将全局发布选项放在根项目的 local.properties 中:

1
2
3
4
REPO_USER=Moosphan
REPO_PASSWORD=*****
REPO_RELEASE_URL=https://maven.xx.xx/repository/releases/
REPO_SNAPSHOT_URL=https://maven.xx.xx/repository/snapshots/

如此一来,插件会优先读取待发布组件的 publishOptions 中的配置,如果未设置,则会获取项目本地根目录下 local.properties 中的配置。

开源仓库地址:component-publisher

参考


许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。