连载 | 深入理解gradle框架之三:artifacts的发布

Posted 字节跳动技术团队

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连载 | 深入理解gradle框架之三:artifacts的发布相关的知识,希望对你有一定的参考价值。

0.引言
0.1:configuration是什么?
什么是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中的产物。
0.2: artifacts是什么?
artifacts的意思就是产物,实际上对于大部分task来说,执行完之后都会有产物输出,输出的就叫artifacts。
而与artifacts密切关联的有以下几个类:
  • ArtifactCollection
  • FileCollection
之所以有FileCollection是因为基本上所有的androidTask的输出都是文件。
ArtifactCollection的定义如下:

这3个方法的作用,其实注释已经写得很清楚了,分别如下:
  • getArtifactFiles() 用于返回包含有所有产物的文件集合,这个方法的返回值可作为一个task的input,利用这个input构建起task之间的依赖关系,具体如何构建在后面会提到。
  • getArtifacts() 用于返回解析好的artifacts,如果没有解析,则会进行解析。在这个过程中,如果需要的话,可能会下载artifact的metadata和artifact文件。
  • getFailures() 返回在解析产物的过程中出现的异常。
FileCollection的定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

注意FileCollection继承的接口,分别有Iterable,AntBuilderAware和Buildable,特别注意Buildable这个接口,它里面含有任务依赖信息,其定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

一个Buildable对象代表一个或多个artifact,这些artifacts也是由一个或多个task产生的。
0.3 artifacts发布
在android gradle plugin框架中,绝大部分的task都是AbstractTask的子类,并且具有inputs和outputs,这也很好理解,在编译过程中,每个task都需要一些输入,执行完成之后,会生成一些产物,即outputs。
在讨论inputs之前,我们先讨论一个task的outputs。本文以输出类型为LIBRARY_MANIFEST的产物发布为例进行讨论。
那android gradle plugin中的task是如何将自己的产物发布出去呢?
起点就在VariantScopeImpl中的 addTaskOutput() 方法中。
在进行分析之前,先看一下核心流程图:
         连载 | 深入理解gradle框架之三:artifacts的发布
其中上部分绿色的流程是在android gradle plugin中,而下半部分黄色的是在gradle中。

1.起点:addTaskOutput()
前面我们说过,以输出类型为LIBRARY_MANIFEST的产物发布为例,其中LIBRARY_MANIFEST的调用在TaskManager中,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

注意其中的taskName就是processManifest这个task的名称。
addTaskOutput() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

首先看 super.addTaskOutput() 方法,调用的是 TaskOutputHolderImpl.addTaskOutput() 方法,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

这个方法很简单,主要做了以下事情:
  • 由于outputMap缓存了之前发布的结果,所以通过outputMap是否包含当前outputType,以防止重复发布。
  • 调用 createCollection() 方法创建ConfigurableFileCollection对象,并且将此对象缓存到outputMap中。
createCollection() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

这里 getProject().files(file); 其实是调用了 DefaultProject的files() 方法,从这里就进入gradle层,下一小节分析。
1.1 DefaultProject.files()方法分析
该方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

getFileOperations() 实际上返回的是DefaultFileOperations对象,该对象的 files() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,这里返回的是DefaultConfigurableFileCollection对象,这里可以先简单地把DefaultConfigurableFileCollection当作文件和编译依赖的包装类。
1.2 TaskOutputHolderImpl.createCollection()分析
再回到android gradle plugin层的TaskOutputHolderImpl中的 createCollection() 方法,其后面的语句为:

连载 | 深入理解gradle框架之三:artifacts的发布

即如果taskName不为空,则为DefaultConfigurableFileCollection对象collection设置编译依赖。
DefaultConfigurableFileCollection的builtBy() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

其中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() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

这个方法先判断outputMap有没有初始化,如果没有,则先进行初始化。 outputMap的初始化分为以下两部分:
  • 创建HashMap对象。
  • 遍历taskSpecs,然后将taskSpec的信息放入outputMap中,其中key为taskSpec的outputType,value则为taskSpec自身。 之后就是以taskOutputType的值为key,在outputMap中找出相应的value并返回,如果没有找到,则再在parentSpec中找一遍。
那么,为了后面的分析,我们先看一下OutputType到底有哪些类型,以及taskSpecs到底是什么。
OutputType和TaskOutputType都是定义在TaskOutputHolder中的内部类,其定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,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对外输出的资源。
再看taskSpecs,要了解清楚它是怎么来的。
需要先看懂 outputSpec() variantSpec() 这两个方法。
先看 outputSpec() 这个方法,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

可见,这个方法就只是返回一个OutputPublishingSpec对象,OutputPublishingSpec的构造方法为:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,它就只是简单地赋值。 需要注意的是它的3个参数:
  • outputType为OutputType类型的参数,表示输出类型,在前面已经分析过。
1.3.1 ArtifactType分析
artifactType为ArtifactType类型的参数,表示产物类型,其定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

在这里可以看到所有的产物类型了,比如CLASSES,JAR,MANIFEST,ASSETS等等。
1.3.2 PublishedConfigType
publishedConfigTypes为发布的configuration的类型集合,因为有些产物可发布到多个configuration中。 注意PublishedConfigType定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

variantSpec() 方法则如下:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,它也就是先利用variantType和taskSpecs创建VariantPublishingSpec对象,然后将其放入缓存中。
而VariantPublishingSpec的构造方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,这里也是非常简单的赋值操作,可见,taskSpecs就是在构造方法中被赋值的。
在VariantPublishingSpec的static块中,就有很多的 variantSpec() 方法调用,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

以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的定义,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,AGP层的VariantType和gradle层的VariantType对应关系如下:
连载 | 深入理解gradle框架之三:artifacts的发布

1.3.2.1 VariantType为DEFAULT

前面说过,VariantType为DEFAULT的含义就是build variant为Application的情形;这种类型下,有以下输出类型与产物类型的组合 。
  • 输出类型为APK的task,其对应的产物类型为ArtifactType.APK,可输出的configuration类型(集合)为RUNTIME_ELEMENTS,而RUNTIME_ELEMENTS_ONLY的定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

  • 可见,实际上就只包含RUNTIME_ELEMENTS:
  • 输出类型为APK_MAPPING的task,其产物类型为ArtifactType.APK_MAPPING,可输出的configuration类型(集合)为API_ELEMENTS_ONLY,而API_ELEMENTS_ONLY的定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

  • 可见,它就只包含API_ELEMENTS;
  • 输出类型为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的定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布

  • 显然,它包含了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中,有如下语句:

连载 | 深入理解gradle框架之三:artifacts的发布

可见,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() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

可以看到,这个方法中对于所有的configuration类型都作了处理,上一章说过,由于LIBRARY_MANIFEST对应的publishConfigTypes为API_AND_RUNTIME_ELEMENTS,即同时包含了API_ELEMENTS和RUNTIME_ELEMENTS,之后调用publishArtifactToConfiguration()方法发布到相应的configurion。
就以RUNTIME_ELEMENTS为例,则发布到buildVaraintRuntimeElements这个configuration中,比如build variant为debug时,则为debugRuntimeElements。
下面分析 publishArtifactToConfiguration() 方法,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

这个configuratiton是DefaultConfiguration对象,下面开始就进入gradle层的代码进行分析。
2.1 DefaultConfiguration.getOutgoing()方法分析
getOutgoing() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

结合outgoing初始化的代码,可知outgoing是DefaultConfigurationPublications对象。
再回到android gradle plugin层,下一步调用outgoing的 variants() 方法, DefaultConfigurationPublications的variants() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

可见,就只是执行Action的execute()方法,不过在执行之前,先调用了getVariants()方法以传入NamedDomainObjectContainer给action调用。

getVariants() 方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

可见,这里是先创建一个ConfigurationVariantFactory对象,然后传入此对象以创建FactoryNamedDomainObjectContainer对象。
2.2 FactoryNamedDomainObjectContainer.create()

之后调用的action为:

连载 | 深入理解gradle框架之三:artifacts的发布

FactoryNamedDomainObjectContainer.create()方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

重载的create()方法为:

连载 | 深入理解gradle框架之三:artifacts的发布

这个方法的作用就是根据传入的type创建相应的对象,并且添加到缓存中,最后再调用 configAction.execute() 方法。
create() 方法是在AbstractNamedDomainObjectContainer中,而FactoryNamedDomainObjectContainer中实现的doCreate()方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

可见,就是调用factory去创建,而factory为DefaultConfigurationPublications中的ConfigurationVariantFactory对象,其定义如下:

连载 | 深入理解gradle框架之三:artifacts的发布
可见,其实就是通过instantiator创建一个DefaultVariant对象。
前面说过,artifactType的值为ArtifactType.MANIFEST, 其 getType() 返回的值为"android-manifest",所以创建的这个DefaultVariant对象的name为"android-manifest"。
2.3 DefaultVariant.artifact()方法
回到VariantScopeImpl中,在创建name为"android-manifest"的DefaultVariant对象之后,执行的action为:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,这里执行的其实是 DefaultVariant的artifact() 方法,其方法如下:

连载 | 深入理解gradle框架之三:artifacts的发布

可见,这里先是利用artifactNotationParser创建一个ConfigurablePublishArtifact对象,然后添加到缓存中。 之后执行配置操作。
配置操作也非常简单,如下:

连载 | 深入理解gradle框架之三:artifacts的发布

显然,就是为ConfigurablePublishArtifact对象设置类型和编译依赖。

3.总结
通过上面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