当前位置: 首页>編程日記>正文

Gradle插件 protobuf自动编译

Gradle插件 protobuf自动编译

前言

protobufGoogle推出的序列化协议,比json所占的字节更小,序列化更快等特点。本文简单介绍协议特别点然后介绍如何编写一个Gradle插件实现自动编译。

protobuf Github地址
protobuf 语法教程

一个直观的例子:

  val protoBufPerson = AddressBookProtos.Person.newBuilder().setEmail("xxx@qq.com").setId(23).setName("王五").build()val jsonPerson = """{"email":"xxx@qq.com","id":23,"name":"王五"}"""Log.e("MainActivity", "protoBufPerson :${protoBufPerson.serializedSize}  jsonPerson :${jsonPerson.toByteArray().size}")

输出:

 protoBufPerson :22  jsonPerson :46

可见protobuf序列化后是json的数据体的一半左右。具体性能相关可以参考官网。

Tip:移动端可以考虑用lite版本减少生成的类体积

protobuf 使用流程

由于官网有详细的介绍,这里只做简单描述。

1 编辑proto文件

syntax = "proto2";package tutorial;option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";message Person {optional string name = 1;optional int32 id = 2;optional string email = 3;}

proto语法是跨语言的,所以我们需要需要对应平台的编译器工具,编译成java文件,比如笔者在MacOs下,需要下载这个平台的编译工具。

2 执行编译工具,将中间语法文件编译成java文件:

protoc --java_out=输出目录  编译文件   -I=编译文件所在的文件夹

3 拷贝生成java文件到工程

为了提高开发效率,大神们早就推出了gradle插件帮我们完成自动编译后自动加入工程目录。

如下将proto文件放入目录即可 直接使用Person.proto编译后产物
在这里插入图片描述

开始编写内嵌插件

gradle如果仅仅为本工程使用,可以在当前目录创建一个特殊目录buildSrc,然后在buildSrc目录放置build.gradle文件即可,如果你插件需要额外的配置可以自行在build.gradle添加依赖。Developing Custom Gradle Plugins.

本例在一个Android工程目录下做演示:

├── AndroidProtpPlugin.iml
├── app
│   ├── build.gradle
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
├── build.gradle
├── buildSrc
│   └── build.gradle
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

基础类编写

首先编辑一个插件类,实现Plugin接口即可

//MyProtoPlugin.java
public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);//插件被应用的时候回调@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");}
}

然后在app模块下的build.gradle下应用

//build.gradle
apply plugin:MyProtoPlugin

运行./gradlew :app:help

输出:

./gradlew -q :app:help
MyProtoPlugin 插件被激活

获取用户配置proto文件目录

我们插件需要用户告诉需要编译的proto文件位置,所以我们暴露一个DSL给开发者自定义。

public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");/*** 创建一个插件DSL扩展,第一个参数为DSL名字,第二个为配置类*  比如:*     protoConfig{*             // protoDirPath为MyProtoConfigExtension内部属性*             protoDirPath = "张三"**     }*/project.getExtensions().create("protoConfig", MyProtoConfigExtension.class);//等project配置阶段完毕输出 用户配置project.afterEvaluate(project1 -> {MyProtoConfigExtension configExtension = project1.getExtensions().getByType(MyProtoConfigExtension.class);logger.log("用户配置的目录" + configExtension.protoDirPath);});}
}
public class MyProtoConfigExtension {//要编译的proto文件目录String protoDirPath;public String getProtoDirPath() {return protoDirPath;}public void setProtoDirPath(String protoDirPath) {this.protoDirPath = protoDirPath;}
}

插件应用处修改代码

//build.gradle
apply plugin: MyProtoPlugin
//配置要编译protobuf文件位置
protoConfig {protoDirPath = "src/main/proto"
}

获取用户当前系统对应proto编译器

有的开发者使用Mac,或者linux,我们需要根据此选择一个对应版本的编译器去编译proto文件。
Google发布了一个artifact: com.google.protobuf:protoc
我们看工件(artifact)目录:
在这里插入图片描述
我们可以看到这个工件中有很多的编译器版本。但是假设我们只想拿mac的编译器怎么办?我们可以看下protoc-xxx.pom文件
我们可以看到pom提供类分类器(classifier)供我们选择.(classifier是maven的基础知识,不清楚的同学可以看下网上轮子,可以简单理解一个artifact具有 组织名,工件名称,版本号,可选分类器,可选文件后缀ext等)
在这里插入图片描述
protoc文件目录

比如我们需要mac下的编译器,依赖写法如下

dependencies {implementation group: 'com.google.protobuf', name: 'protoc', version: '3.14.0',classifier:'osx-x86_64',ext:'exe'
}

上述我们知道了如何从artifact取出对应平台的编译器,那么我们如何判断当前gradle执行环境的操作系统呢?我们可以使用google提供的插件帮助我们

osdetector-gradle-plugin

我们给插件目录buildSrc下的build.gradle添加依赖

repositories {maven {url 'https://mirrors.huaweicloud.com/repository/maven/'}google()jcenter()
}
dependencies {implementation 'com.google.gradle:osdetector-gradle-plugin:1.6.2'
}

继续编辑插件


public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {//...//利用osdetector得到对应系统分类器OsDetector osDetector = new OsDetector();logger.log("当前操作系统的分类器 " +osDetector.getClassifier());//...}
}

输出

当前操作系统的分类器 osx-x86_64

我们最后获取protoc编译器maven即可

OsDetector osDetector = new OsDetector();logger.log("当前操作系统的分类器 " + osDetector.getClassifier());//这个名字用于创建一个configuration,configuration可以简单理解为管理一组依赖的管理器//implementation 和testImplementation就是其中一个管理器String MycName = "customPluginConfiguration";//创建一个管理器名字为customPluginConfigurationConfiguration configuration = project.getConfigurations().create(MycName);//这个是构造proto的artifact的信息,这些信息可以查看pom文件得知HashMap<String, String> protocArtifactMap = new HashMap<>();protocArtifactMap.put("group", "com.google.protobuf");protocArtifactMap.put("name", "protoc");protocArtifactMap.put("version", "3.14.0");protocArtifactMap.put("classifier", osDetector.getClassifier());protocArtifactMap.put("ext", "exe");//添加依赖到MycName这个管理器中Dependency protoDependency = project.getDependencies().add(MycName, protocArtifactMap);//用管理器返回这个依赖的所有的文件FileCollection files = configuration.fileCollection(protoDependency);//因为这个依赖只会存在一个文件也就是编译器File protoExe = files.getSingleFile();logger.log("获得的平台编译器 " + protoExe.getAbsolutePath());

执行编译protoc文件

上面我们得到信息:

  1. 本地平台的protoc编译器,如果mac版本编译器
  2. 工程proto文件路径

我们编写一个专门编译和输出结果的Task

//CompilerProtoTask.java
public class CompilerProtoTask extends DefaultTask {Logger logger = Logging.getLogger(CompilerProtoTask.class);@InputString protoDir;//输出编译后的文件夹@OutputDirectoryString outGeneratedDir;{outGeneratedDir = getProject().getBuildDir() + "/generated/source/protos/";}@TaskActionvoid action() {OsDetector osDetector = new OsDetector();//这个名字用于创建一个configuration,configuration可以简单理解为管理一组依赖的管理器String MycName = "customPluginConfiguration";//创建一个管理器名字为customPluginConfigurationConfiguration configuration = getProject().getConfigurations().create(MycName);//这个是构造proto的artifact的信息,这些信息可以查看pom文件得知HashMap<String, String> protocArtifactMap = new HashMap<>();protocArtifactMap.put("group", "com.google.protobuf");protocArtifactMap.put("name", "protoc");protocArtifactMap.put("version", "3.14.0");protocArtifactMap.put("classifier", osDetector.getClassifier());protocArtifactMap.put("ext", "exe");//添加依赖到MycName这个管理器中Dependency protoDependency = getProject().getDependencies().add(MycName, protocArtifactMap);//用管理器返回这个依赖的所有的文件FileCollection files = configuration.fileCollection(protoDependency);//因为这个依赖只会存在一个文件也就是编译器File protoExe = files.getFiles().stream().findFirst().get();try {//获得扩展类对象实例,主要用于获取用户配置的proto文件路径String protoDirPath = protoDir;File file1 = new File(getProject().getProjectDir(), protoDirPath);//得到用户配置proto文件目录下的所有后缀为proto的文件String[] extensionFilter = {"proto"};Collection<File> protoDifFile = FileUtils.listFiles(new File(getProject().getProjectDir(), protoDirPath), extensionFilter, false);//拼接命令行字符串StringBuilder cmd = new StringBuilder(protoExe.getAbsolutePath() + " ");File outFile = new File(outGeneratedDir);if (!outFile.exists()) {outFile.mkdirs();}cmd.append("--java_out=" + outGeneratedDir);for (File file : protoDifFile) {String replaceFilePath = " " + file.getPath().replaceFirst(file1.getAbsolutePath() + "/", "") + " ";cmd.append(replaceFilePath);}cmd.append(" -I" + protoDirPath + " ");logger.info("运行编译命令 " + cmd);//防止编译器无权限运行if (!protoExe.canExecute() && !protoExe.setExecutable(true)) {throw new GradleException("protoc编译器无法执行");}//执行命令行Process exec = null;try {String[] strings = new String[0];exec = Runtime.getRuntime().exec(cmd.toString(), strings, getProject().getProjectDir());int resultCode = exec.waitFor();//执行成功if (resultCode == 0) {} else {throw new GradleException("编译proto文件错误" + IOUtils.toString(exec.getErrorStream()));}} finally {if (exec != null) {exec.destroy();}}} catch (Exception e) {e.printStackTrace();}}
}

简单来说上面的Task就是得到编译器然后执行shell命令编译出源文件.

我们将上面的Task 整合到插件中

public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");/*** 创建一个插件DSL扩展,第一个参数为DSL名字,第二个为配置类*  比如:*     protoConfig{*             // protoDirPath为MyProtoConfigExtension内部属性*             protoDirPath = "张三"**     }*/project.getExtensions().create("protoConfig", MyProtoConfigExtension.class);//在evaluate之后添加task到gradle中project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {/*** 创建任务并设置输出目录*/CompilerProtoTask compilerProto = project.getTasks().create("compilerProto", CompilerProtoTask.class);
//                compilerProto.onlyIf(new );compilerProto.setGroup("proto");MyProtoConfigExtension myProtoConfigExtension = project.getExtensions().getByType(MyProtoConfigExtension.class);compilerProto.protoDir = myProtoConfigExtension.protoDirPath;}});}
}    

执行如下命令会在build/granerated/source/protos生成java文件

./gradlew compilerProto

在这里插入图片描述
虽然插件生成源文件,但是java编译器不知道这个文件要被打包到工程中。比如生成了A.java,我们想要gradle自动帮我编译这个文件并最后打包到jar或者apk中.

关联插件生成类到编译路径中

我们上面生成了类文件,但是还没有关联到编译路径中,也就是我们生成的类不会打包到Jar中或者Android的apk中.

我们需要知道当前是java工程还是Android工程.

Android工程处理源码关联

对于Android的工程来说会引用AGP无非两种:

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'com.android.library'

前者是Apk应用,后者是Android 类库.

我们在插件buildSrc下的build.gradle添加Android插件依赖:

dependencies {//...implementation  'com.android.tools.build:gradle:4.2.0-alpha16'//..
}

于是乎我们判断应用插件的工程是否为Android的逻辑如下:

 //是Android工程boolean isAndroidProject(Project project) {return project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class) || project.getPlugins().hasPlugin(com.android.build.gradle.LibraryPlugin.class);}

apply plugin: 'com.android.application'应用的是AppPlugin
apply plugin: 'com.android.library'应用的是LibraryPlugin

关于为什么一个字符串可以关联到某个插件类,我们这里简单讲下,插件开发者长传的时候会配置如下内容到gradle中

gradlePlugin {plugins {create("simplePlugin") {id = "org.samples.greeting"implementationClass = "org.gradle.GreetingPlugin"}}
}

这个插件配置会在发布的时候生成一个数据位于
src / main / resources / META-INF / gradle-plugins / org.samples.greeting.properties
内容如下

implementation-class=org.gradle.GreetingPlugin

上面我们知道如何判断Android工程和Java工程(不是Android工程那就是Java工程),但是还没告诉Android插件将我们的生成proto目录加入编译路径中,但是AGP提供了对应的函数方便我们操作。

class BaseVariant{void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
}

本文为了防止读者读Android Gradle插件不熟悉这里解释说明下构建变体和风味:

如下案例:

android{//构建类型buildTypes {release {}debug {}}//风味维度flavorDimensions "version", "hha"//产品风味productFlavors {demo {dimension "version"applicationIdSuffix ".demo"versionNameSuffix "-demo"}full {dimension "version"applicationIdSuffix ".full"versionNameSuffix "-full"}nnimei {dimension "hha"applicationIdSuffix ".full"versionNameSuffix "-full"}}
}

上面会将不同维度的产品风味组合在一起,最后组合到构建类型。

如上案例:两个维度version, hha组合产品风味,就有两种结果分别为:

1. fullnnimei
2. demonnimei

结合构建类型

1.fullnnimeiDebug
2.demonnimeiDebug
3.fullnnimeiRelease
4.demonnimeiRelease

这样便生成了四种最终变体。
在这里插入图片描述
但是还有一种类型变体叫做测试环境变体,也就是在单元测试的时候使用的变体,而测试分两种一种Android 的测试和一种Junit的本地测试。

Android 的测试默认情况只会生成Debug构建类型的变体

 1. demoNnimeiDebugAndroidTest2. fullNnimeiDebugAndroidTest

如果你想添加/输出/管理 测试依赖 可以用testVariants

android{testVariants.all { variant->//可在此配置变体的信息println "变体 ${variant.name}"}
}

输出:

变体 demoNnimeiDebugAndroidTest
变体 fullNnimeiDebugAndroidTest

Junit则会生成全部的构建类型变体,你可以用unitTestVariants管理/添加/输出:

unitTestVariants.all{variant->//可在此配置变体的信息println "unitTestVariants 变体 ${variant.name}"}

输出:

unitTestVariants 变体 demoNnimeiDebugUnitTest
unitTestVariants 变体 fullNnimeiDebugUnitTest
unitTestVariants 变体 demoNnimeiReleaseUnitTest
unitTestVariants 变体 fullNnimeiReleaseUnitTest

综上我们知道变体有三种:

  1. 开发变体
  2. Android测试变体
  3. Junit测试变体

Android Gradle变体提供的所有功能读者可自行参阅文档。我们只需要知道现在他提供了添加registerJavaGeneratingTask帮助我们完成关联类路径。

回过头来我们看下这个函数

class BaseVariant{void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
}

BaseVariant表示某个变体如demoNnimeiDebug等。

registerJavaGeneratingTask关联一个task,编译时会自动运行这个任务,sourceFolders文件下的所有java/kotlin文件会自动加入编译路径

			//如果当前是android工程链接生成的源码路径到编译路径if (isAndroidProject(project)) {linkAndroidProject(project);} else {//是一个java工程}void linkAndroidProject(Project project) {if (project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class)) {//当前是Android 应用工程//Android 插件提供了扩展// 也就是我们经常的写法////  android{////  }//AppExtension extension = (AppExtension) (project.getExtensions().getByName("android"));extension.getApplicationVariants().all(configurationAndroidVariant(project));extension.getTestVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));extension.getApplicationVariants().all(new Action<ApplicationVariant>() {@Overridepublic void execute(ApplicationVariant applicationVariant) {System.out.println("Android 正式环境变体  "+applicationVariant.getName() );}});extension.getTestVariants().all(new Action<TestVariant>() {@Overridepublic void execute(TestVariant testVariant) {System.out.println("Android 测试环境变体 "+testVariant.getName() );}});} else {//当前是Android lib工程LibraryExtension extension = (LibraryExtension) (project.getExtensions().getByName("android"));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));}}
private Action<BaseVariant> configurationAndroidVariant(Project project) {return new Action<BaseVariant>() {@Overridepublic void execute(BaseVariant libraryVariant) {CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");//applicationVariant.addJavaSourceFoldersToModel();libraryVariant.registerJavaGeneratingTask(compilerProto, new File(compilerProto.outGeneratedDir));}};}

Java工程处理源码关联

相对Android工程,Java工程概念就要少很多。
主要是SourceSet的概念。
SourceSet可以简单理解为Java工程目录管理器,比如如下配置:

sourceSets {//自定义的一个源集合mySource {java.srcDir("src/myjava")resources.srcDir("my/res")}//系统默认提供main {}
}

系统默认定义了默认的SourceSet叫做main.默认的java文件目录为src/main/java,资源目录为src/main/resouce.每个SourceSet之间源码不可以相互访问
在这里插入图片描述
下面的代码就会抛出找不到符号错误

public class MainJava {public static void main(String[] args) {//不能访问另一个SourceSet代码MyJavaClass myJavaClass = new MyJavaClass();}
}

java插件在SourceSet中提供了大量的Task帮助我编译某个SourceSet代码。
在这里插入图片描述
当然在默认情况我们运行Jar命令只会打包main的java文件,而不会打包其他SourceSet的代码。
比如下面运行
./gradlew jar
得到一个jar压缩包,内容如下:
在这里插入图片描述
没有mysourceJar的任何资源。但是如果你希望打包非main资源请自定义如下任务。

tasks.create("mysourceJar", Jar) {from(sourceSets.mySource.output)
}

每个SourceSet都提供非常多的属性帮助我们使用,如上output表示这个SourceSet编译后输出的class文件和资源文件目录。然后我们创建一个已有的Jar任务扩展即可.关于自定任务可以参阅官网。

JavaGradle插件说明

我们现在了解了Java插件的基础知识后,回到我们的主题,如何把protoc输出目录加入编译路径上?
我们只需要将我们的编译输出proto文件的输出目录加入sourceSet即可.

 void linkJavaProject(Project project) {SourceSetContainer container = project.getExtensions().getByType(SourceSetContainer.class);//遍历所有源集合 比如main等for (SourceSet sourceSet : container) {//getCompileTaskName用于获取这个sourceSet提供的编译java文件的task//比如我我有一个SourceSet名为MySource,那么编译任务名称为compileMySourceJava//利用这个函数我们快速拿到这个Task的名称String compileName = sourceSet.getCompileTaskName("java");//获取这个Task实例JavaCompile javaCompile = (JavaCompile) project.getTasks().getByName(compileName);//执行ava编译的时候,先执行编译proto文件任务CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");javaCompile.dependsOn(compilerProto);//proto任务的输出目录附加到sourceSet中sourceSet.getJava().srcDirs(compilerProto.outGeneratedDir);}}

总结

我们回顾下整个插件开发流程,我们构造了三个类:
在这里插入图片描述
CompilerProtoTask 用于负责编译整个proto文件
MyProtoConfigExtension是一个让用户可以gradle中自定配置proto文件位置的类
MyProtoPlugin负责将CompilerProtoTask输出目录加入的工程编译路径中,让其打包的时候可以寻找到。

//CompilerProtoTask.java
public class CompilerProtoTask extends DefaultTask {Logger logger = Logging.getLogger(CompilerProtoTask.class);@InputString protoDir;@OutputDirectoryString outGeneratedDir;{outGeneratedDir = getProject().getBuildDir() + "/generated/source/protos/";}@TaskActionvoid action() {OsDetector osDetector = new OsDetector();//这个名字用于创建一个configuration,configuration可以简单理解为管理一组依赖的管理器String MycName = "customPluginConfiguration";//创建一个管理器名字为customPluginConfigurationConfiguration configuration = getProject().getConfigurations().create(MycName);//这个是构造proto的artifact的信息,这些信息可以查看pom文件得知HashMap<String, String> protocArtifactMap = new HashMap<>();protocArtifactMap.put("group", "com.google.protobuf");protocArtifactMap.put("name", "protoc");protocArtifactMap.put("version", "3.14.0");protocArtifactMap.put("classifier", osDetector.getClassifier());protocArtifactMap.put("ext", "exe");//添加依赖到MycName这个管理器中Dependency protoDependency = getProject().getDependencies().add(MycName, protocArtifactMap);//用管理器返回这个依赖的所有的文件FileCollection files = configuration.fileCollection(protoDependency);//因为这个依赖只会存在一个文件也就是编译器File protoExe = files.getFiles().stream().findFirst().get();try {//获得扩展类对象实例,主要用于获取用户配置的proto文件路径String protoDirPath = protoDir;File file1 = new File(getProject().getProjectDir(), protoDirPath);//得到用户配置proto文件目录下的所有后缀为proto的文件String[] extensionFilter = {"proto"};Collection<File> protoDifFile = FileUtils.listFiles(new File(getProject().getProjectDir(), protoDirPath), extensionFilter, false);//拼接命令行字符串StringBuilder cmd = new StringBuilder(protoExe.getAbsolutePath() + " ");File outFile = new File(outGeneratedDir);if (!outFile.exists()) {outFile.mkdirs();}cmd.append("--java_out=" + outGeneratedDir);for (File file : protoDifFile) {String replaceFilePath = " " + file.getPath().replaceFirst(file1.getAbsolutePath() + "/", "") + " ";cmd.append(replaceFilePath);}cmd.append(" -I" + protoDirPath + " ");logger.info("运行编译命令 " + cmd);//防止编译器无权限运行if (!protoExe.canExecute() && !protoExe.setExecutable(true)) {throw new GradleException("protoc编译器无法执行");}//执行命令行Process exec = null;try {String[] strings = new String[0];exec = Runtime.getRuntime().exec(cmd.toString(), strings, getProject().getProjectDir());int resultCode = exec.waitFor();//执行成功if (resultCode == 0) {} else {throw new GradleException("编译proto文件错误" + IOUtils.toString(exec.getErrorStream()));}} finally {if (exec != null) {exec.destroy();}}} catch (Exception e) {e.printStackTrace();}}}
//MyProtoConfigExtension.java
public class MyProtoConfigExtension {//要编译的proto文件目录String protoDirPath;public String getProtoDirPath() {return protoDirPath;}public void setProtoDirPath(String protoDirPath) {this.protoDirPath = protoDirPath;}
}
//MyProtoPlugin.java
public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");/*** 创建一个插件DSL扩展,第一个参数为DSL名字,第二个为配置类*  比如:*     protoConfig{*             // protoDirPath为MyProtoConfigExtension内部属性*             protoDirPath = "张三"**     }*/project.getExtensions().create("protoConfig", MyProtoConfigExtension.class);project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {/*** 创建任务并设置输出目录*/CompilerProtoTask compilerProto = project.getTasks().create("compilerProto", CompilerProtoTask.class);
//                compilerProto.onlyIf(new );compilerProto.setGroup("proto");MyProtoConfigExtension myProtoConfigExtension = project.getExtensions().getByType(MyProtoConfigExtension.class);compilerProto.protoDir = myProtoConfigExtension.protoDirPath;//如果当前是android工程链接生成的源码路径到编译路径if (isAndroidProject(project)) {linkAndroidProject(project);} else {linkJavaProject(project);}}});}void linkAndroidProject(Project project) {if (project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class)) {//当前是Android 应用工程//Android 插件提供了扩展// 也就是我们经常的写法////  android{////  }//AppExtension extension = (AppExtension) (project.getExtensions().getByName("android"));extension.getApplicationVariants().all(configurationAndroidVariant(project));extension.getTestVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));extension.getApplicationVariants().all(new Action<ApplicationVariant>() {@Overridepublic void execute(ApplicationVariant applicationVariant) {System.out.println("Android 正式环境变体  "+applicationVariant.getName() );}});extension.getTestVariants().all(new Action<TestVariant>() {@Overridepublic void execute(TestVariant testVariant) {System.out.println("Android 测试环境变体 "+testVariant.getName() );}});} else {//当前是Android lib工程LibraryExtension extension = (LibraryExtension) (project.getExtensions().getByName("android"));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));}}private Action<BaseVariant> configurationAndroidVariant(Project project) {return new Action<BaseVariant>() {@Overridepublic void execute(BaseVariant libraryVariant) {CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");//applicationVariant.addJavaSourceFoldersToModel();libraryVariant.registerJavaGeneratingTask(compilerProto, new File(compilerProto.outGeneratedDir));}};}void linkJavaProject(Project project) {SourceSetContainer container = project.getExtensions().getByType(SourceSetContainer.class);//遍历所有源集合 比如main等for (SourceSet sourceSet : container) {//getCompileTaskName用于获取这个sourceSet提供的编译java文件的task//比如我我有一个SourceSet名为MySource,那么编译任务名称为compileMySourceJava//利用这个函数我们快速拿到这个Task的名称String compileName = sourceSet.getCompileTaskName("java");//获取这个Task实例JavaCompile javaCompile = (JavaCompile) project.getTasks().getByName(compileName);//执行ava编译的时候,先执行编译proto文件任务CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");javaCompile.dependsOn(compilerProto);//proto任务的输出目录附加到sourceSet中sourceSet.getJava().srcDirs(compilerProto.outGeneratedDir);}}//是Android工程boolean isAndroidProject(Project project) {return project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class) || project.getPlugins().hasPlugin(com.android.build.gradle.LibraryPlugin.class);}
}

源码地址:

本案例源码地址

参考

Android测试相关文档

Android变体说明文档

AGP源码和文档

Java插件文档

Gradle configuration相关文档

JavaGradle插件说明


https://www.fengoutiyan.com/post/16293.html

相关文章:

  • protobuf enum
  • python protobuf解析
  • protocompiler
  • 插件是什么意思
  • 编译proto文件
  • 插件怎么安装
  • Protobuf
  • golang protobuf
  • 鏡像模式如何設置在哪,圖片鏡像操作
  • 什么軟件可以把圖片鏡像翻轉,C#圖片處理 解決左右鏡像相反(旋轉圖片)
  • 手機照片鏡像翻轉,C#圖像鏡像
  • 視頻鏡像翻轉軟件,python圖片鏡像翻轉_python中鏡像實現方法
  • 什么軟件可以把圖片鏡像翻轉,利用PS實現圖片的鏡像處理
  • 照片鏡像翻轉app,java實現圖片鏡像翻轉
  • 什么軟件可以把圖片鏡像翻轉,python圖片鏡像翻轉_python圖像處理之鏡像實現方法
  • matlab下載,matlab如何鏡像處理圖片,matlab實現圖像鏡像
  • 圖片鏡像翻轉,MATLAB:鏡像圖片
  • 鏡像翻轉圖片的軟件,圖像處理:實現圖片鏡像(基于python)
  • canvas可畫,JavaScript - canvas - 鏡像圖片
  • 圖片鏡像翻轉,UGUI優化:使用鏡像圖片
  • Codeforces,CodeForces 1253C
  • MySQL下載安裝,Mysql ERROR: 1253 解決方法
  • 勝利大逃亡英雄逃亡方案,HDU - 1253 勝利大逃亡 BFS
  • 大一c語言期末考試試題及答案匯總,電大計算機C語言1253,1253《C語言程序設計》電大期末精彩試題及其問題詳解
  • lu求解線性方程組,P1253 [yLOI2018] 扶蘇的問題 (線段樹)
  • c語言程序設計基礎題庫,1253號C語言程序設計試題,2016年1月試卷號1253C語言程序設計A.pdf
  • 信奧賽一本通官網,【信奧賽一本通】1253:抓住那頭牛(詳細代碼)
  • c語言程序設計1253,1253c語言程序設計a(2010年1月)
  • 勝利大逃亡英雄逃亡方案,BFS——1253 勝利大逃亡
  • 直流電壓測量模塊,IM1253B交直流電能計量模塊(艾銳達光電)
  • c語言程序設計第三版課后答案,【渝粵題庫】國家開放大學2021春1253C語言程序設計答案
  • 18轉換為二進制,1253. 將數字轉換為16進制
  • light-emitting diode,LightOJ-1253 Misere Nim
  • masterroyale魔改版,1253 Dungeon Master
  • codeformer官網中文版,codeforces.1253 B
  • c語言程序設計考研真題及答案,2020C語言程序設計1253,1253計算機科學與技術專業C語言程序設計A科目2020年09月國家開 放大學(中央廣播電視大學)
  • c語言程序設計基礎題庫,1253本科2016c語言程序設計試題,1253電大《C語言程序設計A》試題和答案200901
  • 肇事逃逸車輛無法聯系到車主怎么辦,1253尋找肇事司機