Gradle Plugin的开发及发布

news2025/4/17 9:08:31

Gradle Plugin的开发及发布

  • 前言
  • 插件的开发
    • 学习阶段(build.gradle脚本)
    • 开发阶段(buildSrc)
      • Plugin
      • Task
      • Extension
    • 发布阶段(独立module)
      • 发布到本地
      • 发布到MavenCentral
        • 准备sonatype账号
        • 创建pgp证书
        • 配置sonatype和证书
        • 发布Snapshot版本
        • 发布Release版本
  • 疑难杂症
    • JDK路径
    • 发布到仓库
      • Received status code 403 from server: Forbidden
      • It may not be a PGP secret key ring
      • Unknown public key algorithm encountered
    • CLOSE步骤
      • Event: Failed: Javadoc Validation
      • Event: Failed: POM Validation
      • Event: Failed: Sources Validation
  • 结尾

前言

经过前面部署Jenkins自动化打包任务,以及桌面端APK工具的开发,基本已经把国内项目组所面临的问题解决了。但是呢,Jenkins这一套着实是繁琐,而且对海外项目组支持也不友好。所以我又决定开发一个gradle插件来作为一个轻量级的打包发布方案,该方案可以同时兼顾公司海内外的所有项目。整体目标大概就是如下:
1、依赖assembleXxxRelease等Task构建新任务;
2、将打包生成的APK文件上传到FTP服务器;
3、根据服务器上APK文件的地址生成二维码;
4、发送通知消息到钉钉群组;

该插件现已开源,GitHub地址在:ApkPublisherGradlePlugin

插件的开发

gradle插件的开发又涉及多种方式,我们这里在不同的阶段分别使用不同方式进行演示。
首先使用AS新建GradleSample5项目。

学习阶段(build.gradle脚本)

在这个阶段我们只需学习如何创建任务(Task),并且了解如何依赖任务即可。

首先创建任务“activeTask1”,我们需要在app模块下的build.gradle文件中编写如下代码:

tasks.register("activeTask1") {
    doLast {
        println(">>>>> Greeting from activeTask1")
    }
}

点击 Sync Now 后就可以在右侧gradle面板中看到我们新创建的任务了。
Snipaste_2022-11-22_11-41-50.png
双击该任务即可执行,并打印出日志信息:

11:42:39: Executing 'activeTask1'...


> Task :app:activeTask1
>>>>> Greeting from activeTask1

BUILD SUCCESSFUL in 284ms
1 actionable task: 1 executed
11:42:40: Execution finished 'activeTask1'.

接下来,我们需要创建一个activeTask2,该任务需要依赖activeTask1,也就是说执行task2的时候要先执行task1,那么关键就是dependsOn()

tasks.register("activeTask2") {

    dependsOn("activeTask1")

    doLast {
        println(">>>>> Greeting from activeTask2")
    }
}

Snipaste_2022-11-22_11-45-44.png

那么这时候执行activeTask2的话,输出信息则如下所示:

11:46:41: Executing 'activeTask2'...


> Task :app:activeTask1
>>>>> Greeting from activeTask1

> Task :app:activeTask2
>>>>> Greeting from activeTask2

BUILD SUCCESSFUL in 180ms
2 actionable tasks: 2 executed
11:46:41: Execution finished 'activeTask2'.

所以这就是我们后续依赖assemble相关任务所需要学习的一点前置内容了。

开发阶段(buildSrc)

buildSrc是Gradle项目的一个特殊目录,我们先看下官网给的整体介绍。

Complex build logic is usually a good candidate for being encapsulated either as custom task or binary plugin. Custom task and plugin implementations should not live in the build script. It is very convenient to use buildSrc for that purpose as long as the code does not need to be shared among multiple, independent projects.
The directory buildSrc is treated as an included build. Upon discovery of the directory, Gradle automatically compiles and tests this code and puts it in the classpath of your build script. For multi-project builds there can be only one buildSrc directory, which has to sit in the root project directory. buildSrc should be preferred over script plugins as it is easier to maintain, refactor and test the code.
buildSrc uses the same source code conventions applicable to Java and Groovy projects. It also provides direct access to the Gradle API. Additional dependencies can be declared in a dedicated build.gradle under buildSrc.

整体大致意思呢就是,gradle发现buildSrc目录后,会自动编译和测试其中的代码,会优先于其他的脚本。这对我们开发插件来说就非常的便利了,如果我们开发一个插件必须先发布然后再从项目中依赖然后再执行的话,效率就大打折扣了。

我们在项目的根目录下创建一个buildSrc的目录,然后创建一个build.gradle的文件放入该目录下,build.gradle文件内容如下所示:
Snipaste_2022-11-22_17-57-37.png

点击Sync Now后可以看到,buildSrc目录样式变了,说明AS成功识别了该目录。
Snipaste_2022-11-22_17-59-25.png

接下来我们来开发示例插件了,这里我们使用Java而不使用Groovy。因为其一我对Groovy不熟悉,其二Groovy太灵活了,我虽然能写出来相关的代码,可以我无法查看到相关的类提示等,这就导致我好奇某个类、某个变量到底是怎么来的。所以我选择了Java(Kotlin当然也不错),这样我可以明确知道相关类型,也方便查看源码。

Plugin

buildSrc目录建好后我们创建src目录,然后建立插件类MyPlugin并实现Plugin接口如下:
Snipaste_2022-11-22_21-19-33.png
如果这个时候你跟我出现一样的情况,例如上面的红色报错,而且方法中连一个简单的String类都无法导入,那么说明AS的JDK配置出现了问题。请先查看下文疑难杂症篇章,解决后再进行插件的开发。

我们实现apply方法后在其中打印相关信息:

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class MyPlugin implements Plugin<Project> {

    @java.lang.Override
    public void apply(Project project) {
        System.out.println(">>>>> Greeting from buildSrc MyPlugin");
    }

}

然后在buildSrc的build.gradle中声明该插件:

plugins {
    id 'java-gradle-plugin'
}

gradlePlugin {
    plugins {
        pluginDemo {
            id = "my-plugin"
            implementationClass = 'com.vsloong.myplugin.MyPlugin'
        }
    }
}

在app模块下的build.gradle中添加该插件的依赖:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'

    //添加插件的依赖
    id 'my-plugin'
}

Sync后即可看到相关打印信息:


Task :buildSrc:validatePlugins UP-TO-DATE
Task :buildSrc:check UP-TO-DATE
Task :buildSrc:build

Configure project :app
>>>>> Greeting from buildSrc MyPlugin

Task :prepareKotlinBuildScriptModel UP-TO-DATE

BUILD SUCCESSFUL in 13s

这表明我们的插件已经成功运行起来了,接下来需要注册我们自己的Task了。

Task

创建MyTask类继承自DefaultTask,这其中有两点需要注意:
1、在构造函数中添加分组(分组名自行定义),便于我们找到该任务;
2、添加一个doTaskAction()(方法名自行定义)的方法打印相关信息,注意该方法需要使用 @TaskAction 注解;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;

public class MyTask extends DefaultTask {

    public MyTask() {
        setGroup("myTaskGroup");
    }

    @TaskAction
    public void doTaskAction() {
        System.out.println(">>>>> Greeting from buildSrc MyTask");
    }
}

然后将MyTask注册到任务列表中,在MyPlugin的apply()方法中注册我们的任务,任务名是myTask1:

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class MyPlugin implements Plugin<Project> {

    @java.lang.Override
    public void apply(Project project) {
        System.out.println(">>>>> Greeting from buildSrc MyPlugin");

        project.getTasks().register("myTask1", MyTask.class);
    }

}

Sync项目后即可在右侧gradle面板中查看到我们创建的任务组以及任务:
Snipaste_2022-11-23_19-58-25.png
双击myTask1任务即可在控制台查看到相关输出信息:


Task :app:myTask1
>>>>> Greeting from buildSrc MyTask

BUILD SUCCESSFUL in 2s

经过上述的一系列步骤之后我们对Plugin以及Task有了一个大致的了解了,接下来我们回归到开始时候的任务,首先我们需要创建自己的任务并依赖于项目中的assembleRelease等任务。

这里我们可能需要了解下Gradle的生命周期,见官网 Build Lifecycle,具体不再详述。这就好比我们在Activity的生命周期中收到onCreate、onResume等回调一样,在Gradle的生命周期中也有相应的回调,比如我们要用到的afterEvaluate(Action<? super Project> var1),在项目评估完后即可收到该回调,收到该回调后我们就可以获取到项目中的所有任务,并插入自己的任务。

接下来我们先改造下MyTask,因为我们的任务需要依赖于匹配到的assembleXxxRelease任务,所以我们设置一个dependsOnTaskName变量,并且通过构造函数传递进来,然后调用dependsOn()方法进行依赖。

注意:构造函数中添加参数后就需要使用@Inject进行注解:

public class MyTask extends DefaultTask {

    private String dependsOnTaskName;

    @Inject
    public MyTask(String dependsOnTaskName) {
        setGroup("myTaskGroup");
        this.dependsOnTaskName = dependsOnTaskName;
        dependsOn(dependsOnTaskName);
    }

    @TaskAction
    public void doTaskAction() {
        System.out.println(">>>>> Greeting from buildSrc MyTask");
    }
}

然后在MyPlugin的apply()方法中,添加gradle项目评估完成的回调,在回调中获取所有匹配到的assembleXxxRelease任务,然后创建publishXxxRelease任务,同时记得传递需要依赖的任务名给MyTask。

public class MyPlugin implements Plugin<Project> {

    @java.lang.Override
    public void apply(Project project) {
        System.out.println(">>>>> Greeting from buildSrc MyPlugin");

        project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                for (Task task : project.getTasks()) {
                    String taskName = task.getName();
                    if (taskName.startsWith("assemble") && taskName.endsWith("Release")) {
                        String myTaskName = taskName.replace("assemble", "publish");
                        System.out.println(">>>>> 匹配到任务 = " + myTaskName);

                        project.getTasks().register(myTaskName, MyTask.class, taskName);
                    }
                }
            }
        });
    }
}

此时,Sync项目后可以在右侧gradle面板中看到我们创建的任务了:
Snipaste_2022-11-24_11-22-19.png
双击publishRelease任务可以从输出日志中看到,先执行了releaseAssemble任务后再执行的publisherRelease任务:


Task :app:packageRelease

Task :app:assembleRelease

Task :app:publishRelease
>>>>> Greeting from buildSrc MyTask

BUILD SUCCESSFUL in 1m 24s
49 actionable tasks: 43 executed, 6 up-to-date
11:25:03: Execution finished ‘publishRelease’.

上面是未配置项目Flavor的情况,如果项目中有多个Flavor的情况呢?比如我们在app模块下的build.gradle中添加如下Flavor:

android {
    defaultConfig {
        //添加 产品、环境 两个维度
        flavorDimensions "product", "environment"
    }

    productFlavors {
        //产品1
        app1 {
            dimension "product"
        }
    	//产品2
        app2 {
            dimension "product"
        }
    	//开发环境
        develop {
            dimension "environment"
        }
    	//生产环境
        product {
            dimension "environment"
        }
    }
}    

Sync项目后即可在右侧看到我们创建的任务组中多了新增的这些风味:
Snipaste_2022-11-24_11-34-26.png

Extension

要实现文章开头的业务,我们还需要配置FTP地址,端口号,钉钉机器token人等信息,这就需要使用到Extension了。新建MyPluginExtension类:

public class MyPluginExtension {
    public String host;
    public String port;
}

然后在MyPlugin类中创建相关Extension的信息:

public class MyPlugin implements Plugin<Project> {

    @java.lang.Override
    public void apply(Project project) {
        project.getExtensions().create("myExtensionInfo", MyPluginExtension.class);

        //....
    }
}

读取的时候可以在需要的地方使用getExtension()方法读取,比如在MyTask类中:

public class MyTask extends DefaultTask {

    @TaskAction
    public void doTaskAction() {
        MyPluginExtension extension = getProject().getExtensions().getByType(MyPluginExtension.class);

        System.out.println(">>>>> extension.host = " + extension.host);
        System.out.println(">>>>> extension.port = " + extension.port);
    }
}

此时执行相关任务后输出结果肯定为null:

Task :app:publishApp1ProductRelease
>>>>> extension.host = null
>>>>> extension.port = null

BUILD SUCCESSFUL in 1m 7s

在app模块下的build.gradle文件中最外层,添加我们插件所需的配置信息如下:

myExtensionInfo {
    host = "192.168.1.1"
    port = 21
}

然后再执行相关任务就能拿到配置信息了:

Task :app:publishApp1ProductRelease
>>>>> extension.host = 192.168.1.1
>>>>> extension.port = 21

BUILD SUCCESSFUL in 3s

OK,至此我们已经讲解完了和我们的目标相关的Plugin和Task内容了,剩下的关于二维码、FTP以及DingTalk SDK的使用请查看源码吧。

发布阶段(独立module)

经过上面两个阶段的内容我们以及开发完毕一个简陋的插件了,接下来我们先尝试发布到本地文件夹,然后再尝试发布到MavenCentral中。

如果你是初次写插件,我建议你再新建一个工程,然后创建一个新的module将该工程中的buildSrc中的内容全部拷贝到新module中,这样还容易处理一点,不会受上述阶段应用插件和配置插件信息的影响。

这里呢,我是将buildSrc目录直接改名为了 module-plugin-my,然后在setting.gradle文件中包含该模块:

include ':module-plugin-my'

同时需要将app模块下的build.gradle做一下修改,注掉了自己的插件id,注掉了插件的配置信息:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'

//    id 'my-plugin'
}

//myExtensionInfo {
//    host = "192.168.1.1"
//    port = 21
//}

OK,接下来准备发布了。

发布到本地

先在module-plugin-my模块下的build.gradle中添加maven-publish插件,然后配置发布内容:

plugins {
    id 'java-gradle-plugin'

    id 'maven-publish'
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = "io.github.vsLoong"
            artifactId = "my-plugin"
            version = "0.0.1"

            from components.java
        }
    }

    repositories {
        maven {
            //发布到该工程下的localRepo文件夹下
            url = "../localRepo"
        }
    }
}

gradlePlugin {
    plugins {
        apkPublisher {
            id = "my-plugin"
            implementationClass = 'com.vsloong.myplugin.MyPlugin'
        }
    }
}

脚本内容如上所示,Sync工程后,可以在右侧gradle面板中找到publish任务组了:
Snipaste_2022-11-24_14-31-43.png

双击publish任务,构建完毕后即可在工程目录下查看到生成的依赖仓库:
Snipaste_2022-11-24_14-36-50.png

那么如何应用该仓库这里有得好好说说了,7.0后的配置有些变化,但是使用之前的方式的话还是没问题的,这里就演示了通用的写法,在项目的build.gradle文件开头添加如下信息:

buildscript {
    repositories {
        maven {
            url("./localRepo")
        }
    }

    dependencies {
        classpath "io.github.vsLoong:my-plugin:0.0.1"
    }
}

然后在app模块下的build.gradle文件中应用该插件,并添加插件配置信息,Sync项目后即可成功看到我们自定义的Tasks成功显示在gradle面板了:

plugins {
	//......

    id 'my-plugin'
}

myExtensionInfo {
    host = "192.168.1.1"
    port = 21
}

发布到MavenCentral

准备sonatype账号

MavenCentral是由sonatype运营的,所以首先我们需要创建一个sonatype的账号,网上文章非常多了,这里就提一点,现在填写groupId的时候已经不支持com.github开头的命名了,建议使用io.github.yourgroupid。

创建pgp证书

账号注册好并拥有上传权限后,我们还需要的一个东西就是签名证书。类似我们的apk文件需要签名后才能发布到应用市场一样,我们的插件也需要一个签名。创建签名证书的话需要使用GPG4,我是Windows电脑所以使用了win工具,官网地址是:https://gpg4win.org 。我下载的版本是Gpg4win-4.0.4,页面如下所示。
Snipaste_2022-11-24_20-33-39.png

点击 文件-> New OpenPGP Key Pair… ,填写自己的名称和邮箱地址:
Snipaste_2022-11-24_15-53-12.png

填写完后记得点击高级设置,将密钥类型设置为RSA,4096比特,OK后进入创建证书的页面,填写证书密码,这里的密码需要记住后续会用到:
Snipaste_2022-11-24_16-04-56.png

创建好证书后,回到主页面右键该证书,然后导出公钥备份私钥
Snipaste_2022-11-24_16-01-18.png

导出和备份的时候也请务必手动更改其文件的后缀名为pgp:
Snipaste_2022-11-24_16-07-10.png

最后右键你创建好的证书,然后选择“在服务器上发布…”,发布成功后过段时间,在主面板中点击“在服务器上查找”,能找到自己名称的证书的话,则表示这一步已经成功了。
Snipaste_2022-11-24_16-10-57.png

配置sonatype和证书

经过上两步的操作后我们有了sonatype的账号密码以及仓库的上传权限,还有了已经发布到服务器的证书。上传插件到仓库需要这些信息,但这些信息又是不能暴漏到项目中的,否则这些信息被别人拿到岂不是可以随意上传内容到你的仓库了,所以这里的话我们需要将这些信息配置到本地。首先查看你的Gradle user home文件夹在哪里,例如我这里更改到的是D盘下AndroidGradle目录下:
Snipaste_2022-11-24_16-24-19.png

那么我们就将备份的私钥文件拷贝到该目录,然后创建gradle.properties文件并输入如下内容:

# pgp信息
signing.keyId=证书密钥ID的后8位
signing.password=证书的密码
signing.secretKeyRingFile=D\:\\AndroidGradle\\你的私钥文件.pgp

# sonatype账号、密码
ossrhUsername=sonatype账号
ossrhPassword=sonatype密码

发布Snapshot版本

上述的pgp证书及sonatype信息在本地设置好后,编辑module-plugin-my模块下的build.gradle文件(请仔细按照如下脚本内容配置,避免出错):

plugins {
    id 'java-gradle-plugin'

    //发布使用
    id 'maven-publish'
    id 'signing'
}

//发布所需的相关内容
def MAVEN_GROUP_ID = "io.github.vsLoong"
def MAVEN_ARTIFACT_ID = "my-plugin"

def MAVEN_ARTIFACT_VERSION = "0.0.1-SNAPSHOT"
def REPOSITORY_URL_SNAPSHOT = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
def REPOSITORY_URL_RELEASE = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"

gradlePlugin {
    plugins {
        apkPublisher {
            id = MAVEN_ARTIFACT_ID
            implementationClass = 'com.vsloong.myplugin.MyPlugin'
        }
    }
}

// 将源码打包
task publishSourcesJar(type: Jar) {
    classifier = 'sources'
    exclude "**/R.class"  //排除`R.class`
    exclude "**/BuildConfig.class"  //排除`BuildConfig.class`
}

// mavenCentral要求必须有Javadoc的信息
task publishJavadocsJar(type: Jar, dependsOn: publishSourcesJar) {
    archiveClassifier.set('javadoc')
}

afterEvaluate {
    publishing {
        publications {
            maven(MavenPublication) {
                groupId = MAVEN_GROUP_ID
                artifactId = MAVEN_ARTIFACT_ID
                version = MAVEN_ARTIFACT_VERSION

                from components.java

                artifact publishSourcesJar
                artifact publishJavadocsJar

                pom {
                    name = 'Apk publisher gradle plugin'
                    description = 'A gradle plugin that helps upload APK files to FTP servers and generate QR code images with download links.'
                    url = 'https://github.com/vsLoong/ApkPublisherGradlePlugin'
                    licenses {
                        license {
                            name = 'The Apache License, Version 2.0'
                            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                        }
                    }
                    developers {
                        developer {
                            id = 'vsLoong'
                            name = 'vsLoong'
                            email = '你的邮箱地址'
                        }
                    }
                    scm {
                        connection = 'https://github.com/vsLoong/ApkPublisherGradlePlugin.git'
                        developerConnection = 'https://github.com/vsLoong/ApkPublisherGradlePlugin.git'
                        url = 'https://github.com/vsLoong/ApkPublisherGradlePlugin'
                    }
                }
            }
        }

        repositories {
            maven {
                allowInsecureProtocol true
                url = MAVEN_ARTIFACT_VERSION.endsWith('SNAPSHOT') ? REPOSITORY_URL_SNAPSHOT : REPOSITORY_URL_RELEASE
                credentials {
                    username = ossrhUsername
                    password = ossrhPassword
                }
            }
        }
    }
}

// 证书及签名信息
signing {
    sign publishing.publications
}

配置完毕Sync项目后在右侧双击 publishMavenPublicationToMavenRepository(这个名字取决于上述脚本中的相关命名),然后即可成功上传到MavenCentral了:
Snipaste_2022-11-24_16-39-26.png

请注意脚本中的一处版本判断的代码,如果版本号后缀是SNAPSHOT,那么会发布到Snapshots仓库,否则会发布到Releases仓库。发布到Snapshots仓库的话没有那么严格,可以先发布到该仓库进行尝试。
发布到Snapshots仓库成功后可以登录 https://s01.oss.sonatype.org/#welcome 进行查看,此时在项目中也可以依赖到该插件的SNAPSHOT版本。

发布Release版本

发布到Releases仓库的话,你的插件会先放在 暂存库 中,我们需要点击左侧的 Build Promotion -> Staging Repositories,然后选中你发布的插件操作Close以及Release步骤。Close这一步会检测你发布上来内容的各种规范等,如果你在Close的阶段成功了,那么恭喜你少走了很多弯路,我所踩的坑都已经罗列在下文疑难杂症中了,同时上文脚本也基本是最完善的脚本了。

下面是终于close成功的截图,就差点击Release按钮就可以发布了。Release后再耐心等等就可以从Maven Central中搜索到你的插件了,搜索地址在 https://search.maven.org/。
image.png

疑难杂症

JDK路径

也有可能你建立Android工程的时候AS已经提示过你了,例如下图这种情况。
Snipaste_2022-11-22_21-26-34.png
但是离谱的情况就是你按照提示select a JDK 处理完后,即便是选择了自己安装的JDK11的路径,但是AS仍旧不生效。
image.png

这时我们可以查看下 .idea/misc.xml文件,相关配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
    <component name="ExternalStorageConfigurationManager" enabled="true" />
    <component name="ProjectRootManager" default="true" languageLevel="JDK_11"
        project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK" version="2">
        <output url="file://$PROJECT_DIR$/build/classes" />
    </component>
    <component name="ProjectType">
        <option name="id" value="Android" />
    </component>
</project>

project-jdk-name字段的值是Android Studio default JDK,项目左侧的依赖中依旧是Android Studio default JDK。
image.png
所以如果我们使用上面自行安装的JDK11版本的话,直接手动把 project-jdk-name=“Android Studio default JDK” 修改为 project-jdk-name=“11”,然后重启Android Studio即可。

如果这时候你的AS还是无法识别这种情况,那么你就用IntelliJ IDEA打开这个项目,然后IDEA也会提示你JDK有问题,按照提示配置一遍应该也可以了。如果还有其他情况我也束手无策了,我就在这个破问题上卡了一天。

发布到仓库

Received status code 403 from server: Forbidden

Execution failed for task ':apk-publisher-gradle-plugin:publishPluginMavenPublicationToMavenRepository'.
> Failed to publish publication 'pluginMaven' to repository 'maven'
> Could not PUT 'https://s01.oss.sonatype.org/content/repositories/snapshots/ApkPublisherGradlePlugin/apk-publisher-gradle-plugin/unspecified/apk-publisher-gradle-plugin-unspecified.jar'. Received status code 403 from server: Forbidden

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

解决方案添加sign,或者服务器上还未查询到签名。

It may not be a PGP secret key ring

Execution failed for task ':apk-publisher-gradle-plugin:signMavenPublication'.
> Unable to read secret key from file: E:\WorkSpace\ApkPublisherGradlePlugin\vsLoong_0x000000_SECRET.pgp (it may not be a PGP secret key ring)

请务必将导出时候默认的asc后缀,修改为pgp后缀。如果是导出了asc格式的文件后,又手动修改文件名后缀为pgp同样不可以,也会报错如上。
Snipaste_2022-11-24_16-07-10.png

Unknown public key algorithm encountered

请务必在创建时选择高级设置,将密钥类型更改为 RSA。
Snipaste_2022-11-24_16-04-56.png

CLOSE步骤

如果你在编写发布脚本的时候未按照上文脚本添加sourcesJar,javadocJar,以及必须的POM信息,那么在发布Release版本操作close步骤时可能会遇到如下错误,请认真按照上述发布脚本进行检查。
image.png

Event: Failed: Javadoc Validation

typeId javadoc-staging
failureMessage Missing: no javadoc jar found in folder ‘/io/github/vsLoong/apk-publisher-gradle-plugin/1.0.0’

Event: Failed: POM Validation

typeId pom-staging
failureMessage Invalid POM: /io/github/vsLoong/apk-publisher-gradle-plugin/1.0.0/apk-publisher-gradle-plugin-1.0.0.pom: Project name missing, Project description missing, Project URL missing, License information missing, SCM URL missing, Developer information missing

Event: Failed: Sources Validation

typeId sources-staging
failureMessage Missing: no sources jar found in folder ‘/io/github/vsLoong/apk-publisher-gradle-plugin/1.0.0’

结尾

该插件已开源,GitHub地址在:ApkPublisherGradlePlugin 。
最后,祝大家一路披荆斩棘,都能顺利发布好玩的gradle插件吧。

参考文章

  • Android Studio中不能展开jdk源码的问题
  • Maven Publish Plugin
  • Signatory credentials
  • The Signing Plugin
  • The Central Repository Documentation
  • Missing: no javadoc jar found in folder解决办法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/117368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构与算法基础(王卓)(6):带尾指针(单向)循环链表的合并;双链表及其插入和删除的详细操作

带尾指针&#xff08;单向&#xff09;循环链表的合并 PPT(157)&#xff1a; 操作前初始设定预设的条件​​​​我们要做的&#xff0c;即&#xff1a; 让A表尾节点指向B表首结点&#xff0c;让B表尾节点指向A表首结点&#xff1a; Project 1&#xff1a; Status 合并链表(…

NVIDIA NCCL 源码学习(九)- 单机内ncclSend和ncclRecv的过程

上节介绍了通信链路的建立过程&#xff0c;本节介绍下单机内部ncclSend和ncclRecv的运行过程。 单机内的通信都是通过kernel来进行的&#xff0c;所以整个通信的过程可以分为两步&#xff0c;第一步是准备kernel相关的参数&#xff0c;第二步是实际执行kernel的过程。 为方便…

闯关(贪心)

某综艺频道推出了一个闯关活动。 活动一共包含 n 个关卡&#xff08;编号 1∼n&#xff09;&#xff0c;其中 m 个关卡为特殊关卡。 每个关卡都有一个通关分数&#xff0c;其中第 i 个关卡的通关分数为 ai。 挑战者可以自由决定所有关卡的具体挑战顺序&#xff0c;并且每通过…

【工具类】Elasticsearch的HTTP客户端(Java)

一、介绍 1. 原理 Java基于Http请求操作ES&#xff0c;与Kibana上的操作一致。 Kibana上的dsl与Http的关系&#xff1a; GET、POST等同于HTTP的POSTPUT 等同于HTTP的PUTDELETE 等同于HTTP的DELETE 如图 该DSL可转化为HTTP请求 POST ip:port/docwrite/_search 请求体为&…

Mysql online DDL工具:gh-ost

gh-ost特点&#xff1a; 1、不使用触发器。 在gh-ost出现之前第三方MySQL DDL工具均采用触发器的方式进行实现&#xff0c;包括前面percona的pt-osc&#xff0c;Facebook的OSC等等。而gh-ost采用的机制和他们完全不同&#xff1a;它通过MySQL binlog来同步数据。 gh-ost会伪装…

PowerToys 微软效率工具包 使用指南

PowerToys 微软效率工具包 使用教程 Microsoft PowerToys 是一组实用程序&#xff0c;供高级用户调整和简化其 Windows 10 和 11 体验以提高工作效率。 下载 PowerToys⇲ 安装教程 1.双击文件运行 点击我同意 2.等待下载安装完成 3.安装完成 使用指南 Always on Top 通…

FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D)

本教程将在Unity3D中混合Optitrack与数据手套的数据流&#xff0c;在人体运动的基础上&#xff0c;添加双手手指部分的运动。 双手手背的角度仍由Optitrack提供&#xff0c;数据手套提供双手手指的角度。 01 客户端软件 分别安装MotiveBody与MotionVenus并校准人体与数据…

Kafka-Topic创建源码分析

Kafka-Topic创建源码分析 在kafka中,创建topic通过使用kafka-topics.sh脚本或者直接调用AdminClient对外提供的adminApi来进行创建. 即使是使用kafka-topics.sh&#xff0c;其最终会通过生成并调用AdminClient来进行处理. 0,创建topic流程图 1,创建topic示例代码 通过引入A…

工作两年半,终于学会了Jenkins部署Maven项目

上期我们讲了Linux部署Jenkins Linux安装Jenkins&#xff08;Java11最新版&#xff09; 这期我们来讲的是使用Jenkins部署一个maven项目 文章目录&#x1f46e;所需要的环境&#xff08;必须要有&#xff0c;否则不能进行下一步&#xff09;&#x1f64b;第一步&#xff0c;安装…

02 运算符

目录 第一章&#xff1a;概述 第二章&#xff1a;算术运算符 2.1 概述 2.2 应用示例 2.3 号的两种用法 2.4 自增自减运算 2.4.1 概述 2.4.2 单独使用 2.4.3 复合使用 第三章&#xff1a;赋值运算符 3.1 概述 3.2 应用示例 第四章&#xff1a;关系运算符&#xff0…

vue3.0找不到模块“./App.vue”或其相应的类型声明

vue3报错提示 找不到模块“/App.vue”或其相应的类型声明 情况一、vue3.0js 报错原因&#xff1a;javascript只能理解.js文件&#xff0c;无法理解.vue文件。 解决方案&#xff1a;根目录新建jsconfig.json {"compilerOptions": {"baseUrl": "./&qu…

被取消的AP考试到底是什么嘞?

最近&#xff0c;好多考试都延期或取消了&#xff0c;美国大学理事会&#xff08;College Board&#xff09;也发布公告&#xff0c;宣布受疫情影响&#xff0c;上海、北京等地的AP考试正式取消&#xff0c;不提供线上考试的机会&#xff0c;而且不会安排后续补考。 这条消息惊…

芯片漫游指南(5)-- UVM寄存器

目录1.寄存器模型概览1.1 概述1.2 uvm_reg相关概念1.3 MCDF寄存器模型1.4 寄存器建模1.5 模型使用流程2.寄存器模型集成2.1 总线UVC的实现2.2 纵向UVC的示例3.寄存器模型的常规方法3.1 mirror、desired和actual value3.2 prediction的分类3.3 uvm_reg的访问方法3.4 mem与reg的联…

python扩展实现方法--python与c混和编程

大部分的Python的扩展都是用C语言写的&#xff0c;但也很容易移植到C中。 一般来说&#xff0c;所有能被整合或者导入到其它python脚本的代码&#xff0c;都可以称为扩展。 扩展可以用纯Python来写&#xff0c;也可以用C或者C之类的编译型的语言来扩展。 就算是相同的架构的两…

Spring Boot学习篇(三)之通用mapper的使用(oracle版)

Spring Boot学习篇(三)之通用mapper的使用(oracle版) 1 配置pom.xml <parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.7.2</version></parent> <de…

概念辨析|电子文件单轨制与电子档案单套制

电子文件单轨制和电子档案单套制是档案业务中的重要概念&#xff0c;在建设数字中国的时代背景下&#xff0c;厘清“套”与“轨”的区别和联系是革新档案工作的基础 产生背景 随着信息技术的不断发展和政务信息化的持续推进&#xff0c;电子文件逐渐在业务工作中大量出现&…

从工具到实践:如何在GitHub上保障开源项目安全?

1998年&#xff0c;Christine Peterson创造了 “开源软件”这个词。她解释道&#xff1a;“这是刻意为之&#xff0c;为了让其他人更容易理解这个领域”。同年&#xff0c;O’Reilly组织了首届“开源峰会”。 开源软件受到更多人青睐原因在于&#xff0c;用户对软件拥有更多的…

【圣诞节】简单代码实现圣诞树|圣诞贺卡 | 快来为心爱的她送上专属的圣诞礼物叭~

圣诞节马上就要到了&#xff0c;不知道给自己喜欢的人准备什么样的惊喜吗&#xff1f;作为一名程序员&#xff0c;当然是用编程制作专属于她or他的圣诞树&#xff01; 目录 &#x1f384;圣诞树 ✨3D圣诞树 代码块 打开方式 修改位置 效果展示 ✨音乐律动圣诞树 代码块…

详解 Vue 过渡 transition 动画 animation 并结合第三方库 animation.css 和 gsap

transition vue过渡组件 标签自带类名 触发时机默认类名 自定义类名 <transition name"xxx"> 自定义行内式类名 方便结合第三方库 transition 钩子 接收参数el enter 和leave 第二个参数 done 可以 决定 after-enter after-leave 的 周期内的执行时机 v-…

YonBuilder移动开发平台 AVM框架 封装虚拟数字键盘组件

AVM&#xff08;Application-View-Model&#xff09;前端组件化开发模式基于标准Web Components组件化思想&#xff0c;提供包含虚拟DOM和Runtime的编程框架avm.js以及多端统一编译工具&#xff0c;完全兼容Web Components标准&#xff0c;同时兼容Vue和React语法糖编写代码&am…