连载 | 深入理解gradle框架之三:artifacts的发布
Posted 字节跳动技术团队
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连载 | 深入理解gradle框架之三:artifacts的发布相关的知识,希望对你有一定的参考价值。
什么是gradle中的configuration?
先从感性认识,那就是平常我们在编译中写的compile,implementation,provided,compileOnly等等,都是configuration。
这样看的话,configuration承担了项目或者aar依赖的作用。
比如,一个app module依赖一个library module,它到底是如何在我们点击编译(或者用命令行编译)之后,开始驱动整个项目的编译的。
而这个问题的核心就是,它是如何保证这两个project的任务能够有机地组合起来,在保证任务并行的同时,又能够很好地组织好任务的依赖关系?
以Manifest为例,app module是如何能够merge到library module的manifest信息的?
是在plugin内部有设置task的依赖关系吗?
这里先剧透一点,就是承担项目或者aar依赖只是configuration表面的任务,对于项目依赖来说,它真正的作用,其实是当project A依赖project B时,它就拥有了对于project B中产物的所有权,即它能够获取到project B中的产物。
artifacts的意思就是产物,实际上对于大部分task来说,执行完之后都会有产物输出,输出的就叫artifacts。
之所以有FileCollection是因为基本上所有的androidTask的输出都是文件。
这3个方法的作用,其实注释已经写得很清楚了,分别如下:
-
getArtifactFiles()
用于返回包含有所有产物的文件集合,这个方法的返回值可作为一个task的input,利用这个input构建起task之间的依赖关系,具体如何构建在后面会提到。
-
getArtifacts()
用于返回解析好的artifacts,如果没有解析,则会进行解析。在这个过程中,如果需要的话,可能会下载artifact的metadata和artifact文件。
-
getFailures()
返回在解析产物的过程中出现的异常。
注意FileCollection继承的接口,分别有Iterable,AntBuilderAware和Buildable,特别注意Buildable这个接口,它里面含有任务依赖信息,其定义如下:
一个Buildable对象代表一个或多个artifact,这些artifacts也是由一个或多个task产生的。
在android gradle plugin框架中,绝大部分的task都是AbstractTask的子类,并且具有inputs和outputs,这也很好理解,在编译过程中,每个task都需要一些输入,执行完成之后,会生成一些产物,即outputs。
在讨论inputs之前,我们先讨论一个task的outputs。本文以输出类型为LIBRARY_MANIFEST的产物发布为例进行讨论。
那android gradle plugin中的task是如何将自己的产物发布出去呢?
起点就在VariantScopeImpl中的
addTaskOutput()
方法中。
其中上部分绿色的流程是在android gradle plugin中,而下半部分黄色的是在gradle中。
前面我们说过,以输出类型为LIBRARY_MANIFEST的产物发布为例,其中LIBRARY_MANIFEST的调用在TaskManager中,如下:
注意其中的taskName就是processManifest这个task的名称。
首先看
super.addTaskOutput()
方法,调用的是
TaskOutputHolderImpl.addTaskOutput()
方法,如下:
-
由于outputMap缓存了之前发布的结果,所以通过outputMap是否包含当前outputType,以防止重复发布。
-
调用
createCollection()
方法创建ConfigurableFileCollection对象,并且将此对象缓存到outputMap中。
而
createCollection()
方法如下:
这里
getProject().files(file);
其实是调用了
DefaultProject的files()
方法,从这里就进入gradle层,下一小节分析。
1.1 DefaultProject.files()方法分析
而
getFileOperations()
实际上返回的是DefaultFileOperations对象,该对象的
files()
方法如下:
显然,这里返回的是DefaultConfigurableFileCollection对象,这里可以先简单地把DefaultConfigurableFileCollection当作文件和编译依赖的包装类。
1.2 TaskOutputHolderImpl.createCollection()分析
再回到android gradle plugin层的TaskOutputHolderImpl中的
createCollection()
方法,其后面的语句为:
即如果taskName不为空,则为DefaultConfigurableFileCollection对象collection设置编译依赖。
而
DefaultConfigurableFileCollection的builtBy()
方法如下:
其中buildDependency是在构造方法中创建的DefaultTaskDependency对象,显然,这里就是将编译依赖的task添加到buildDependency中。
至此,
VariantScopeImpl.addTaskOutput()
中的
super.addTaskOutput()
方法就分析完了。总结起来就是:
-
创建了DefaultConfigurableFileCollection对象,并且将file添加到其中,而taskName则添加到编译依赖中。
-
将创建的DefaultConfigurableFileCollection缓存到outputMap中。
如果在super调用中没有抛出异常,并且传入的file确实是File对象的话,则执行
variantPublishingSpec.getSpec(outputType);
在下一小节分析这个方法。
1.3 VariantPublishingSpec.getSpec()方法分析
VariantPublishingSpec.getSpec()
方法如下:
这个方法先判断outputMap有没有初始化,如果没有,则先进行初始化。
outputMap的初始化分为以下两部分:
-
-
遍历taskSpecs,然后将taskSpec的信息放入outputMap中,其中key为taskSpec的outputType,value则为taskSpec自身。
之后就是以taskOutputType的值为key,在outputMap中找出相应的value并返回,如果没有找到,则再在parentSpec中找一遍。
那么,为了后面的分析,我们先看一下OutputType到底有哪些类型,以及taskSpecs到底是什么。
OutputType和TaskOutputType都是定义在TaskOutputHolder中的内部类,其定义如下:
显然,OutputType就是表示为输出类型的接口,而TaskOutputType则是task的输出类型,并且为了使用方便,定义成了枚举类。
-
JAVAC. 显然就是javac这个java编译task的输出。
-
LIBRARY_CLASSES. aar module对外输出的classes。
-
APP_CLASSES. apk module对外输出的classes。
-
FEATURE_CLASSES. feature module对外输出的classes。
-
PROCESSED_RES. aapt输出的编译好的资源。
-
PACKAGED_RES. aar module对外输出的资源。
需要先看懂
outputSpec()
和
variantSpec()
这两个方法。
可见,这个方法就只是返回一个OutputPublishingSpec对象,OutputPublishingSpec的构造方法为:
显然,它就只是简单地赋值。
需要注意的是它的3个参数:
-
outputType为OutputType类型的参数,表示输出类型,在前面已经分析过。
artifactType为ArtifactType类型的参数,表示产物类型,其定义如下:
在这里可以看到所有的产物类型了,比如CLASSES,JAR,MANIFEST,ASSETS等等。
1.3.2 PublishedConfigType
publishedConfigTypes为发布的configuration的类型集合,因为有些产物可发布到多个configuration中。
注意PublishedConfigType定义如下:
显然,它也就是先利用variantType和taskSpecs创建VariantPublishingSpec对象,然后将其放入缓存中。
而VariantPublishingSpec的构造方法如下:
显然,这里也是非常简单的赋值操作,可见,taskSpecs就是在构造方法中被赋值的。
在VariantPublishingSpec的static块中,就有很多的
variantSpec()
方法调用,如下:
以VariantType.DEFAULT为例,它表示为默认的variant类型,它对应的OutputPublishingSpec对象非常多,有通过
outputSpec(MANIFEST_METADATA, ArtifactType.MANIFEST_METADATA, API_ELEMENTS_ONLY)
构造的OutputPublishingSpec对象,它表示输出类型为MANIFES_METADATA的task,其对应的产物类型为ArtifactType.MANIFEST_METADATA,可输出到的configuration类型为API_ELEMENTS_ONLY;类似的,可作出如下总结,详细情况请看下一小节。
在这里,我们先看一下VaraintType的定义,如下:
显然,AGP层的VariantType和gradle层的VariantType对应关系如下:
1.3.2.1 VariantType为DEFAULT
前面说过,VariantType为DEFAULT的含义就是build variant为Application的情形;这种类型下,有以下输出类型与产物类型的组合 。
-
输出类型为APK的task,其对应的产物类型为ArtifactType.APK,可输出的configuration类型(集合)为RUNTIME_ELEMENTS,而RUNTIME_ELEMENTS_ONLY的定义如下:
-
可见,实际上就只包含RUNTIME_ELEMENTS:
-
输出类型为APK_MAPPING的task,其产物类型为ArtifactType.APK_MAPPING,可输出的configuration类型(集合)为API_ELEMENTS_ONLY,而API_ELEMENTS_ONLY的定义如下:
-
-
输出类型为MERGED_ASSETS的task,其产物类型为ArtifactType.ASSETS,可输出的configuration类型(集合)为BUNDLE_ELEMENTS_ONLY,类似地,BUNDLE_ELEMENTS_ONLY中也只包含BUNDLE_ELEMENTS;
1.3.2.2 VariantType为LIBRARY
前面说过,VariantType为LIBRARY对应的是build variant为library的情形。
此时,有以下输出类型与产物类型的组合 。
-
输出类型为LIBRARY_MANIFEST的task,其产物类型为ArtifactType.MANIFEST,可输出到的configuration类型(集合)为API_AND_RUNTIME_ELEMENTS;API_AND_RUNTIME_ELEMENTS的定义如下:
-
显然,它包含了API_ELEMENTS和RUNTIME_ELEMENTS这两种configuration类型,用通俗地话说就是这个task的产物可分别给出到编译时和运行时的configuration中;
-
输出类型为LIBRARY_ASSETS的task,其产物类型为ArtifactType.ASSETS,可输出到的configuration类型为RUNTIME_ELEMENTS_ONLY,即只能输出到运行时的configuration中;
-
输出类型为LIBRARY_CLASSES的task,其产物类型为ArtifactType.CLASSES,可输出到的configuration类型集合为API_AND_RUNTIME_ELEMENTS,即可输出到编译时和运行时的configuration中;
-
输出类型为FULL_JAR的task,其产物类型为ArtifactType.JAR,可输出到的类型为API_AND_RUNTIME_ELEMENTS;
-
输出类型为LIBRARY_JNI的task,其产物类型为ArtifactType.JNI,可输出到的configuration类型为RUNTIME_ELEMENTS_ONLY。
1.3.2.3 VariantType为FEATURE
前面分析过,VariantType.FEATURE对应的就是build variant为feature的情形;此时有以下输出类型与产物类型的组合。
-
输出类型为METADATA_FEATURE_MANIFEST的task,其产物类型为ArtifactType.METADATA_FEATURE_MANIFEST,可输出到的configuration类型为METADATA_ELEMENTS_ONLY;
-
输出类型为FEATURE_APPLICATION_ID_DECLARATION的task,其产物类型为ArtifactType.FEATURE_APPLICATION_ID_DECLARATION,可输出到的configuration类型为API_ELEMENTS_ONLY;
-
输出类型为FEATURE_CLASSES的task,其产物类型为ArtifactType.CLASSES,可输出到的configuration类型为API_ELEMENTS_ONLY;
-
输出类型为APK的task,其产物类型为ArtifactType.CLASSES,可输出的configuration类型为API_ELEMENTS_ONLY;
1.3.3 variantPublishingSpec.getSpec(outputType)的结果
在本文开头说过,输出类型为LIBRARY_MANIFEST,而在VariantPublishingSpec中,有如下语句:
可见,outputType为LIBRARY_MANIFEST,对应的产物类型为ArtifactType.MANIFEST;即此时返回的OutputPublishingSpec对象中outputType为LIBRARY_MANIFEST,产物类型为ArtifactType.MANIFEST,可输出的configuration类型为API_AND_RUNTIME_ELEMENTS。
即
taskSpec.getArtifactType()
为ArtifactType.MANIFEST,
taskSpec.getPublishedConfigTypes()
为只包含API_AND_RUNTIME_ELEMENTS的Set。
之后,调用
publishIntermediateArtifact()
方法,在下一章分析这个方法的调用流程。
2. publishIntermediateArtifact()
VariantScopeImpl的publishIntermediateArtifact()
方法如下:
可以看到,这个方法中对于所有的configuration类型都作了处理,上一章说过,由于LIBRARY_MANIFEST对应的publishConfigTypes为API_AND_RUNTIME_ELEMENTS,即同时包含了API_ELEMENTS和RUNTIME_ELEMENTS,之后调用publishArtifactToConfiguration()方法发布到相应的configurion。
就以RUNTIME_ELEMENTS为例,则发布到buildVaraintRuntimeElements这个configuration中,比如build variant为debug时,则为debugRuntimeElements。
下面分析
publishArtifactToConfiguration()
方法,如下:
这个configuratiton是DefaultConfiguration对象,下面开始就进入gradle层的代码进行分析。
2.1 DefaultConfiguration.getOutgoing()方法分析
结合outgoing初始化的代码,可知outgoing是DefaultConfigurationPublications对象。
再回到android gradle plugin层,下一步调用outgoing的
variants()
方法,
DefaultConfigurationPublications的variants()
方法如下:
可见,就只是执行Action的execute()方法,不过在执行之前,先调用了getVariants()方法以传入NamedDomainObjectContainer给action调用。
可见,这里是先创建一个ConfigurationVariantFactory对象,然后传入此对象以创建FactoryNamedDomainObjectContainer对象。
2.2 FactoryNamedDomainObjectContainer.create()
之后调用的action为:
而FactoryNamedDomainObjectContainer.create()方法如下:
重载的create()方法为:
这个方法的作用就是根据传入的type创建相应的对象,并且添加到缓存中,最后再调用
configAction.execute()
方法。
create()
方法是在AbstractNamedDomainObjectContainer中,而FactoryNamedDomainObjectContainer中实现的doCreate()方法如下:
可见,就是调用factory去创建,而factory为DefaultConfigurationPublications中的ConfigurationVariantFactory对象,其定义如下:
可见,其实就是通过instantiator创建一个DefaultVariant对象。
前面说过,artifactType的值为ArtifactType.MANIFEST, 其
getType()
返回的值为"android-manifest",所以创建的这个DefaultVariant对象的name为"android-manifest"。
2.3 DefaultVariant.artifact()方法
回到VariantScopeImpl中,在创建name为"android-manifest"的DefaultVariant对象之后,执行的action为:
显然,这里执行的其实是
DefaultVariant的artifact()
方法,其方法如下:
可见,这里先是利用artifactNotationParser创建一个ConfigurablePublishArtifact对象,然后添加到缓存中。
之后执行配置操作。
显然,就是为ConfigurablePublishArtifact对象设置类型和编译依赖。
通过上面android gradle plugin层和gradle层的流程梳理,可以得到以下结论:
-
需要对外发布的artifacts,都有一个OutputType和与之对应的ArtifactType。
-
需要对外发布的artifacts,都有一个PublishedConfigType集合,表示这个artifacts能够发布到哪些类型的configuration中。
-
在gradle层会创建一个名为
artifactType.getType()
的DefaultVariant对象并缓存起来。
-
在gradle层会创建一个ConfigurablePublishArtifact对象(这个对象缓存在DefaultVariant中),并且会为其设置type和builtBy属性,其中的builtBy就是要生成这个产物所依赖的task。
到这里,android gradle plugin和gradle一起构建的生产者-消费者模型就呼之欲出了。
显然,本文只讲了左边的发布流程,至于右边的消费流程,且听下回分解。
以上是关于连载 | 深入理解gradle框架之三:artifacts的发布的主要内容,如果未能解决你的问题,请参考以下文章
神经网络入门(连载之三)
.Net Discovery系列之三 深入理解.Net垃圾收集机制(上)
深入理解计算机系统(第二版)----之三:程序的机器级表示
图文并茂带你深入理解三次交换之三层交换机
《02.Spring Boot连载:Spring Boot实战.Spring Boot核心原理剖析》
深入理解gradle中的task