Gradle Build Scan

552 阅读2分钟

背景

观察一些 Gradle 最糟糕的做法,看看如何用最佳做法替换它们,并以 CPU 配置文件和构建扫描作为指南。学习如何创建构建过程的 CPU 配置文件并学习一些关于解释它们的知识。

Build Scan

查看项目的配置性能:

image.png

虽然构建扫描足以表明正在发生的事情,但它并没有提供足够的细节来理解那是什么。什么花了这么多时间?为什么要创建这些任务?我以为我专门使用了任务配置避免......

val adviceTasks = tasks.withType<AdviceTask>()
val dependencyOutputs = mutableListOf<RegularFileProperty>()
adviceTasks.all {
  dependencyOutputs.add(adviceReport)
}

val aggregateAdviceTask = 
  tasks.register<AdviceSubprojectAggregationTask>("aggregateAdvice") {
    // a ListProperty<RegularFileProperty>
    dependencyAdvice.set(dependencyOutputs) 
  }

这里的问题是 tasks.withType().all { ... },它急切地实现了 AdviceTask 类型的所有任务,从而克服了配置规避。

val aggregateAdviceTask
  = tasks.register<AdviceSubprojectAggregationTask>("aggregateAdvice")

val adviceTask = ...
aggregateAdviceTask.configure {
  // a ListProperty<RegularFile>
  dependencyAdvice.add(adviceTask.map { it.adviceReport })
}

不同之处在于我立即注册了聚合任务,没有配置块。然后我在每次添加“子”或特定于变体的任务时对其进行零碎配置。

CPU 配置文件

AndroidAppAnalyzer 是负责分析 com.android.application 项目的代码。它负责 50% 的 CPU 使用率!从配置文件中,我可以看出这是由于 registerClassAnalysisTask() 造成的,这反过来又需要一些 - 显然 - 相当昂贵的计算。这是有问题的代码:

val javaVariantFiles = androidSourceSets.flatMapToSet { sourceSet -> 
  project.files(sourceSet.javaDirectories)
    .asFileTree
    .files
    .toVariantFiles(sourceSet.name)
}

此代码执行以下操作:

迭代给定变体的 Android 源集(一个 Set<SourceProvider>)
获取每个源集的 Java 目录
将每个目录转换为 FileTree(指向文件而不是目录)
将该文件树作为 Set<File> 获取
将它们转换为名为 VariantFile 的自定义数据类,定义如下:

/**
 * Associates a [variant] ("main", "debug", "release", ...) with a 
 * [filePath] (to a file such as Java, Kotlin, or XML).
 */
data class VariantFile(
  val variant: String,
  val filePath: String
)

最后两个步骤(.files 和 .toVariantFiles())是昂贵的部分,分别占总成本的 35% 和 10%。

我能够通过添加一个完成这项工作的新任务(将工作从配置中推送到可缓存任务中)来找到这个问题的解决方案,并将新任务的输出作为我现在便宜得多的输入配置 ClassListAnalysisTask。

总 CPU 时间的 50% 到 7% 以下!不错。配置时间也从超过 6 秒减少到刚刚超过 3 秒。

使用 Gradle-Profiler 分析构建

如何分析构建的配置阶段:

$ gradle-profiler —profile async-profiler --project-dir <root-dir-of-build> help