使用Gradle插件上传组件搭配Maven仓库的奇技淫巧

1,890 阅读6分钟

常规上传方式

为了避免重复功能的编码工作,我们通常会把一些公共组件打包上传到Maven仓库,而上传的功能通常是使用

Gradle提供的mavenmaven-publish来实现,如下:

maven插件(已废弃)

plugins {
    id 'maven'
}

//配置仓库和版本信息
ext {
    libGroup = "com.yumdao.daydayup"
    libArtifactId = "demo01"
    libVersion = "1.0.0"

    mavenUrl = libVersion.endsWith("SNAPSHOT") ? "http://localhost:8081/repository/maven-snapshots/" : "http://localhost:8081/repository/maven-app/"
    mavenAccount = "admin"
    mavenPassword = "admin123"
}

//定义上传方法
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: mavenUrl) {
                authentication(userName: mavenAccount, password: mavenPassword)
            }

            pom.groupId = libGroup
            pom.artifactId = libArtifactId
            pom.version = libVersion
        }
    }
}

然后就可以通过该模块Tasksupload分组里的uploadArchives完成上传到maven仓库的功能。不过maven插件在Gradle 7.x版本已经被移除,不建议使用该方式。

maven-publish插件

plugins {
    id 'maven-publish'
}


ext {
    libGroup = "com.yumdao.daydayup"
    libArtifactId = "demo01"
    libVersion = "1.0.1"

    mavenUrl = libVersion.endsWith("SNAPSHOT") ? "http://localhost:8081/repository/maven-snapshots/" : "http://localhost:8081/repository/maven-app/"
    mavenAccount = "admin"
    mavenPassword = "admin123"
}

//在afterEvaluate后才能获取到buildTypes里的release和debug
afterEvaluate {
    publishing {
        publications {
            //配置生成上传release节点的task
            release(MavenPublication) {
                from components.release

                groupId = libGroup
                artifactId = libArtifactId
                version = libVersion
            }

            //如果有需要,可以配置上传debug版本,也可以只上传release版本
            debug(MavenPublication) {
                from components.debug
                groupId = libGroup
                artifactId = libArtifactId
                version = libVersion
            }
        }

        repositories {
            maven {
                url = mavenUrl
                credentials {
                    username = mavenAccount
                    password = mavenPassword
                }
            }
        }
    }
}

配置完成之后可以在模块的Taskspublishing分组下生成两个新的task,分别为publishReleasePublicationToMavenRepositorypublishDebugPublicationToMavenRepository,对应的是打包release或debug后上传到maven仓库。

常规方式的不足

虽然能够通过上面的配置,很容易实现组件上传功能,但是确有以下一些问题:

问题1:如果维护的组件数量比较多,每个都需要这么配置,会比较繁琐。

问题2:上传到Maven之前没有进行版本校验,如果一不小心忘记升级版本号,会导致原版本被覆盖,而且在使用时也会有缓存问题。

问题3:无法有效管理版本更新日志,通常是在发布版本之后手动修改README来实现。

解决方案

方案1:可以使用自定义gradle脚本或插件,把核心上传功能封装起来,只需要对外暴露必要的参数,最大程度上降低配置的繁琐度,为了避免自定义gradle脚本到处copy的问题,可以把脚本托管到服务器端,使用远程依赖的方式完成,例如:

//使用文件服务器托管脚本
apply from: "http://localhost:8080/gradle/upload.gradle"

远程依赖的方式适合简单的脚本,但如果想要对功能现实增强的话,会在单个文件中编写大量的代码,不利于后期脚本的维护迭代,而且在开发时,gradle脚本的api对于开发者来说并不友好,相信写过脚本的同学都深有体会。

所以,我选择用插件的方式来对上传功能进行封装。

方案2:在打包上传Maven仓库之前,需要做一些前置操作,判断当前上传配置项是否规范以及线上是否存在相同的版本号。

方案3:在原始的打包上传中,我们升级万类库之后一般需要手动修改README对当前版本更新的内容进行描述,这样做起来相当繁琐,而且也不能直观的查看各个历史版本信息,如果把更新信息放到配置里去,在上传Maven仓库后上传到服务器端,统一管理起来,然后用页面的形式展现,无疑会更直观。

脚本实现

1、创建自定义参数类

//该类要是open的才可以
open class Publisher {

    companion object {
        const val NAME = "publisher"
    }

    //配置仓库信息
    var repoRelease: String = ""
    var repoSnapshot: String = ""

    //配置账户名和密码
    var repoAccount: String = ""
    var repoPassword: String = ""

    //配置library版本信息
    var libGroup: String = ""
    var libArtifact: String = ""
    var libVersion: String = ""

    //当前版本的更新描述
    var libUpdateDesc: ArrayList<String> = arrayListOf()

    //版本更新描述
    var pomDesc: String = ""
    var pomName: String = ""
    var pomUrl: String = ""

    //发布人
    var publisher: String = ""
}

2、创建自定义Task

//该类要是open的才可以
open class PublisherTask : DefaultTask() {

    //是否完成执行任务
    private var executeFinishFlag: AtomicBoolean = AtomicBoolean(false)

    //检验状态是否通过
    private var checkStatus = false

    private lateinit var publisher: Publisher

    init {
        group = "upload"

        project.run {
            publisher = extensions.getByName(Publisher.NAME) as Publisher

            //动态为该模块引入上传插件
            apply(hashMapOf<String, String>(Pair("plugin", "maven-publish")))
            val publishing = project.extensions.getByType(PublishingExtension::class.java)

            afterEvaluate {
                components.forEach {
                    if (it.name == "release") {
                        publishing.publications { publications ->
                            //注册上传task
                            publications.create("release",
                                MavenPublication::class.java) { publication ->
                                publication.groupId = publisher.libGroup
                                publication.artifactId = publisher.libArtifact
                                publication.version = publisher.libVersion

                                publication.from(it)
                            }
                        }

                        publishing.repositories { artifactRepositories ->
                            artifactRepositories.maven { mavenArtifactRepository ->
                                mavenArtifactRepository.url =
                                    if (publisher.libVersion.endsWith("SNAPSHOT")) {
                                        URI(publisher.repoSnapshot)
                                    } else {
                                        URI(publisher.repoRelease)
                                    }
                                mavenArtifactRepository.credentials { credentials ->
                                    credentials.username = publisher.repoAccount
                                    credentials.password = publisher.repoPassword
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @TaskAction
    fun doTask() {

        executeTask()

        //开启线程守护,防止子线程任务还没执行完毕,task就已经结束了
        while (!executeFinishFlag.get()) {
            Thread.sleep(500)
        }
    }

    private fun executeTask() {
        //1、对publisher配置的信息进行基础校验
        //2、把publisher上传到服务器端,做版本重复性校验
        checkStatus = requestCheckVersion()

        //如果前两步都校验通过了,checkStatus设置为true

        if (checkStatus) {
            val out = ByteArrayOutputStream()
            //通过命令行的方式进行调用上传maven的task
            project.exec { exec ->
                exec.standardOutput = out
                exec.isIgnoreExitValue = true
                exec.commandLine(
                    "${project.rootDir}/gradlew",
                    "publishReleasePublicationToMavenRepository"
                )
            }
            val result = out.toString()
            if (result.contains("UP-TO-DATE")) {
                //上传maven仓库成功,上报到服务器
                val isSuccess = requestUploadVersion()
                if (isSuccess) {
                    //提示成功信息
                } else {
                    //提示错误信息
                }
                executeFinish()
            } else {
                throw Exception("上传Maven仓库失败,请检查配置!")
            }
        }
    }

    private fun requestCheckVersion(): Boolean {
        //TODO 上报服务器进行版本检查,这里直接模拟返回成功
        return true
    }

    private fun requestUploadVersion(): Boolean {
        //TODO 上报服务器进行版本更新操作,这里直接模拟返回成功
        return true
    }

    /**
     * 任务执行完毕
     */
    private fun executeFinish() {
        executeFinishFlag.set(true)
    }
}

3、创建自定义Plugin并注册参数及Task

class DDPlugin : Plugin<Project> {

    override fun apply(project: Project) {
        //注册自定义参数
        project.extensions.create(Publisher.NAME, Publisher::class.java)

        val currProjectName = project.displayName

        //在afterProject生命周期之后,才可以从build.gradle文件中获取到配置的参数
        project.gradle.afterProject { currProject ->

            //如果是当前模块,才进行task的注册,避免冗余注册
            if (currProjectName == currProject.displayName) {
                //注册上传task,这里不要dependsOn
                project.tasks.create("publishToMaven", PublisherTask::class.java)
            }
        }
    }
}

4、在module中配置插件

plugins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'com.yumdao.daydayup' //声明插件
}
//配置参数
publisher {
    //配置release版本的仓库地址
    repoRelease = "http://localhost:8081/repository/maven-app/"
    //配置快照版本的仓库地址
    repoSnapshot = "http://localhost:8081/repository/maven-snapshots/"
    //账号
    repoAccount = "admin"
    //免密
    repoPassword = "admin123"

    //上报组件信息三件套
    libGroup = "com.yumdao.test"
    libArtifact = "daydayup"
    libVersion = "1.0.0-SNAPSHOT"

    //更新日志
    libUpdateDesc = [
            "1.发布新版本",
    ]

    //配置项目信息
    pomName = "DayDayUp"
    pomUrl = "http://daydayup.com"
    pomDesc = "这仅仅是一个测试项目!"

    //发布人
    publisher = "wangyumdao"
}

Sync项目之后,会在该模块Tasks下出现upload分组,里面有一个名为publishToMaven的Task,运行Task即可完成上传功能。

打包完成之后配置信息需要上传到服务器端,由服务器记录,虽然需要服务器端的支持才能完成组件的上报功能,不过问题不大,这年头谁还不会搭一个服务器呢(手动狗头)。

最后,献上Demo地址