scope.add(PROGUARD_FILE)
} else if (name.endsWith(DOT_PROPERTIES)) {
scope.add(PROPERTY_FILE)
} else if (name.endsWith(DOT_PNG)) {
scope.add(BINARY_RESOURCE_FILE)
} else if (name == RES_FOLDER || file.parent == RES_FOLDER) {
scope.add(ALL_RESOURCE_FILES)
scope.add(RESOURCE_FILE)
scope.add(BINARY_RESOURCE_FILE)
scope.add(RESOURCE_FOLDER)
}
}
} else {
// Specified a full project: just use the full project scope
scope = Scope.ALL
break
}
}
}
可以看到,如果Project的Subset为Null,Scope就为Scope.ALL,表示本次扫描会针对能检测的所有范围,相应地在扫描时也会用到所有全部的Detector来扫描文件。
如果Project的Subset不为Null,就遍历Subset的集合,找出Subset中的文件分别对应哪些范围。其实到这里我们已经可以知道,Subset就是我们增量扫描的突破点。接下来我们看一下runFileDetectors:
if(scope.contains(Scope.JAVA_FILE)||scope.contains(Scope.ALL_JAVA_FILES)){
val checks = union(scopeDetectors[Scope.JAVA_FILE],scopeDetectors[Scope.ALL_JAVA_FILES])
if (checks != null && !checks.isEmpty()) {
val files = project.subset
if (files != null) {
checkIndividualJavaFiles(project, main, checks, files)
} else {
val sourceFolders = project.javaSourceFolders
val testFolders = if (scope.contains(Scope.TEST_SOURCES))
project.testSourceFolders
else
emptyList ()
val generatedFolders = if (isCheckGeneratedSources)
project.generatedSourceFolders
else
emptyList ()
checkJava(project, main, sourceFolders, testFolders, generatedFolders, checks)
}
}
}
这里更加明确,如果project.subset不为空,就对单独的Java文件扫描,否则,就对源码文件和测试目录以及自动生成的代码目录进行扫描。整个runFileDetectors的扫描顺序入下:
- Scope.MANIFEST
- Scope.ALL_RESOURCE_FILES)|| scope.contains(Scope.RESOURCE_FILE) || scope.contains(Scope.RESOURCE_FOLDER) || scope.contains(Scope.BINARY_RESOURCE_FILE)
- scope.contains(Scope.JAVA_FILE) || scope.contains(Scope.ALL_JAVA_FILES)
- scope.contains(Scope.CLASS_FILE) || scope.contains(Scope.ALL_CLASS_FILES) || scope.contains(Scope.JAVA_LIBRARIES)
- scope.contains(Scope.GRADLE_FILE)
- scope.contains(Scope.OTHER)
- scope.contains(Scope.PROGUARD_FILE)
- scope.contains(Scope.PROPERTY_FILE)
与官方文档的描述顺序一致。
现在我们已经知道,增量扫描的突破点其实是需要构造project.subset对象。
/**
- Adds the given file to the list of files which should be checked in this
- project. If no files are added, the whole project will be checked.
- @param file the file to be checked
*/
public void addFile(@NonNull File file) {
if (files == null) {
files = new ArrayList<>();
}
files.add(file);
}
/**
- The list of files to be checked in this project. If null, the whole
- project should be checked.
- @return the subset of files to be checked, or null for the whole project
*/
@Nullable
public List getSubset() {
return files;
}
注释也很明确的说明了只要Files不为Null,就会扫描指定文件,否则扫描整个工程。
Lint增量扫描Gradle任务实现
前面分析了如何获取差异文件以及增量扫描的原理,分析的重点还是侧重在Lint工具本身的实现机制上。接下来分析,在Gradle中如何实现一个增量扫描任务。大家知道,通过执行./gradlew lint命令来执行Lint静态代码检测任务。创建一个新的Android工程,在Gradle任务列表中可以在Verification这个组下面找到几个Lint任务,如下所示:
这几个任务就是 Android Gradle插件在加载的时候默认创建的。分别对应于以下几个Task:
- lint->LintGlobalTask:由TaskManager创建;
- lintDebug、lintRelease、lintVitalRelease->LintPerVariantTask:由ApplicationTaskManager或者LibraryTaskManager创建,其中lintVitalRelease只在release下生成;
所以,在Android Gradle 插件中,应用于Lint的任务分别为LintGlobalTask和LintPerVariantTask。他们的区别是前者执行的是扫描所有Variant,后者执行只针对单独的Variant。而我们的增量扫描任务其实是跟Variant无关的,因为我们会把所有差异文件都收集到。无论是LintGlobalTask或者是LintPerVariantTask,都继承自LintBaseTask。最终的扫描任务在LintGradleExecution的runLint方法中执行,这个类位于lint-gradle-26.1.1中,前面提到这个库是基于Lint的API针对Gradle任务做的一些封装。
/** Runs lint on the given variant and returns the set of warnings */
private Pair<List, LintBaseline> runLint(
@Nullable Variant variant,
@NonNull VariantInputs variantInputs,
boolean report, boolean isAndroid) {
IssueRegistry registry = createIssueRegistry(isAndroid);
LintCliFlags flags = new LintCliFlags();
LintGradleClient client =
new LintGradleClient(
descriptor.getGradlePluginVersion(),
registry,
flags,
descriptor.getProject(),
descriptor.g