简化Android库上传到Maven仓库的gradle配置

配置 android 上传到 maven 中心仓库,发现配置的代码有点多,而且如果有多个库模块需要上传,则需要复制粘贴不少重复的配置代码,于是编写了一个 gradle 插件用于简化提取这个配置过程;

插件用途及效果

本插件用于简化Android库上传到Maven中心仓库的配置,避免每个project的gradle中都放置一份重复较多的配置;

插件有如下功能:

  1. 简化 maven-publish 插件的配置流程;
  2. 添加上传到Maven中心仓库的 publish 任务;
  3. 支持配置上传时是否包含 javadoc 及源码;

直接使用maven-publish插件的配置:

import org.gradle.api.publish.maven.MavenPom

plugins {
    id("com.android.library")
    id("signing")
    `maven-publish`
}

android {
    defaultConfig {
        versionName("1.0.0-SNAPSHOT")
    }
}

dependencies {
   // 
}

tasks.register("javadoc", Javadoc::class.java) {
    group = "publishing"
    dependsOn("assemble")
    source = android.sourceSets["main"].java.getSourceFiles()
    classpath += project.files(android.bootClasspath + File.pathSeparator)
    if (JavaVersion.current().isJava9Compatible) {
        (options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
    }
    android.libraryVariants.forEach { libraryVariant ->
        classpath += libraryVariant.javaCompileProvider.get().classpath
    }
    options.apply {
        encoding("UTF-8")
        charset("UTF-8")
        isFailOnError = false

        (this as StandardJavadocDocletOptions).apply {
//            addStringOption("Xdoclint:none")
            links?.add("https://developer.android.google.cn/reference/")
            links?.add("http://docs.oracle.com/javase/8/docs/api/")
        }
    }
}

tasks.register("jarSource", Jar::class.java) {
    group = "publishing"
    from(android.sourceSets["main"].java.srcDirs)
    archiveClassifier.set("sources")
}

tasks.register("jarJavadoc", Jar::class.java) {
    group = "publishing"
    dependsOn("javadoc")
    val javadoc: Javadoc = tasks.getByName("javadoc") as Javadoc
    from(javadoc.destinationDir)
    archiveClassifier.set("javadoc")
}

fun getMyPom(): Action<in MavenPom> {
    return Action<MavenPom> {
        name.set("Android Common Utils Lib")
        description.set("Android Common Utils Library For HJ")
        url.set("https://github.com/hanlyjiang/lib_common_utils")
        licenses {
            license {
                name.set("The Apache License, Version 2.0")
                url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
            }
        }
        developers {
            developer {
                id.set("hanlyjiang")
                name.set("Hanly Jiang")
                email.set("hanlyjiang@outlook.com")
            }
        }
        scm {
            connection.set("scm:git:git://github.com/hanlyjiang/lib_common_utils.git")
            developerConnection.set("scm:git:ssh://github.com/hanlyjiang/lib_common_utils.git")
            url.set("https://github.com/hanlyjiang/lib_common_utils")
        }
    }
}


afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("release") {
                from(components.getByName("release"))
                groupId = "com.github.hanlyjiang"
                artifactId = "android_common_utils"
                version = android.defaultConfig.versionName
                pom(getMyPom())
                // 添加javadoc
                artifact(tasks.getByName("jarJavadoc") as Jar)
                // 添加source
                artifact(tasks.getByName("jarSource") as Jar)
            }
        }

        repositories {
            val ossrhCredentials = Action<PasswordCredentials> {
                username = properties["ossrhUsername"].toString()
                password = properties["ossrhPassword"].toString()
            }
            // sonar的仓库,地址根据项目的版本号来确定是snapshot还是正式仓库
            maven {
                name = "Sonartype"

                val releasesRepoUrl = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
                val snapshotsRepoUrl = uri("https://oss.sonatype.org/content/repositories/snapshots/")
                url = if (android.defaultConfig.versionName.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
                credentials(ossrhCredentials)
                // snapshot的地址:
                // https://oss.sonatype.org/content/repositories/snapshots/com/github/hanlyjiang/android_common_utils/
            }
            // 项目本地的仓库
            maven {
                name = "ProjectLocal"

                val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases"))
                val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots"))
                url = if (android.defaultConfig.versionName.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
            }
        }
    }

    signing {
        sign(publishing.publications.getByName("release"))
    }

}

使用AndroidMavenPubPlugin简化后的配置:

import org.gradle.api.publish.maven.MavenPom


plugins {
    id("com.android.library")
    id("signing")
    `maven-publish`
    // 引入我们本地仓库中的gradle插件
    id("com.github.hanlyjiang.android_maven_pub") version ("0.0.5") apply (false)
}

android {
    defaultConfig {
        versionName("1.0.1-SNAPSHOT")
    }
}

dependencies {
  // 
}

apply(plugin = "com.github.hanlyjiang.android_maven_pub")

configure<io.hanlyjiang.gradle.android.AndroidMavenPubPluginExtension> {
    groupId.set("com.github.hanlyjiang")
    artifactId.set("android-common-utils")
    projectLocalRepoPath.set("local-maven-repo")
    mavenPomAction.set(Action<MavenPom> {
        name.set("Android Common Utils Lib")
        description.set("Android Common Utils Library For HJ")
        url.set("https://github.com/hanlyjiang/lib_common_utils")
        properties.set(
            mapOf(
                "myProp" to "value",
                "prop.with.dots" to "anotherValue"
            )
        )
        licenses {
            license {
                name.set("The Apache License, Version 2.0")
                url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
            }
        }
        developers {
            developer {
                id.set("hanlyjiang")
                name.set("Hanly Jiang")
                email.set("hanlyjiang@outlook.com")
            }
        }
        scm {
            connection.set("scm:git:git://github.com/hanlyjiang/lib_common_utils.git")
            developerConnection.set("scm:git:ssh://github.com/hanlyjiang/lib_common_utils.git")
            url.set("https://github.com/hanlyjiang/lib_common_utils")
        }
    })
}

使用步骤

引入插件

在需要使用的模块的build脚本中,引入我们的插件,同时引入 maven-publish 插件和signing 插件。

kotlin.kts 脚本写法

plugins {
    id("com.android.library")
    
    // 引入signing插件
    id("signing")
    // 引入maven-publish插件
    `maven-publish`
    // 引入 android_maven_pub 插件,注意这里设置 apply 为 false,表示引入但是不应用,我们需要放在android配置段定义之后再应用
    id("com.github.hanlyjiang.android_maven_pub") version ("0.0.5") apply (false)
}

groovy 的写法

plugins {
    id 'com.android.library'
    id 'signing'
    id 'maven-publish'
    id("com.github.hanlyjiang.android_maven_pub") version("0.0.5") apply(false)
}

配置 gradle

在引入 AndroidMavenPubPlugin 插件之后,我们可以对插件进行配置。 建议将脚本转换为 kotlin dsl 的写法,能有对应的自动提示;

kotlin dsl 写法

android {
    
}

// 需要先应用插件,在android配置完成之后,建议放在脚本最下方
apply(plugin = "com.github.hanlyjiang.android_maven_pub")

configure<io.hanlyjiang.gradle.android.AndroidMavenPubPluginExtension> {
    groupId.set("com.github.hanlyjiang")
    artifactId.set("android_common_utils")
    mavenPomAction.set(Action<MavenPom> {
        name.set("Android Common Utils Lib")
        description.set("Android Common Utils Library For HJ")
        url.set("https://github.com/hanlyjiang/lib_common_utils")
        properties.set(
            mapOf(
                "myProp" to "value",
                "prop.with.dots" to "anotherValue"
            )
        )
        licenses {
            license {
                name.set("The Apache License, Version 2.0")
                url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
            }
        }
        developers {
            developer {
                id.set("hanlyjiang")
                name.set("Hanly Jiang")
                email.set("hanlyjiang@outlook.com")
            }
        }
        scm {
            connection.set("scm:git:git://github.com/hanlyjiang/lib_common_utils.git")
            developerConnection.set("scm:git:ssh://github.com/hanlyjiang/lib_common_utils.git")
            url.set("https://github.com/hanlyjiang/lib_common_utils")
        }
    })
}

groovy 脚本写法

apply plugin: "com.github.hanlyjiang.android_maven_pub"

android_maven_pub {
    groupId.set("com.github.hanlyjiang")
    artifactId.set("android-common-utils")
    mavenPomAction.set({ pom ->
        pom.with {
            name.set('HJ Android Plugin Framework')
            description.set("A Android Plugin Framework")
            url.set("https://github.com/hanlyjiang/apf-library")
            licenses {
                license {
                    name = 'The Apache Software License, Version 2.0'
                    url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                }
            }
            developers {
                developer {
                    id = 'hanlyjiang'
                    name = 'hanly jiang'
                    email = 'hanlyjiang@outlook.com'
                }
            }
            scm {
                connection = 'https://github.com/hanlyjiang/apf-library'
                developerConnection = 'https://github.com/hanlyjiang/apf-library.git'
                url = 'https://github.com/hanlyjiang/apf-library'
            }
        }
    } as Action<MavenPom>)
}

⚠️注意

  • 可以看到我们这里并没有定义版本号,版本号从 android.defaultConfig.versionName 中取 ;

  • 同时,如果版本号结尾为 -SNAPSHOT,则会发布到 snapshot 仓库,如果没有,则发布到 release 仓库;

maven仓库的属性文件配置

上传到maven center中心仓库需要进行一些账号申请和key的生成操作,可以参考Jcenter 停止服务,说一说我们的迁移方案 - InfoQ 写作平台 来完成,我们需要将最后获取到的认证信息:

按如下配置,将对应的 value 的值更改为自己的账号及 key 的对应值,然后填入到 ~/.gradle/gradle.properties 中即可

# 配置maven中心仓库访问账号
ossrhUsername=sonatype jira 账号的用户名
ossrhPassword=sonatype jira 账号的密码

# 配置签名信息
signing.keyId=公钥 ID 的后 8 位
signing.password=钥匙串的密码
signing.secretKeyRingFile=导出的 gpg 文件路径 如: /Users/hanlyjiang/.gnupg/secring.gpg 

⚠️注意: 这里的 key 是固定的,不要修改

执行上传任务

经过上面的配置,我们同步下 gradle ,对应引入了插件的项目中会生成若干任务:

在这里插入图片描述

其中:

  • jarJavadoc, jarSource,javadoc 为我们生成的辅助任务
  • publishgenerate 开头的为 mave-publish 插件生成的任务,我们执行 publish 相关的任务即可发布 maven 库;

publish 相关的任务有如下几个:

Task Name Description
publish 等于执行了下面的所有任务
publishAllPublicationsToProjectLocalRepository 将由此项目产生的所有Maven库发布到 ProjectLocal 存储库。 ProjectLocal 定义为当前项目的 build 目录的 mavenRepos 目录中,有两个子目录 snapshotsrelease, 方便查看将要发布的生成物及进行本地测试;
publishAllPublicationsToSonartypeRepository 将由此项目产生的所有 Maven 库发布到 Sonartype 存储库。
publishReleasePublicationToMavenLocal 将由此项目产生的名为release的 Maven库发布到本机 maven 缓存库。
publishReleasePublicationToProjectLocalRepository 将由此项目产生的名为release的 Maven库发布到本机 ProjectLocal 库。
publishReleasePublicationToSonartypeRepository 将由此项目产生的名为release的 Maven库发布到本机 Sonartype 库。
publishToMavenLocal 将由此项目产生的所有 Maven 库发布到 本机 maven 缓存库。

说明:

上面的任务由 maven-publish 的插件生成,该插件生成任务的规则使用 Publications 和 Repo 来组合实现,其中:

  • publication 是我们定义的发布库;android_maven_pub 插件中,我们定义了一个名为release 的配置;
  • repo 是我们定义的 maven 仓库的位置,android_maven_pub 定义了两个仓库的位置,分别名为 ProjectLocal 和 Sonartype ,另外maven-publish会给我们加上一个 MavenLocal 的配置(指向本机 maven 仓库缓存目录)
    • 其中 ProjectLocal 指向引入了该插件的项目(模块)的 build/repos 目录
    • Sonartype 则默认指向 maven 中心仓库的地址,可以通过配置来更改;

可配置项目说明

配置字段 说明 默认值
groupId maven 的 group id,需要设置为自己申请的 无默认值-必需填写
artifactId library 的 id 无默认值-必需填写
mavenPomAction 用于配置 pom 信息的字段 无默认值-必需填写
fromAndroidPubName 表示发布的 android 的 aar 的类型,releasedebug release
releasesRepoUrl maven release 仓库的上传地址 https://oss.sonatype.org/service/local/staging/deploy/maven2
snapshotsRepoUrl maven snapshots 仓库的上传地址 https://oss.sonatype.org/content/repositories/snapshots/
includeSourceJar 是否上传源码 true
includeJavadocJar 是否上传 javadoc true
projectLocalRepoPath 本地仓库的目录,相对于rootProject的目录,如:local-maven-repo 默认位于根项目的 build/mavenRepos 目录中

常见问题

发布到 snapshot

我们根据版本号来选择发布到的是 snapshot 还是 release 仓库,版本号从 android->defaultConfig - versionName 中读取,如:

android {
    compileSdkVersion(30)
    buildToolsVersion("30.0.3")

    defaultConfig {
        minSdkVersion(22)
        targetSdkVersion(30)
        versionCode(1)
        versionName("1.0.0-SNAPSHOT")
    }
}

发布到本地并进行测试

首先我们通过 projectLocalRepoPath 来改变 ProjectLocal 仓库的目录,下面的示例中将其设置为 rootProject 的 local-maven-repo 目录中

apply(plugin = "com.github.hanlyjiang.android_maven_pub")

configure<io.hanlyjiang.gradle.android.AndroidMavenPubPluginExtension> {
    groupId.set("com.github.hanlyjiang")
    artifactId.set("android-common-utils")
    projectLocalRepoPath.set("local-maven-repo")
    // ...
}

接下来我们定义本地的 Repo ,见下方名为 ProjectLocal-SnapshotsProjectLocal-Release 的仓库。

allprojects {
    repositories {
        maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
        maven { setUrl("https://maven.aliyun.com/repository/google") }
        maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
        maven { setUrl("https://maven.aliyun.com/repository/public") }
        // 定义本地repo路径
        maven {
            name = "ProjectLocal-Snapshots"
            setUrl(File(rootProject.rootDir, "local-maven-repo${File.separator}snapshots"))
        }
        maven {
            name = "ProjectLocal-Release"
            setUrl(File(rootProject.rootDir, "local-maven-repo${File.separator}release"))
        }
        maven {
            name = "Sonatype-Snapshots"
            setUrl("https://oss.sonatype.org/content/repositories/snapshots")
//            setUrl("https://s01.oss.sonatype.org/content/repositories/snapshots")
            // snapshot可以不用用户名密码
            // 查看自己的snapshot版本: https://oss.sonatype.org/content/repositories/snapshots/com/github/hanlyjiang/
            credentials(PasswordCredentials::class.java) {
                username = property("ossrhUsername").toString()
                password = property("ossrhPassword").toString()
            }
        }
        maven {
            name = "Sonatype-Staging"
            setUrl("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
//            setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials(PasswordCredentials::class.java) {
                username = property("ossrhUsername").toString()
                password = property("ossrhPassword").toString()
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容