-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathAndroidLintRunner.kt
More file actions
164 lines (148 loc) · 5.35 KB
/
AndroidLintRunner.kt
File metadata and controls
164 lines (148 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package com.rules.android.lint.cli
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
import kotlin.io.path.pathString
import kotlin.io.path.writeText
internal class AndroidLintRunner {
internal fun runAndroidLint(
args: AndroidLintActionArgs,
workingDirectory: Path,
): Int {
// Create the input baseline file. This is either a copy of the existing baseline
// or a new temp one that can be written to
val baselineFile = workingDirectory.resolve("${args.label}_lint_baseline")
if (!args.regenerateBaselineFile && args.baselineFile != null) {
Files.copy(args.baselineFile!!, baselineFile)
}
// Split the aars and jars
val aars = args.classpath.filter { it.extension == "aar" }
val jars = args.classpath.filter { it.extension == "jar" }
require(aars.size + jars.size == args.classpath.size) { "Error: Classpath size mismatch" }
// Unarchive the AARs to avoid lint having to do this work. This also prevents some
// concurrency issues inside of Lint when multiplex workers are enabled
val unpackedAars = unpackAars(aars, workingDirectory.resolve("aars"))
// Collect the custom lint rules from the unpacked aars
val aarLintRuleJars = unpackedAars
.asSequence()
.map { it.first.resolve("lint.jar") }
.filter { it.exists() && it.isRegularFile() }
// Create the project configuration file for lint
val projectFile = workingDirectory.resolve("${args.label}_project_config.xml")
Files.createFile(projectFile)
val rootDir = System.getenv("PWD")
projectFile.writeText(
createProjectXMLString(
moduleName = args.label,
rootDir = rootDir,
srcs = args.srcs.sortedDescending(),
resources = args.resources.sortedDescending(),
androidManifest = args.androidManifest,
classpathJars = jars.sortedDescending(),
classpathAars = emptyList(),
classpathExtractedAarDirectories = unpackedAars,
customLintChecks = (args.customChecks + aarLintRuleJars).sortedDescending(),
),
)
// Run Android Lint
val androidCacheFolder = workingDirectory.resolve("android-cache")
Files.createDirectory(androidCacheFolder)
val invoker = AndroidLintCliInvoker.createUsingJars(jars = arrayOf(args.androidLintCliTool))
val exitCode = invokeAndroidLintCLI(
invoker = invoker,
actionArgs = args,
rootDirPath = rootDir,
projectFilePath = projectFile,
baselineFilePath = baselineFile,
cacheDirectoryPath = androidCacheFolder,
)
return when (exitCode) {
AndroidLintCliInvoker.ERRNO_SUCCESS,
AndroidLintCliInvoker.ERRNO_CREATED_BASELINE,
-> 0
else -> exitCode
}
}
private fun invokeAndroidLintCLI(
invoker: AndroidLintCliInvoker,
actionArgs: AndroidLintActionArgs,
rootDirPath: String,
projectFilePath: Path,
baselineFilePath: Path,
cacheDirectoryPath: Path,
): Int {
val args = mutableListOf(
"--project",
projectFilePath.pathString,
"--path-variables",
"PWD=$rootDirPath",
"--exitcode",
"--compile-sdk-version",
actionArgs.compileSdkVersion,
"--java-language-level",
actionArgs.javaLanguageLevel,
"--kotlin-language-level",
actionArgs.kotlinLanguageLevel,
"--stacktrace",
"--quiet",
"--offline",
"--baseline",
baselineFilePath.pathString,
"--update-baseline",
"--cache-dir",
cacheDirectoryPath.pathString,
"--client-id", "cli",
)
if (actionArgs.xmlOutput != null) {
args.add("--xml")
args.add(actionArgs.xmlOutput!!.pathString)
}
if (actionArgs.htmlOutput != null) {
args.add("--html")
args.add(actionArgs.htmlOutput!!.pathString)
}
if (actionArgs.warningsAsErrors) {
args.add("-Werror")
} else {
args.add("--nowarn")
}
if (actionArgs.config != null) {
args.add("--config")
args.add(actionArgs.config!!.pathString)
}
if (actionArgs.enableChecks.isNotEmpty()) {
args.add("--enable")
args.add(actionArgs.enableChecks.joinToString(","))
}
if (actionArgs.disableChecks.isNotEmpty()) {
args.add("--disable")
args.add(actionArgs.disableChecks.joinToString(","))
}
invoker.setCheckDependencies(actionArgs.enableCheckDependencies)
return invoker.invoke(args.toTypedArray())
}
/**
* Takes a list of AARs and unarchives them into the provided directory
* with this structure: ${tmpDirectory}/${aarFileName}--aar-unzipped/
*
* This is a necessary workaround for Lint wanting to unpack these aars into a global
* shared directory, which causes lots of obscure concurrency issues inside of lint
* when operating in persistent worker mode.
*/
private fun unpackAars(
aars: List<Path>,
dstDirectory: Path,
executorService: ExecutorService = Executors.newFixedThreadPool(6),
): List<Pair<Path, Path>> {
val aarsToUnpack = aars.map { it to dstDirectory.resolve("${it.name}-aar-contents") }
aarsToUnpack.forEach { (src, dst) -> unzip(src, dst) }
executorService.awaitTermination(15, TimeUnit.SECONDS)
return aarsToUnpack.sortedBy { it.first }
}
}