1.简介构建系统是一种用于从源代码生成目标产品的自动化工具。目标产品包括库、可执行文件、生成的脚本等。构建系统通常提供特定于平台的可执行文件。程序通过执行命令从外部触发构建,例如GUNMake、Ant、CMake、Gradle等。Gradle是一个灵活而强大的开源构建系统。它提供了一个跨平台的可执行程序,用于通过命令行窗口中的命令在外部执行Gradle构建。例如,./gradlewassemble命令触发Gradle构建任务。现代成熟的IDE会集成所需的构建系统,组合多个命令行工具,将它们打包成一套自动化构建工具,并提供构建视图工具以提高开发人员的工作效率。在IntelliJIDEA中,可以通过Gradle视图工具来触发Gradle任务的执行,但并不是通过封装命令行工具来实现的,而是通过集成GradleToolingAPI这一Gradle专门提供的编程SDK,通过Gradle可以bebuilt能够嵌入到IDE或其他工具软件中:为什么Gradle会提供一个ToolingAPI供外部集成调用,而不是像其他构建系统一样只提供基于可执行程序的命令?ToolingAPI是GradleExtension的一大贡献,它提供了比命令模式更可控、更深入的构建控制能力,让IDE等工具更方便、更紧密地与Gradle能力结合。ToolingAPI接口可以直接返回构建结果,不需要像命令模式那样手动解析命令行程序的日志输出,并且可以独立于版本运行,也就是说同一个版本的ToolingAPI可以处理的构建不同的Gradle版本,同时向前和向后兼容。2.接口函数及调用示例2.1接口函数ToolingAPI提供了执行和监控构建、查询构建信息等功能:查询构建信息,包括项目结构、项目依赖、外部依赖、项目任务;执行构建任务并监控构建进度信息;取消正在进行的构建任务;自动下载与项目匹配的Gradle版本;关键API如下:2.2调用示例查询项目结构和任务。班级);设置subProject=rootProject.getChildren();设置tasks=rootProject.getTasks();}如上API中所述,首先通过ToolingAPI的GradleConnector入口类创建到参与建设项目的连接ProjectConnection,然后通过getModel(ClassmodelType)获取本项目的结构信息模型GradleProject,其中包含项目我们要查询的结构、项目任务等信息。执行构建任务String[]gradleTasks=newString[]{"clean","app:assembleDebug"};try(ProjectConnectionconnection=GradleConnector.newConnector().forProjectDirectory(newFile("someFolder")).connect()){BuildLauncherbuild=connection.newBuild();build.forTasks(gradleTasks).addProgressListener(progressListener).setColorOutput(true).setJvmArguments(jvmArguments);build.run();}这里是通过ProjectConnection的newBuild()方法创建一个BuildLauncher来执行构建任务,然后通过forTasks(String...tasks)配置要执行的Gradle任务,配置执行进度监控等,最后通过run()触发任务的执行。3.原理分析3.1如何与Gradle构建进程通信?GradleToolingAPI不具备真正的Gradle构建能力,只是提供了调用原生Gradle程序的入口,方便以代码的形式与Gradle进行通信。在我们自己的工具程序中通过API调用Gradle构建能力后,还需要与真正的Gradle构建程序进行跨进程通信。无论是通过GradleToolingAPI与Gradle交互的IDE或工具程序,还是以命令的形式与Gradle交互的命令行窗口程序,这个跨进程调用Gradle构建程序的客户端程序就是一个Gradle实际执行任务的客户端Gradle构建程序就是Gradle构建过程。Gradle守护进程是一个长期存在的Gradle构建过程。它通过避免构建GradleJVM环境和内存缓存来提高构建速度。对于集成了GradleToolingAPI的Gradle客户端,守护进程将始终处于启用状态。也就是说,集成了GradleToolingAPI的工具程序会一直和daemon进程进行跨进程通信,调用Gradle构建能力。Gradle守护进程是动态创建的。如果Gradle客户端要连接动态创建的daemon进程,需要通过服务注册和服务发现机制记录daemon进程注册和开启查询。DaemonRegistry提供了这样的机制。Client-GradleClient下面以项目结构信息为切入点,从源码的角度分析GradleToolingAPI的跨进程通信机制:try(ProjectConnectionconnection=GradleConnector.newConnector().forProjectDirectory(newFile("someFolder")).connect()){GradleProjectrootProject=connection.getModel(GradleProject.class);}从代码来看,ProjectConnection虽然看似建立了一个到daemon进程的链接,其实并没有,但是在getModel(ClassmodelType)方法只有这样才能真正建立与守护进程的链接。在这个方法内部,会从ToolingAPI端调用到Gradle源码,最后会在DefaultDaemonConnector.java中找到可用的daemon进程:>,Collection>idleBusy=partitionByState(daemonRegistry.getAll(),Idle);finalCollectionidleDaemons=idleBusy.getLeft();finalCollectionbusyDaemons=idleBusy.getRight();//检查是否有兼容的空闲守护进程DaemonClientConnectionconnsection=connectToIdleDaemon(idleDaemons,constraint);如果(连接!=null){返回连接;}//检查是否有任何兼容的已取消守护进程并等待查看是否有一个变为空闲connection=connectToCanceledDaemon(busyDaemons,constraint);如果(连接!=null){返回连接;}//没有可用的兼容守护进程-启动一个新的守护进程handleStopEvents(idleDaemons,busyDaemons);returnstartDaemon(constraint);}通过上面的daemon进程查找逻辑和相关代码,我们可以得到:Daemon进程包括Idle、Busy、Canceled、StopRequested、Stopped、Broken这6种状态;通过daemon进程方式执行Gradle构建时,会依次尝试寻找处于Idle、Canceled状态和环境兼容的daemon进程,如果没有找到,则创建一个新的与Gradleclient环境兼容的daemon进程;所有Daemon进程都记录在DaemonRegistry.java注册表中,供Gradle客户端获取;Daemon进程的环境兼容性判断包括Gradle版本、文件编码、JVM堆大小等属性;一个兼容守护进程后,会通过Socket连接到守护进程监听的端口,然后通过Socket与守护进程通信;服务器守护进程是一个当Gradle客户端调用Gradle构建能力时,会触发守护进程的创建。进程入口函数在GradleDaemon.java中,然后调用DaemonMain.java初始化进程。最后在TcpIncomingConnector.java中打开SocketServer并绑定监听指定端口:publicConnectionAcceptoraccept(Actionaction,booleanallowRemote){本地端口;尝试{serverSocket=ServerSocketChannel.open();serverSocket.socket().bind(newInetSocketAddress(addressFactory.getLocalBinding),0));localPort=serverSocket.socket().getLocalPort();}catch(Exceptione){throwUncheckedException.throwAsUncheckedException(e);}...}然后会将守护进程记录到注册表中的DaemonRegistryUpdater.java表中:LOGGER.debug("Advertiseddaemoncontext:{}",daemonContext);this.connectorAddress=connectorAddress;守护进程Registry.store(newDaemonInfo(connectorAddress,daemonContext,token,Busy));}这样Gradleclient就可以在registry中获取兼容的daemon进程及其端口,从而与daemon进程建立连接来实现沟通。具体过程如下:总结梳理ToolingAPI与GradleDaemon进程的连接建立过程:ToolingAPI本身并没有过多的代码,调用和获取项目信息的接口被ModelProducer抽象封装后,它会进入Gradle源码,但它仍然属于Gradle客户端进程;在DefaultDaemonConnector中,它会尝试从DaemonRegistry获取一个可用且兼容的守护进程,如果没有,则创建一个新的守护进程;Daemon进程启动后,会通过Socket绑定监听一个固定的端口,然后将自己的监听端口等信息记录到DaemonRegistry中3.2如何实现向前向后兼容?ToolingAPI支持Gradle2.6及之后的版本,即某个版本的ToolingAPI向前向后兼容其他版本的Gradle。支持调用旧版或新版Gradle进行Gradle构建,但ToolingAPI包含的接口函数并不适用于所有Gradle版本;Gradle5.0及以后版本对ToolingAPI版本也有要求,需要ToolingAPI3.0及以后版本Gradle和ToolingAPI如何实现不同版本之间的兼容?想一个问题,如果我们有两个软件:主软件A和专门用来调用A的工具软件B,如何实现A和B之间最大的优雅版本兼容?下面深入分析ToolingAPI和Gradle源码,看看Gradle在版本兼容方面采用了哪些技术方案。Gradle版本适配于GradleToolingAPI源码仓库。有一张流程图介绍获取项目信息的调用链:图中我们只关注DefaultConnection——从ToolingAPI调用到Gradlelauncher模块的关键类:DefaultConnection有入口点接受来自不同ToolingAPI的调用版本ToolingAPI端最终通过DefaultToolingImplementationLoader.java中的自定义URLClassLoader加载DefaultConnection。自定义URLClassLoader类加载路径指定对应Gradle版本lib下的jar包,从而可以加载不同Gradle版本的DefaultConnection:implementationClasspath=distribution.getToolingImplementationClasspath(progressLoggerFactory,progressListener,connectionParameters,cancellationToken);LOGGER.debug("Usingtoolingproviderclasspath:{}",implementationClasspath);FilteringClassLoader.SpecfilterSpec=newFilteringClassLoader.Spec();filterSpec.allowPackage("org.gradle.tooling.internal.protocol");filterSpec.allowClass(JavaVersion.class);FilteringClassLoaderfilteringClassLoader=newFilteringClassLoader(classLoader,filterSpec);returnnewVisitableURLClassLoader("tooling-implementation-loader",filteringClassLoader,implementationClasspath);}ToolingAPI通过自定义Java类加载器调用指定机器版本的Gradle源码。需要注意的是,DefaultConnection虽然已经是Gradle端的源码,但它仍然属于Gradle客户端Process,即IDE等工具软件程序中的模型类适配。项目结构信息模型GradleProject可以通过getModel(ClassmodelType)方法从Gradledaemon进程获取,不同的Gradle版本可能有不同的GradleProject定义。同一个版本的ToolingAPI如何兼容多个版本的信息模型结构?ToolingAPI在请求获取模型信息前,会根据Gradle版本判断是否支持获取VersionDetails.java中的模型。如果支持,则向守护进程发送获取请求。daemon进程返回对应版本的信息模型后,会在ToolingAPI的ProtocolToModelAdapter.java中封装一层动态代理,最后以Proxy的形式返回:privatestaticTcreateView(ClasstargetType,ObjectsourceObject,ViewDecorationdecoration,ViewGraphDetailsgraphDetails){......//创建代理InvocationHandlerImplhandler=newInvocationHandlerImpl(targetType,sourceObject,decorationsForThisType,graphDetails);对象代理=Proxy.newProxyInstance(viewType.getClassLoader(),newClass>[]{viewType},handler);handler.attachProxy(代理);returnviewType.cast(proxy);}最后,ToolingAPI返回的GradleProject只是一个动态代理接口,如下:publicinterfaceGradleProjectextendsHierarchicalElement,BuildableElement,ProjectModel{......;}可以看到,即使是支持的信息模型,由于Gradle版本不匹配,其部分内容可能不支持获取,调用会抛出UnsupportedMethodException异常。适配不同版本的模型类是通过动态代理接口的方式来实现的,但是这种方式也带来了一个缺点。在ToolingAPI端,只有获取模型信息的接口,并不是真正的模型实体类。那么后续在对整个模型信息类进行序列化或传递时,还需要再进行一层转换,才能构造出真正包含内容的实体类。Androidsdktools库构造了一个实体类IdeAndroidProjectImpl,它实际上包含了AndroidProject模型的内容。4.总结本文从现代IDE与构建系统的结合入手,介绍GradleToolingAPI,介绍其对Gradle构建系统的特殊意义,然后通过ToolingAPI的具体API和调用实例介绍其主要功能,以及最后在原理分析方面,结合源码,着重分析了跨进程通信和版本兼容的原理,这也是ToolingAPI中两个非常重要的机制。通过对GradleToolingAPI的分析和学习,可以深入掌握ToolingAPI的整体架构,从而更好地基于它开发具有Gradle能力的工具软件。另外,在类似的技术架构场景下可以学到一些方法论:与程序运行时动态创建的服务进行通信时,一般可以引入服务注册和服务发现机制来查询和连接动态服务;作为一个对外访问的工具程序,在同类程序只提供命令行方法的情况下,我们要敢于打破常规,提供一种全新的方式,才能更大限度地赋能其他软件,实现双赢。双方。5.参考文章org.gradle.tooling(GradleAPI7.2)https://docs.gradle.org/current/javadoc/org/gradle/tooling/package-summary.htmlGradle&Third-partyToolshttps://docs.gradle.org/current/userguide/third_party_integration.html#embeddingGradle|Gradle特性https://gradle.org/features/#embed-gradle-with-tooling-apiGradle守护进程https://docs.gradle.org/current/userguide/gradle_daemon.html